Android小知识-剖析OkHttp中的同步请求

无论是同步请求还是异步请求,都需要一个OkHttpClient。

    private OkHttpClient mHttpClient = null;

    private void initHttpClient() {
        if (null == mHttpClient) {
            mHttpClient = new OkHttpClient.Builder()
                    .readTimeout(5, TimeUnit.SECONDS)//设置读超时
                    .writeTimeout(5,TimeUnit.SECONDS)设置写超时
                    .connectTimeout(15,TimeUnit.SECONDS)//设置连接超时
                    .retryOnConnectionFailure(true)//是否自动重连
                    .build();
        }
    }

进入OkHttpClient的静态内部类Builder:

    public static final class Builder {

        Dispatcher dispatcher;
        ConnectionPool connectionPool;

        public Builder() {
            //分发器
            dispatcher = new Dispatcher();
            //连接池
            connectionPool = new ConnectionPool();
            ...
        }

        public OkHttpClient build() {
            return new OkHttpClient(this);
        }
    }

Builder构造器进行初始化操作,这里先介绍几个比较重要参数

  1. Dispatcher分发器:它的作用是用于在异步请求时是直接进行处理,还是进行缓存等待,对于同步请求,Dispatcher会将请求放入队列中。

  2. ConnectionPool连接池:可以将客户端与服务器的连接理解为一个connection,每一个connection都会放在ConnectionPool这个连接池中,当请求的URL相同时,可以复用连接池中的connection,并且ConnectionPool实现了哪些网络连接可以保持打开状态以及哪些网络连接可以复用的相应策略的设置。

通过Builder进行了一些参数的初始化,最后通过build方法创建OkHttpClient对象。

创建OkHttpClient对象时使用到了设计模式中的Builder模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

    private void synRequest() {
        Request request=new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();
        Call call=mHttpClient.newCall(request);
        try {
            Response response=call.execute();
            System.out.println(request.body().toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

创建完OkHttpClient对象后,接着创建Request对象,也就是我们的请求对象。Request的创建方式和OkHttpClient的创建方式一样,都使用了Builder模式。

Request内部类Builder的构造方法:

    public static class Builder {
        String method;
        Headers.Builder headers;

        public Builder() {
            this.method = "GET";
            this.headers = new Headers.Builder();
        }

    }

Request内部类的Builder构造方法非常的简单,初始化了请求方式,默认是GET请求,接着初始化了头部信息。

接着看它的build方法:

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }

build方法中先校验传入的url是否为空,再将当前配置的请求参数传给Request。

Request的构造方法:

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

将我们配置好的参数,比如请求地址url、请求方式、请求头部信息、请求体以及请求标志传给了Request。

总结同步请求的前两步:

  1. 创建一个OkHttpClient对象。

  2. 构建了携带请求信息的Request对象。

完成上面两步后,就可以通过OkHttpClient的newCall方法将Request包装成Call对象。

Call call=mHttpClient.newCall(request);

进入OkHttpClient的newCall方法:

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

Call只是一个接口,因此它的所有操作都在RealCall中实现的。

看一下RealCall的newRealCall方法:

    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.eventListener = client.eventListenerFactory().create(call);
        return call;
    }

可以看到newRealCall方法只有三行代码,第一行创建RealCall对象,第二行设置RealCall的eventListener也就是监听事件,最后返回RealCall对象,我们看下RealCall的构造方法。

    private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        this.client = client;
        this.originalRequest = originalRequest;
        this.forWebSocket = forWebSocket;
        //重定向拦截器
        this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
    }

RealCall的构造方法中,设置了前两步创建的对象OkHttpClient和Request并设置了重定向拦截器(拦截器概念后面会进行详解)。

到这里Call对象也创建完毕了,最后通过Call的execute方法来完成同步请求,看一下execute方法到底做了哪些操作,由于Call是接口,execute方法的具体实现在RealCall中:

    @Override public Response execute() throws IOException {
        //第一步:判断同一Http是否请求过
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        //捕捉Http请求的异常堆栈信息
        captureCallStackTrace();
        //监听请求开始
        eventListener.callStart(this);
        try {
            //第二步:同步请求添加到同步队列中
            client.dispatcher().executed(this);
            //第三步:
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } catch (IOException e) {
            eventListener.callFailed(this, e);
            throw e;
        } finally {
            //第四步:回收请求
            client.dispatcher().finished(this);
        }
    }

第一步,在同步代码块中判断标志位executed是否为ture,为true抛出异常,也就是说同一个Http请求只能执行一次。

第二步,调用Dispatcher分发器的executed方法,我们进入Dispatcher的executed方法中。

    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

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

Dispatcher的executed方法只是将我们的RealCall对象也就是请求添加到runningSyncCalls同步队列中。

Dispatcher的作用就是维持Call请求发给它的一些状态,同时维护一个线程池,用于执行网络请求,Call这个请求在执行任务时通过Dispatcher分发器,将它的任务添加到执行队列中进行操作。

第三步,通过getResponseWithInterceptorChain方法获取Response对象,getResponseWithInterceptorChain方法的作用是通过一系列拦截器进行操作,最终获取请求结果。

第四步,在finally块中,主动回收同步请求,进入Dispatcher的finished方法:

    void finished(RealCall call) {
        finished(runningSyncCalls, call, false);
    }

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            //移除请求
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            if (promoteCalls) promoteCalls();
            //计算同步请求队列+异步请求队列的总数
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }

        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }

将我们正在执行的同步请求RealCall对象通过finished方法传了进去,接着从当前的同步队列中移除本次同步请求,promoteCalls默认传入是false,也就是promoteCalls方法不会执行到,但如果是异步请求,这里传入的是ture,会执行promoteCalls方法,关于异步请求后面进行讲解。从同步队列中清除当前请求RealCall后,重新计算当前请求总数,我们可以看下runningCallsCount方法的具体实现。

  public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }

方法非常简单,就是计算正在执行的同步请求和异步请求的总和。

在finished方法最后判断runningCallsCount,如果正在执行的请求数为0并且idleaCallback不为null,就执行idleaCallback的回调方法run。

到这里同步请求已经介绍完了,在同步请求中Dispatcher分发器做的工作很简单,就做了保存同步请求和移除同步请求的操作。

总结流程图如下:

技术共享笔记

©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页