Android开源框架之OkHttp

OkHttp相信搞android的都不陌生,它是目前应用最多的网络请求开源框架,虽然现在Retrofit更加流行,但到底层其实也是基于OkHttp的。你要是说你没用过OkHttp都不好意思说自己是做过Android开发。那么今天就来聊聊OkHttp。
本文的要点如下:

  • 概述
  • OkHttp的使用
  • 源码分析
    • 同步请求
    • 异步请求
  • 总结

概述

OkHttp是一个网络请求开源库,即将网络请求的相关功能封装好的类库。要知道,没有网络请求框架之前,App想与服务器进行网络请求交互是一件很痛苦的事,因为Android的主线程不能进行耗时操作,那么就需另开1个线程请求、考虑到线程池,缓存等一堆问题。
于是乎,网络请求库出现了,网络请求库的本质其实是封装了 网络请求 + 异步 + 数据处理功能的库

OkHttp的使用

同步请求:

GET请求:

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
            .url(url)
            .build();
    Response response = client.newCall(request).execute();
    return response.body().string();

POST请求:

    OkHttpClient client = new OkHttpClient();
    RequestBody requestBody = new FormBody.Builder()
              .add("username","abc")
              .add("password","123456")
              .build();
    Request request = new Request.Builder()
              .url("http://www.baidu.com")
              .post(requestBody)
              .build();
    Response response = client.newCall(request).execute();
    return response.body().string();

异步请求(以GET为例):

  OkHttpClient client = new OkHttpClient();
  Request request = new Request.Builder()
            .url(url)
            .build();
  client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }
});

使用这一块没什么好讲的,不同类型的请求方式有些许的不同,不过都比较好理解。下面我们还是来看看源码中究竟是怎么做的吧。

源码分析

ps:我用的源码是OkHttp3.14.1。
首先,使用OkHttp时,必然要先创建OkHttpClient对象。

 OkHttpClient client = new OkHttpClient();

一行代码就搞定了,似乎有点过于简单了,我们来看看OkHttpClient()里面做了什么:

  public OkHttpClient() {
    this(new Builder());
  }

原来是方便我们使用,提供了一个默认的配置,直接传入了一个默认的Builder类型的对象。Builder在初始化时就会设置这一些参数,全都是默认值。这里就是运用了Builder模式,简化了构建过程。

public Builder() {
      dispatcher = new Dispatcher();//调度器
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

同步请求

尽管现在项目中很少用同步请求了,但是其实异步请求的基础还是同步请求,只不过中间转换了线程而已。
在同步请求,初始化之后,我们又用Builder构建了Request对象,然后执行了OKHttpClient的newCall方法,那么咱们就看看这个newCall里面都做什么操作?

@Override 
public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

可以看出client.newCall(request).execute();实际上执行的是RealCall的execute方法,现在咱们再回来看下RealCall的execute的具体实现。

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      //同步判断,保证每个call只用一次,重复使用会抛出异常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
      client.dispatcher().executed(this);
      return getResponseWithInterceptorChain();
    } finally {
      client.dispatcher().finished(this);
    }
  }

这里主要做了 4 件事:

  1. 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用call#clone方法进行克隆。
  2. 利用client.dispatcher().executed(this)来进行实际执行,dispatcher是刚才看到的OkHttpClient.Builder的成员之一。
  3. 调用getResponseWithInterceptorChain()函数获取 HTTP 返回结果,从函数名也可以看出,这一步还会进行一系列“拦截”操作。
  4. 最后还要通知dispatcher自己已经执行完毕,释放dispatcher。

那么我们看下dispatcher里面的execute()是如何处理的。

    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }

可以看到,其实也很简单,runningSyncCalls执行了add方法,添加的参数是RealCall。runningSyncCalls是什么呢?

/** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

可以看到runningSyncCalls是双向队列。另外,我们发现Dispatcher里面定义了三个双向队列,看下注释,我们大概能明白readyAsyncCalls是一个存放了等待执行任务Call的双向队列,runningAsyncCalls是一个存放异步请求任务Call的双向任务队列,runningSyncCalls是一个存放同步请求的双向队列
那么这一步的目的就是将RealCall放入同步队列中

回到之前,执行完client.dispatcher().executed()方法,要执行getResponseWithInterceptorChain()方法,这就是OkHttp的核心拦截器的工作了:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加开发者应用层自定义的Interceptor
    interceptors.addAll(client.interceptors());
    //这个Interceptor是处理请求失败的重试,重定向    
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //这个Interceptor工作是添加一些请求的头部或其他信息
    //并对返回的Response做一些友好的处理(有一些信息你可能并不需要)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //这个Interceptor的职责是建立客户端和服务器的连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //添加开发者自定义的网络层拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //把chain传递到第一个Interceptor手中
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

可以看到,主要的操作就是new了一个ArrayList,然后就是不断的add拦截器Interceptor,之后new了一个RealInterceptorChain对象,最后调用了chain.proceed()方法。

我们来看看RealInterceptorChain()构造方法。

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;//将拦截链保存
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

就是一些赋值操作,将信息保存,关键的是this.interceptors = interceptors这里就保存了拦截链。

之后我们来看一下chain.proceed()方法获取返回的信息。由于Interceptor是个接口,所以应该是具体实现类RealInterceptorChain的proceed实现。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    //省略其他代码   
    calls++;
    RealInterceptorChain next = new RealInterceptorChain(
    interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    //省略其他代码   
    return response;
}

然后看到在proceed方面里面又new了一个RealInterceptorChain类的next对象,这个next对象和chain最大的区别就是index属性值不同chain是0,而next是1,然后取interceptors下标为1的对象的interceptor。由从上文可知,如果没有开发者自定义的应用层Interceptor时,首先调用的RetryAndFollowUpInterceptor,如果有开发者自己定义的应用层interceptor则调用开发者interceptor。

后面的流程都差不多,在每一个interceptor的intercept方法里面都会调用chain.proceed()从而调用下一个interceptor的intercept(next)方法,这样就可以实现遍历getResponseWithInterceptorChain里面interceptors的item,实现遍历循环。

之前我们看过getResponseWithInterceptorChain里面interceptors的最后一个item是CallServerInterceptor,最后一个Interceptor(即CallServerInterceptor)里面是直接返回了response 而不是进行继续递归。

CallServerInterceptor返回response后返回给上一个interceptor,一般是开发者自己定义的networkInterceptor,然后开发者自己的networkInterceptor把他的response返回给前一个interceptor,依次以此类推返回给第一个interceptor,这时候又回到了realCall里面的execute()里面了。

最后把response返回给get请求的返回值。至此同步GET请求的大体流程就已经结束了。

异步请求

讲了这么多同步请求,其实异步请求才是重头戏,毕竟现在的项目中大多用的都是异步请求。
由于前面的步骤和同步一样new了一个OKHttp和Request。这块和同步一样就不说了,那么说说和同步不一样的地方,后面异步进入的是newCall()的enqueue()方法
之前分析过,newCall()里面是生成了一个RealCall对象,那么执行的其实是RealCall的enqueue()方法。我们来看源码:

 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      //同步判断,保证每个call只用一次,重复使用会抛出异常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到和同步请求的enqueue方法一样,还是先同步判断是否被请求过了,不一样的地方就在于调用了client.dispatcher().enqueue(new AsyncCall(responseCallback))方法。即实际调用的是Dispatcher的enqueue()方法:

void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);//将call添加到了异步请求队列

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }

这里主要做了两件事:

  1. 将call添加到了异步请求队列;
  2. 调用promoteAndExecute方法。
private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);//将异步请求添加到executableCalls中
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

可以看出先是迭代了上面的队列,取出队列里的AsyncCall后添加到了executableCalls集合中。然后遍历这个集合,开始执行每个AsyncCall的executeOn方法。参数是executorService(),我们来具体看看:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

不难看出,这个方法传递进去的是ExecutorService线程池。那看来关键就在executeOn方法中了:

void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);//用executorService线程池执行了当前的线程
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

通过executorService线程池执行了当前的线程,也就是AsyncCall,那么AsyncCall由于继承了NamedRunnable,这个NamedRunnable的run方法里又执行了抽象方法execute,所以,实际上这里执行了AsyncCall的execute方法。

@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);//成功
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);//失败
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

终于,我们看到了几个熟悉的名字,getResponseWithInterceptorChain、onResponse和onFailure。不难看出,这里依旧是用getResponseWithInterceptorChain()通过拦截链进行请求,最终执行结果回调给了我们传递进去的Callback。至此,异步请求的主要流程也分析完了。

对比一下同步请求和异步请求,不难看出,其实异步请求就是维护了一个线程池用于进行请求,在请求完成之后回调我们一开始传入的CallBack接口。

总结

  1. OkHttpClient实现了Call.Factory,负责为Request创建Call;
  2. RealCall为具体的Call实现,execute()为同步接口,通过getResponseWithInterceptorChain()函数实现;enqueue()为异步接口通过Dispatcher利用ExecutorService线程池实现,而最终进行网络请求时和同步接口一致,都是通过getResponseWithInterceptorChain()函数实现;
  3. getResponseWithInterceptorChain()中利用拦截链机制,分层实现缓存、透明压缩、网络 IO 等功能。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容