美文网首页Android开发Android开发
Android开源框架之OkHttp

Android开源框架之OkHttp

作者: 不会游泳的金鱼_ | 来源:发表于2019-05-25 19:58 被阅读6次

    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 等功能。

    相关文章

      网友评论

        本文标题:Android开源框架之OkHttp

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