浅谈OkHttp-阿里云开发者社区

开发者社区> 开发与运维> 正文

浅谈OkHttp

简介: 本来想看看源码提高下自己的水平,以前也有看过某些框架的源码,但是基本都是只看某段,这次打算好好的研究一遍。本来是想拿RecyclerView的源码来开刀的,但是好像RecyclerView的代码没那么容易看懂,而且变量还贼多,还大量用了设计模式,讲真,看起来还真觉得挺费劲的。

本来想看看源码提高下自己的水平,以前也有看过某些框架的源码,但是基本都是只看某段,这次打算好好的研究一遍。本来是想拿RecyclerView的源码来开刀的,但是好像RecyclerView的代码没那么容易看懂,而且变量还贼多,还大量用了设计模式,讲真,看起来还真觉得挺费劲的。既然前段时间写了Http,那我就拿okhttp的代码来看看好了。

一.Okhttp

Android 4.4之后,HttpURLConnection底层实现已被OkHttp替换,okhttp内部依赖okio,现在的最新版本应该还是OkHttp3。其实我并直接的用过OkHttp来做网络请求的操作,我用的是Retrofit,基于OkHttp,其实差不多。

1.简单调用

一个简单的异步GET请求的话可以这样写

OkHttpClient mOkHttpClient = new OkHttpClient();  
final Request request = new Request.Builder()  
                .url("https://www.baidu.com/")  
                .build();  
Call call = mOkHttpClient.newCall(request);  
call.enqueue(new Callback());
2.okhttp的原理

我的总结就是获取到请求Call,进行一系列的Interceptors链的操作,异步的时候会涉及到Dispather的操作。

在网上很好的找到了一张图来说明这个过程(出自https://blog.piasy.com/2016/07/11/Understand-OkHttp/

img_c71584bf23d5a858ddf62b22c812d1d0.png

我觉得图中最核心的三个部分是


img_5b6ae0c2019dd3a875d303d1751f408e.png

二.浅谈Okhttp执行流程

同步和异步在调用时的区别就是同步调用的是execute方法,异步调用的是enqueue方法。

1.OkHttpClient

这个类用了Builder模式

  private OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
  }

里面也是有大量的属性,基本都是和整个请求过程中相关的参数,比如dns 啊,connectTimeout 啊这些,所以我把它看成是一个抽象整个请求过程的对象。而且它的注释也说了


img_9072a66af55e738e8628d0ce1344604e.png

就是一个Call的工厂,Call是什么之后会讲。

2.Request与Response

请求时会创建一个Request对象。这两个东西其实不用说就知道一个代表请求,一个代表响应。
看看这个Request其实也是在内部用了Builder模式


img_f5ec615fa8480e097f5d8604db3ede33.png

记住这个请求有五个属性,地址,方法(就是GET这些),tag表示的是标志,不知道是干啥的,好像是取消请求的时候会用到的。然后还有请求头和请求体。
(1)headers请求头
它有个参数,20个长度的列表,保存的是请求头相关的参数


img_b87d991cffd063b9dbccd9520c1760f0.png

比如addHeader方法,会调用Headers的add方法。
img_b2e80ce0429b3bc0600427ea48f1a793.png
image.png

这个checkNameAndValue是判断key和value是否符合,先不用管。
img_e4cfbe5df15b47c3ba8bb157fc1071ad.png

然后看到addLenient中是先加name再加value,所以到这里你就能知道namesAndValues的数组是怎么存值的。
其实这些都不是很重要,简单了解一下就行。

(2)RequestBody请求体


img_75998c997f16c63aa7b210452c48d2ab.png

它是一个抽象类,然后实体类,其实我这里主要是为了看这个,验证我上一篇讲http的请求体


img_e5fdc67183b50883d5d26954f234eb8a.png

可以看到请求的body是分普通情况和文件的情况。
这些都不重要。
3.Call

这个就是其中一个重要的东西了。
可以看到是用OkHttpClient的newCall方法创建Call,也验证了OkHttpClient就是Call的工厂。

img_3b128343f3b2f2dbd029a0f48e967a65.png

img_0c69459b3375c80656bdd3c265d19f01.png

Call是一次请求的抽象。既然是抽象,那就需要一个实现,那就是这里的RealCall,记住它代表一次请求,它代表一次请求,它代表一次请求,这一听是没什么,但是它这样的一个思想我觉得是很好的,你想想一次请求是什么,是一个行为,一个过程,它这里把行为进行抽象,从而方便于进行管理与操作。
RealCall有4个属性
img_8ba16078ee3329bc76aaeeaf9f4a0406.png

(1)它这里引用了OkHttpClient,我觉得是因为OkHttpClient里面保存了和整个过程相关的参数。但是这样相互引用的话耦合度会不会有点高。
(2)RetryAndFollowUpInterceptor是一个拦截器,后面会说。
(3)executed用来记录请求是否执行,一个请求只能执行一次
(4)originalRequest就是传进来的Request。

这个请求类,它的行为主要是一些请求的操作,比如开始同步请求,开始异步请求这些。所以如果你想对“请求”做什么操作,可以先来这个类来找找有没有对应的方法,比如取消请求,就在这里。

看看执行同步请求的方法。


img_976a39431003c6b391352f6f4ddd7ab2.png

第一个判断请求是否执行够,因为一个请求只允许执行一次。主要的操作是这3行

client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
client.dispatcher().finished(this);

