美文网首页Android程序员Android开发经验谈
OkHttp 知识梳理(1) - OkHttp 源码解析之入门

OkHttp 知识梳理(1) - OkHttp 源码解析之入门

作者: 泽毛 | 来源:发表于2017-11-21 22:44 被阅读504次

    OkHttp 知识梳理系列文章

    OkHttp 知识梳理(1) - OkHttp 源码解析之入门
    OkHttp 知识梳理(2) - OkHttp 源码解析之异步请求 & 线程调度
    OkHttp 知识梳理(3) - OkHttp 之缓存基础


    一、简介

    OkHttp无疑是目前使用的最多的网络框架,现在最火的Retrofit也就是基于OkHttp来实现的,和以前一样,我们将从最简单的例子开始,一步步剖析OkHttp的使用及内部实现原理。

    OkHttp项目相关文档

    二、OKHttp 的使用

    2.1 应用背景

    我们首先用一个简单的例子来演示OkHttp的简单使用 - 通过中国天气网提供的官方API获取城市的天气,完整的代码可以查看我的Github仓库 RepoOkHttp 中第一章的例子。

    2.2 引入依赖

    目前官网的最新版本为3.9.1,因此我们需要在build.gradle文件中进行声明:

    compile 'com.squareup.okhttp3:okhttp:3.9.1'
    

    不要忘了在AndroidManifest.xml中声明网络权限:

    <uses-permission android:name="android.permission.INTERNET" />
    

    最后,我们用一个简单的例子演示OkHttp的同步请求,首先,通过HandlerThread创建一个异步线程,通过发送消息的形式,通知它发起网络请求,请求完毕之后,将结果中的body部分发送回主线程用TextView进行展示:

    public class SimpleActivity extends AppCompatActivity {
    
        private static final String URL = "http://www.weather.com.cn/adat/sk/101010100.html";
        private static final int MSG_REQUEST = 0;
        private static final int MSG_UPDATE_UI = 0;
    
        private Button mBtRequest;
        private Button mBtRequestAsync;
        private TextView mTvResult;
        private BackgroundHandler mBackgroundHandler;
        private MainHandler mMainHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_simple);
            mBtRequest = (Button) findViewById(R.id.bt_request_sync);
            mBtRequestAsync = (Button) findViewById(R.id.bt_request_async);
            mTvResult = (TextView) findViewById(R.id.tv_result);
            mBtRequest.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    startSyncRequest();
                }
    
            });
            mBtRequestAsync.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    startAsyncRequest();
                }
            });
            HandlerThread backgroundThread = new HandlerThread("backgroundThread");
            backgroundThread.start();
            mBackgroundHandler = new BackgroundHandler(backgroundThread.getLooper());
            mMainHandler = new MainHandler();
        }
    
        /**
         * 同步发起请求的例子。
         */
        private void startSyncRequest() {
            //发送消息到异步线程,发起请求。
            mBackgroundHandler.sendEmptyMessage(MSG_REQUEST);
        }
    
        /**
         * 异步发起请求的例子。
         */
        private void startAsyncRequest() {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(URL).build();
            Call call = client.newCall(request);
            call.enqueue(new Callback() {
    
                @Override
                public void onFailure(Call call, IOException e) {
    
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String result = response.body().string();
                    //返回结果给主线程。
                    Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
                    mMainHandler.sendMessage(message);
                }
            });
        }
    
        private class BackgroundHandler extends Handler {
    
            BackgroundHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //在异步线程发起请求。
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder().url(URL).build();
                Call call = client.newCall(request);
                try {
                    Response response = call.execute();
                    String result = response.body().string();
                    //返回结果给主线程。
                    Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
                    mMainHandler.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private class MainHandler extends Handler {
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //主线程获取到结果之后进行更新。
                String result = (String) msg.obj;
                mTvResult.setText(result);
            }
        }
    
    }
    

    运行结果为:


    运行结果

    三、简要流程分析

    是不是很简单,仅仅几句话就完成了网络请求,核心的四个步骤为:

    • 构建OkHttpClient对象
    • 构建Request对象
    • 由前两步创建的OkHttpClientRequest对象创建Call对象
    • 通过Call对象发起请求,并得到一个Response,它就是最终返回的结果。
    //1.构建 OkHttpClient 对象
    OkHttpClient client = new OkHttpClient();
    //2.构建 Request 对象。
    Request request = new Request.Builder().url(URL).build();
    //3.由 OkHttpClient 通过 Request 创建 Call 对象
    Call call = client.newCall(request);
    try {
        //4.通过 Call 对象发起请求
        Response response = call.execute();
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    3.1 构建 OkHttpClient 对象

    OkHttpClient提供了许多配置项供使用者进行设置,例如缓存、Cookie、超时时间等等,对于其中参数的初始化可以采用建造者模式,其源码地址为 OkHttpClient.java,例如:

    OkHttpClient.Builder builder = new OkHttpClient.Builder()
                        .connectTimeout(3000, TimeUnit.MILLISECONDS)
                        .readTimeout(3000, TimeUnit.MILLISECONDS);
    OkHttpClient client = builder.build();
    

    如果我们什么也不做,像最开始的那样直接new OkHttpClient(),那么将会采用默认的配置,部分配置如下,关于各个参数的含义可以参考:OkHttpClient.Builder

    //(1) Sets the dispatcher used to set policy and execute asynchronous requests.
    this.dispatcher = new Dispatcher();
    
    //(2) Configure the protocols used by this client to communicate with remote servers
    this.protocols = OkHttpClient.DEFAULT_PROTOCOLS;
    
    //(3) Unknow 
    this.connectionSpecs = OkHttpClient.DEFAULT_CONNECTION_SPECS;
    
    //(4) Configure a factory to provide per-call scoped listeners that will receive analytic events for this client
    this.eventListenerFactory = EventListener.factory(EventListener.NONE);
    
    //(5) Sets the proxy selection policy to be used if no is specified explicitly
    this.proxySelector = ProxySelector.getDefault();
    
    //(6) Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to outgoing HTTP requests
    this.cookieJar = CookieJar.NO_COOKIES;
    
    //(7) Sets the socket factory used to create connections
    this.socketFactory = SocketFactory.getDefault();
    
    //(8) Sets the verifier used to confirm that response certificates apply to requested hostnames for HTTPS connections
    this.hostnameVerifier = OkHostnameVerifier.INSTANCE;
    
    //(9) Sets the certificate pinner that constrains which certificates are trusted
    this.certificatePinner = CertificatePinner.DEFAULT;
    
    //(10) Sets the authenticator used to respond to challenges from proxy servers
    this.proxyAuthenticator = Authenticator.NONE;
    
    //(11) Sets the authenticator used to respond to challenges from origin servers
    this.authenticator = Authenticator.NONE;
    
    //(12) Sets the connection pool used to recycle HTTP and HTTPS connections
    this.connectionPool = new ConnectionPool();
    
    //(13) ets the DNS service used to lookup IP addresses for hostnames
    this.dns = Dns.SYSTEM;
    
    //(14) Configure this client to follow redirects from HTTPS to HTTP and from HTTP to HTTPS
    this.followSslRedirects = true;
    
    //(15) Configure this client to follow redirects
    this.followRedirects = true;
    
    //(16) Configure this client to retry or not when a connectivity problem is encountered
    this.retryOnConnectionFailure = true;
    
    //(17) TimeOut
    this.connectTimeout = 10000;
    this.readTimeout = 10000;
    this.writeTimeout = 10000;
    
    //(18) Sets the interval between web socket pings initiated by this client
    this.pingInterval = 0;
    

    上面的参数很多,后面用到的时候再去分析,这里我们主要关注两个重要的成员变量,它们是Interceptor类型元素的列表,在后面我们将会花很大的篇幅来介绍它:

    final List<Interceptor> interceptors = new ArrayList();
    final List<Interceptor> networkInterceptors = new ArrayList();
    

    3.2 构建 Request

    OkHttpClient用于全局的参数配置,一般来说,一个进程中拥有一个OkHttpClient对象即可。而Request则对应于一个请求的具体信息,每发起一次请求,就需要创建一个新的Request对象,其配置信息包括请求的urlmethodheadersbody等等,这些都是HTTP的基础知识,推荐大家看一下这篇文章 一篇文章带你详解 HTTP 协议(网络协议篇一),总结得很全面。

    Request中的参数也可以通过建造者模式来进行配置,其源码地址为 Request.java

    3.3 构建 Call

    经过前面两步我们得到了OkHttpClientRequest这两个实例,接下来就需要创建请求的具体执行者:

    Call call = client.newCall(request);
    

    newCall的代码很简单,其实就是通过RealCall的静态方法返回了一个RealCall对象,并持有OkHttpClientRequest的引用,同时它实现了Call接口:

    public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false);
    }
    

    Call接口的定义如下,它定义了请求的接口:

    public interface Call extends Cloneable {
    
        //返回原始的请求对象。
        Request request();
        //同步发起请求。
        Response execute() throws IOException;
        //异步发起请求。
        void enqueue(Callback var1);
        //取消请求。
        void cancel();
        //请求是否已经被执行。
        boolean isExecuted();
        //请求是否取消。
        boolean isCanceled();
        //clone 对象。
        Call clone();
        //工厂类。
        public interface Factory {
            Call newCall(Request var1);
        }
    
    }
    

    当使用RealCall发起请求时,有同步和异步两种方式,分别对应于.execute.enqueue两个方法,这里我们先将 同步的方法,因为 异步 也是建立在它的基础之上的。

    3.4 同步发起请求 - execute()

        //[同步请求的函数]
        public Response execute() throws IOException {
            synchronized(this) {
                //如果已经发起过请求,那么直接跑出异常。
                if(this.executed) {
                    throw new IllegalStateException("Already Executed");
                }
                //标记为已经发起过请求。
                this.executed = true;
            }
            //捕获这个请求的 StackTrace。
            this.captureCallStackTrace();
            //通知监听者已经开始请求。
            this.eventListener.callStart(this);
    
            Response var2;
            try {
                //通过 OkHttpClient 的调度器执行请求。
                this.client.dispatcher().executed(this);
                //getResponseWithInterceptorChain() 责任链模式。
                Response result = this.getResponseWithInterceptorChain();
                if(result == null) {
                    throw new IOException("Canceled");
                }
                
                var2 = result;
            } catch (IOException var7) {
                //通知监听者发生了异常。
                this.eventListener.callFailed(this, var7);
                throw var7;
            } finally {
                //通过调度器结束该任务。
                this.client.dispatcher().finished(this);
            }
            //返回结果。
            return var2;
        }
    
        Response getResponseWithInterceptorChain() throws IOException {
            List<Interceptor> interceptors = new ArrayList();
            interceptors.addAll(this.client.interceptors());
            interceptors.add(this.retryAndFollowUpInterceptor);
            interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
            interceptors.add(new CacheInterceptor(this.client.internalCache()));
            interceptors.add(new ConnectInterceptor(this.client));
            if(!this.forWebSocket) {
                interceptors.addAll(this.client.networkInterceptors());
            }
    
            interceptors.add(new CallServerInterceptor(this.forWebSocket));
            Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
            return chain.proceed(this.originalRequest);
        }
    

    整个通过请求分为以下几步:

    3.4.1 将 Call 任务加入到同步队列当中

    this.client.dispatcher().executed(this);
    

    这里的执行并不是真正的执行,默认情况下调度器的实现为Dispatcher,它的executed方法实现为:

        //同步队列。
        private final Deque<RealCall> runningSyncCalls = new ArrayDeque();
    
        //Dispatcher.executed 仅仅是将 Call 加入到队列当中,而并没有真正执行。
        synchronized void executed(RealCall call) {
            this.runningSyncCalls.add(call);
        }
    

    3.4.2 执行请求

    Response result = this.getResponseWithInterceptorChain()
    

    真正地触发了请求的执行是上面这句,我们来简单看一下getResponseWithInterceptorChain是怎么触发请求的。

        Response getResponseWithInterceptorChain() throws IOException {
            List<Interceptor> interceptors = new ArrayList();
            //先加入使用者自定义的拦截器。
            interceptors.addAll(this.client.interceptors());
            //加入标准的拦截器。
            interceptors.add(this.retryAndFollowUpInterceptor);
            interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
            interceptors.add(new CacheInterceptor(this.client.internalCache()));
            interceptors.add(new ConnectInterceptor(this.client));
            if(!this.forWebSocket) {
                interceptors.addAll(this.client.networkInterceptors());
            }
            //访问服务器的拦截器。
            interceptors.add(new CallServerInterceptor(this.forWebSocket));
            //创建调用链,注意第五个参数目前的值为0。
            Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
            //执行调用链的 proceed 方法。
            return chain.proceed(this.originalRequest);
        }
    

    这里我们将一系列的Interceptor加入到了List当中,构建完之后,List中的内容如下所示,对于使用者自定义的interceptors将会加在列表的头部,而自定义的networkInterceptors则会加在CallServerInterceptor之前:

    List<Interceptor>
    接着创建了一个RealInterceptorChain对象,它的构造函数的第1参数就是上面List<Interceptor>列表,除此之外还需要注意第5个参数为0,这个对于下面的分析很重要,最后就是调用了RealInterceptorChainproceed方法,其实参就是前面创建的Request对象,为了便于理解,我们再看一下RealInterceptorChain RealInterceptorChain

    接下来,看一下 最关键的 RealInterceptorChainproceed中的逻辑:

        //RealInterceptorChain 的 proceed 方法。
        public Response proceed(Request request) throws IOException {
            return this.proceed(request, this.streamAllocation, this.httpCodec, this.connection);
        }
    
    
        public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
            if(this.index >= this.interceptors.size()) {
                throw new AssertionError();
            } else {
                ++this.calls;
                if(this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
                    throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must retain the same host and port");
                } else if(this.httpCodec != null && this.calls > 1) {
                    throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must call proceed() exactly once");
                } else {
                    //关键部分的代码是这几句。
                    //(1) 创建一个新的 RealInterceptorChain 对象,这里注意前面说的第5个参数变成了 index+1。
                    RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request, this.call, this.eventListener, this.connectTimeout, this.readTimeout, this.writeTimeout);
                    //(2) 取列表中位于 index 位置的拦截器。
                    Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);
                    //(3) 调用它的 intercept 方法,并传入新创建的 RealInterceptorChain。
                    Response response = interceptor.intercept(next);
                    if(httpCodec != null && this.index + 1 < this.interceptors.size() && next.calls != 1) {
                        throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once");
                    } else if(response == null) {
                        throw new NullPointerException("interceptor " + interceptor + " returned null");
                    } else if(response.body() == null) {
                        throw new IllegalStateException("interceptor " + interceptor + " returned a response with no body");
                    } else {
                        return response;
                    }
                }
            }
        }
    

    忽略不重要的代码,RealInterceptorChain关键的代码有三个步骤:

    • 创建一个新的RealInterceptorChain对象,这里注意前面说的第5个参数变成了 index+1
    • 取列表中位于index位置的拦截器。
    • 调用它的intercept方法,并传入新创建的RealInterceptorChain

    而每个Interceptor在执行完它的操作之后,就会调用RealInterceptorChainproceed方法,使得下一个Interceptorintercept方法可以被执行,以第一个拦截器RetryAndFollowUpInterceptor为例:

        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            Call call = realChain.call();
            EventListener eventListener = realChain.eventListener();
            this.streamAllocation = new StreamAllocation(this.client.connectionPool(), this.createAddress(request.url()), call, eventListener, this.callStackTrace);
            int followUpCount = 0;
            Response priorResponse = null;
            //这是一个 While 循环,知道没有达到终止的条件就一直重试。
            while(!this.canceled) {
                boolean releaseConnection = true;
    
                Response response;
                try {
                    //调用下一个拦截器。
                    response = realChain.proceed(request, this.streamAllocation, (HttpCodec)null, (RealConnection)null);
                    releaseConnection = false;
                } catch (RouteException var16) {
                    if(!this.recover(var16.getLastConnectException(), false, request)) {
                        throw var16.getLastConnectException();
                    }
    
                    releaseConnection = false;
                    continue;
                } catch (IOException var17) {
                    boolean requestSendStarted = !(var17 instanceof ConnectionShutdownException);
                    if(!this.recover(var17, requestSendStarted, request)) {
                        throw var17;
                    }
    
                    releaseConnection = false;
                    continue;
                } finally {
                    if(releaseConnection) {
                        this.streamAllocation.streamFailed((IOException)null);
                        this.streamAllocation.release();
                    }
    
                }
    
                if(priorResponse != null) {
                    response = response.newBuilder().priorResponse(priorResponse.newBuilder().body((ResponseBody)null).build()).build();
                }
    
                Request followUp = this.followUpRequest(response);
                if(followUp == null) {
                    if(!this.forWebSocket) {
                        this.streamAllocation.release();
                    }
                    //返回了 response,那么整个调用就结束了。
                    return response;
                }
                //....
            }
    
            this.streamAllocation.release();
            throw new IOException("Canceled");
        }
    

    整个递归调用的过程为:

    递归调用结果

    在整个递归调用过程中,如果有任意一个Interceptorintercept方法返回了而没有调用proceed方法,那么整个调用将会结束,排在它之后的Interceptor将不会被执行。

    CallServerInterceptor

    CallServerInterceptor是最后一个Interceptor,与之前的拦截器不同,在它的intercept方法中 不会创建一个新的 RealInterceptorChain ,而是直接返回了Response,使得整个递归调用一步步向上返回。

        public Response intercept(Chain chain) throws IOException {
            //发起请求..
            Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
            realChain.eventListener().responseHeadersEnd(realChain.call(), response);
            int code = response.code();
            if(this.forWebSocket && code == 101) {
                response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
            } else {
                response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();
            }
            //只有两种选择,抛出异常或者返回结果,不会进行下一步的调用。
            if((code == 204 || code == 205) && response.body().contentLength() > 0L) {
                throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
            } else {
                return response;
            }
        }
    

    3.4.3 结束任务

    回到最开始的代码,当getResponseWithInterceptorChain返回之后,最后通过dispatcher.finish(RealCall call)方法结束任务:

        void finished(RealCall call) {
            this.finished(this.runningSyncCalls, call, false);
        }
    
        private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
            int runningCallsCount;
            Runnable idleCallback;
            synchronized(this) {
                //从 runningSyncCalls 移除它。
                if (!calls.remove(call)) {
                    throw new AssertionError("Call wasn't in-flight!");
                }
                //false 不执行。
                if (promoteCalls) {
                    this.promoteCalls();
                }
    
                runningCallsCount = this.runningCallsCount();
                idleCallback = this.idleCallback;
            }
            //如果当前已经没有可以执行的任务,那么调用 idleCallback.run() 方法。
            if (runningCallsCount == 0 && idleCallback != null) {
                idleCallback.run();
            }
    
        }
    
        public synchronized int runningCallsCount() {
            return this.runningAsyncCalls.size() + this.runningSyncCalls.size();
        }
    

    四、小结

    可以看到,对于 同步请求,这个函数的调用都是在.execute()调用的线程执行的,其实 异步请求 的核心逻辑和同步请求是相同的,只不过加入了线程的管理。不知不觉说得又有点长了,还是把 异步请求 的分析放在下一篇文章里面讲吧,顺便结合OkHttp中对于线程的管理,这一章只是一个入门,关键是让大家对整个OkHttp请求的流程有个大概的印象,特别是调用链的模式。


    更多文章,欢迎访问我的 Android 知识梳理系列:

    相关文章

      网友评论

        本文标题:OkHttp 知识梳理(1) - OkHttp 源码解析之入门

        本文链接:https://www.haomeiwen.com/subject/fmmbvxtx.html