OkHttp源码解析(一):同步请求

作者: zYoung_Tang | 来源:发表于2018-09-06 14:33 被阅读17次

    本篇文章主要介绍同步的源码实现,异步的源码实现解析会放到下一篇文章

    1.内容

    • 简单的 Okhttp 例子
    • 同步请求 execute() 源码分析
    • 拦截器链调用流程
    依赖
    implementation 'com.squareup.okhttp3:okhttp:3.11.0
    

    2.例子

    OkHttpClient client = new OkHttpClient();
    Request request = new Request
                                .Builder()
                                .url("https://www.baidu.com")
                                .method("GET", null)
                                .build();
    // 同步请求
    Call synCall = client.newCall(request);
    Response response = synCall.execute();
    if (response.isSuccessful()) {
        ResponseBody body = response.body();
        if (body != null) {
            System.out.println(body.string());
        }
    } else System.err.println(response.message());
    
    // 异步请求
    Call asynCall = client.newCall(request);
    asynCall.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.err.println(e.getMessage());
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (response.isSuccessful()) {
                ResponseBody body = response.body();
                if (body != null) {
                    System.out.println(body.string());
                }
            } else System.err.println(response.message());
        }
    });
    

    Okhttp基本使用步骤如下:

    1. 创建 OkHttpClient 实例
    2. 创建 Request 实例
    3. 通过 OkHttpClient 和 Request 封装成一个请求 Call
    4. 同步请求调用 Call.execute(),异步请求调用 Call.enqueue()

    3.源码分析

    步骤 1 和 2 不深入从步骤 3 创建 Call 对象开始阅读源码

    // OkHttpClient.java
    @Override public Call newCall(Request request) {
        // 返回的是 RealCall 对象
        return RealCall.newRealCall(this, request, false /* for web socket */);
    }
    
    • Call 是一个 interface ,newCall() 方法返回的对象类型是 Call 的实现类 RealCall.

    • 也就是说同步执行请求的 execute() 和异步执行请求的 enqueue() 实现都在 RealCall 内.

    本文主要介绍 execute():

    // RealCall.java
    @Override public Response execute() throws IOException {
        synchronized (this) {
            // 判断该请求是否已被执行过
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        try {
            // 为了防止混淆这里调用 dispatcher 的 executed() 并没有执行该任务
            // 只是标记该 call 为正在执行的同步请求
            client.dispatcher().executed(this);
            // 向服务器发送 request 并获得服务器的 response
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } catch (IOException e) {
            eventListener.callFailed(this, e);
            throw e;
        } finally {
            // 最后要通知 dispatcher 该请求已完成,finished() 介绍放在下篇文章
            client.dispatcher().finished(this);
        }
    }
    

    关于任务调度器Ddispatcher的部分可以不管,因为它在同步请求中只做标记任务,在介绍异步请求的文章中我们再详细介绍它的功能。

    根据上面源码我们可以知道getResponseWithInterceptorChain()返回了 Response ,由此可见访问网络从服务器获取数据的操作都在getResponseWithInterceptorChain()内:

    // RealCall.java
    Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        // 创建一个拦截器链
        List<Interceptor> interceptors = new ArrayList<>();
        // 应用拦截器
        interceptors.addAll(client.interceptors());
        interceptors.add(retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
            // 网络拦截器
            interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));
        // originalRequest:我们写的 request
        Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        return chain.proceed(originalRequest);
    }
    

    这里引入了拦截器链,Request 需要通过拦截器链接收相应的处理最后才会发送到服务器并获取服务器返回的响应。

    拦截器链的添加顺序与拦截器执行顺序一致:

    1. 应用拦截器:开发者添加的拦截器
    2. retryAndFollowUpInterceptor:负责失败重连操作,以及重定向,如果 call 被取消会抛出 IOException
    3. BridgeInterceptor:作为网络层应用层之间的桥梁,把(应用层请求)user request转化为(网络层请求)network request,然后向服务器发送network request,得到(网络层响应)network reseponse后转化为(应用层响应) user response
    4. CacheInterceptor:处理缓存中的 requests 以及把 responses 写到缓存
    5. ConnectInterceptor:负责与目标服务器建立连接
    6. 网络拦截器:开发者添加的拦截器
    7. CallServerInterceptor:拦截器链的最后一个拦截器,负责向服务器发送请求和从服务器获取响应数据

    关于网络拦截器和应用拦截器的介绍可以查看这篇文章

    创建自定义拦截器并添加到应用拦截器或者网络拦截器流程如下:

    1. 创建类继承Interceptor
    class MyInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            return chain.proceed(chain.request());
        }
    }
    
    1. 在创建OkHttpClient的时候用 Builder 模式创建并添加应用拦截器
    OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new MyInterceptor())
        .build();
    
    1. 在创建OkHttpClient的时候用 Builder 模式创建并添加网络拦截器
    OkHttpClient client = new OkHttpClient.Builder()
        .addNetworkInterceptor(new LoggingInterceptor())
        .build();
    

    再来看看getResponseWithInterceptorChain()最后一行代码 return chain.proceed(originalRequest);

    // RealInterceptorChain.java 
    @Override public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
    }
    
    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
       
         ... // 略过
    
        // Call the next interceptor in the chain.
        // 创建执行目标为下一个拦截器的 RealInterceptorChain 对象
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
            writeTimeout);
        // 获取当前目标拦截器
        Interceptor interceptor = interceptors.get(index);
        // 调用当前拦截器的 intercept() 并传入下一个拦截器链
        Response response = interceptor.intercept(next);
    
        // Confirm that the next interceptor made its required call to chain.proceed().
        if (httpCodec != 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");
        }
    
        if (response.body() == null) {
          throw new IllegalStateException(
              "interceptor " + interceptor + " returned a response with no body");
        }
    
        return response;
    }
    

    拦截器链的思路和工作原理:

    • 把根据功能细分为多个拦截器。
    • 按照拦截顺序组成一个拦截器链。
    • Reuqest 拦截器链中传递(可被修改)并在每个拦截器中调用Interceptor.intercept()完成各自的功能。
    • 最后返回从服务器获取的响应Response

    问题:拦截器链如何而实现按序调用Interceptor.intercept()

    • 主要涉及两个方法

      /** 
      * Interceptor
      * chain : 目标拦截器为 this 拦截器的下一个拦截器 
      */
      Response intercept(Chain chain) throws IOException;
      /**
      * Interceptor.Chain
      * request : 我们传的 request ,可以被拦截器修改,给最后的 CallServerInterceptor 向服务器发送请求
      */
      Response proceed(Request request) throws IOException;
      
    1. chain.proceed(request)会创建一个目标拦截器为下一个拦截器的拦截器链InterceptorChain对象 next ,然后调用当前目标拦截器的intercept(next)

    2. 除了最后一个拦截器,其余拦截器的都在intercept()内直接或间接调用chain.proceed(request)重新回到上一步,然后重复刚才的步骤。这样就达到遍历拦截器链且按照顺序执行拦截器的具体操作的目的。

    3. getResponseWithInterceptorChain()返回的是服务器的响应 Response,所以 CallServerInterceptor 必须在拦截器链的最后一个,因为它是真正向服务器发送请求和获取响应的拦截器。

    相关文章

      网友评论

        本文标题:OkHttp源码解析(一):同步请求

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