这里有涉及到client.dispatcher(),这是OkHttpClient的一个参数,看看是个啥子。Dispatcher,它又是一个okhttp里面比较重要的对象,表示检测者,负责监视整个请求的过程,它内部有很多的参数。


img_35f096afd957ea45290859809ac8eeba.png

最大请求数maxRequests,最大主机请求数maxRequestsPerHost(好吧,其实我也不懂这是啥),有个Runnable,然后ExecutorService线程池,还有三个队列readyAsyncCalls、runningAsyncCalls和runningSyncCalls。
我讲讲这三个队列的意思,runningSyncCalls是存储同步请求,也就是Call(其实这里我有点蒙,你想想,同步请求如果有多个的话肯定是个排队请求的过程,而每个请求结束后都会移除队列,那这样的话这个队列不是一直都只有当前进行的请求吗,这样做有什么意义)。runningAsyncCalls是存储异步请求的队列,这个很有必要,因为异步是同时进行的,所以可以用个队列来存储,readyAsyncCalls表示准备进行异步请求的队列,因为有最大请的限制。

扯多了,看看同步请求时的executed方法和finished方法。


img_14a74d7be518ded52ee72d8118afd160.png

executed就是把请求添加到同步请求队列。


img_d139fa52197401d42b23ad4a41f59c62.png

img_5361a335f312f8213513ad834d0202ed.png

finish就是把请求从队列中移除。这个run()方法我不知道,我没找到在哪。

看完同步再来看看异步


img_79298e4713fe06c0f84c1255d8674ed9.png

异步的具体操作是写在Dispatcher里面


img_c3b4f32a759bb69aa3c630268ed6f40b.png

判断当前异步的队列数量做比较,超过了最大值就把Call添加到准备队列,否则添加到执行队列并执行线程,而AsyncCall继承了Runnable,所以executorService().execute(call)会执行call的run方法。这个run在内部又调用了execute方法,来看看这个方法。
 @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          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);
      }
    }
  }

这个也不难看懂吧,主要两行代码

Response response = getResponseWithInterceptorChain();
client.dispatcher().finished(this);

finished和上面的一样讲过了,不同的是在里面多调用了个promoteCalls方法,其实就是对runningAsyncCalls、readyAsyncCalls两个队列做操作,也不难看懂,影响不大。


img_7c8a74ac6906b307db0783f16b29e860.png

总结一下,不管是同步还是异步,做的操作都是 添加进队列——>调用getResponseWithInterceptorChain()方法——>从队列中移除。

到这里是不是就很容易看出getResponseWithInterceptorChain()就是请求网络整个过程的核心操作。

4.getResponseWithInterceptorChain()方法

我觉得这个就是整个okhttp里面最核心的操作,这个方法就是按顺序调用个个拦截器对请求进行处理。说得好听一点,这个叫责任链模式。

什么是责任链模式,不懂的我觉得有必要百度一波。简单点说就是一个请求就来,你对这个请求做处理或者不做处理,然后传给下一个人,一个一个的传。不了解的话不太容易看懂我下面的一些话。

getResponseWithInterceptorChain方法是RealCall类里面的,也可以说,这个请求执行了责任链的一系列操作。

img_db5e66a3ef2a268d8c06fd6e01113c7b.png

interceptors表示存储拦截器的列表,进行添加一些列的拦截器操作之后,创建拦截器链对象,然后执行proceed方法就能返回请求的响应Response。

那么重心又到了RealInterceptorChain这个链对象的身上。

img_3fd5a2504588f8ed6d1db802e7807613.png

先要注意一下它的这些参数。
(1)interceptors是拦截器列表
(2)streamAllocation 一开始传空进来
(3)httpStream 一开始传空进来
(4)connection 一开始传空
(5)index表示当前链的一个下标。我上边也说了,责任链执行操作后丢给下一个执行,这里就是每次丢给下一个拦截器的时候index加一。所以初始肯定是0
(6)request就是请求,包含什么参数之前也有写。

简单了解一下参数后看看proceed,看看具体的请求数据的流程是咋样的。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpStream != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpStream != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpStream, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpStream != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

先这样,所有关于报错的判断我们先不管,抽出核心的代码。

 RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpStream, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

它先创建一个新的责任链对象,然后下标加1,然后让第一个拦截器Interceptor 执行intercept操作,其实这个操作里面会调用新的责任链的proceed方法,这样就使得整个责任链模式给运作起来。其实我是感觉这个有点绕的,反正就是创建一个新的链对象,把链对象传给拦截器,然后拦截器里面又调用链对象的proceed方法,这个方法又重复这些操作直到最后一个拦截器。

稍微理清楚后来看看这个链上拦截器的一个顺序
其实就是列表中的顺序

img_362efb009ae074ad32bdebcdca10484a.png

先添加用户自定义的拦截器,这个我们后面再说吧。然后按这样的顺序添加
(1)RetryAndFollowUpInterceptor重试和重定向拦截器
(2)BridgeInterceptor 桥梁拦截器(我喜欢叫它配置请求拦截器)
(3)CacheInterceptor 缓存拦截器
(4)ConnectInterceptor 连接拦截器
(5)interceptors.addAll(client.networkInterceptors()); 是添加用户定义的网络请求拦截器(所以可以看出用户可以定义两种拦截器)
(7)CallServerInterceptor 内部的网络拦截器

因为我也并不是能完全的看懂这些拦截器内部的所有代码,所以我就不讲拦截器内详细做的操作了,免得误人子弟,我当时是看了这篇文章
https://blog.csdn.net/qq_19431333/article/details/53207220
其实还是看不懂详细的代码。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章