美文网首页Android技术知识Android开发
OkHttp源码之同步、异步的区别

OkHttp源码之同步、异步的区别

作者: 低情商的大仙 | 来源:发表于2018-09-18 22:52 被阅读5次

    对于okhttp来说,大家都知道有同步和异步两种模式,所谓同步就是在发起请求的线程完成整个网络的连接传输过程,异步是新建一个线程完成这些流程,那么不知大家有没有想过,异步是不是就新的线程中直接调用同步的请求方法实现的呢?
    我们今天就来探讨这个问题。
    首先,我们看下同步请求方法:

    @Override public Response execute() throws IOException {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        //初始化异常扑捉器
        captureCallStackTrace();
        //进度监听
        eventListener.callStart(this);
        try {
        //只是简单的加入到了dispatcher的同步请求队列,没有任何作用
          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 {
        //将自己从dispatcher的同步队列中移除
          client.dispatcher().finished(this);
        }
      }
    

    上面的注释将每一步的作用都说的很清楚,核心就是那句getResponseWithInterceptorChain();这里是获取真正结果的。
    那么异步的是怎么样的呢?

    @Override public void enqueue(Callback responseCallback) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    

    这里我们看到这里核心就是封装了一个AsyncCall中,塞到了线程调度器中,真正的请求是在线程调度器中执行的,我们看下线程调度器中的相关代码:

     synchronized void enqueue(AsyncCall call) {
            //省略不相干代码
          executorService().execute(call);
          //省略不相干代码
       
      }
    

    可以看到,真正的异步请求是通过封装的AsyncCall来执行的,而且是通过一个线程池执行的,那么我们可以大胆猜测AsyncCall肯定有一个run()方法,核心请求在那里。
    我们继续看AsyncCall:

    final class AsyncCall extends NamedRunnable {
    

    可以看到,继承了NamedRunnable,继续跟进去:

    public abstract class NamedRunnable implements Runnable {
      protected final String name;
    
      public NamedRunnable(String format, Object... args) {
        this.name = Util.format(format, args);
      }
    
      @Override public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
          execute();
        } finally {
          Thread.currentThread().setName(oldName);
        }
      }
    
      protected abstract void execute();
    }
    

    整个NamedRunnable非常简单,主要就是为了给线程设置名字而已,我们重点关注的是在run()方法中调用了execute()方法,所以,AsyncCall的核心方法在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 {
              eventListener.callFailed(RealCall.this, e);
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            client.dispatcher().finished(this);
          }
        }
    

    可以看到这里并没有直接调用RealCall的同步请求方法execute(),而是类似的代码又写了一遍,这是为什么呢?我们来对比下两个方法有什么不一样
    先看同步:

    • 修改标记位
    • 初始化异常捕捉器
    • 进度监听器回调
    • 获取请求结果

    再看异步:

    • 获取请求结果
    • 判断请求是否结束,回调结果

    差异很明显,我们现在要分析的是造成这些差别的原因,首先是同步请求中有的:修改标记位、初始化异常扑捉器、进度监听器回调
    这个其实没有在AsyncCall中执行,而是在RealCall的enqueue()方法中执行的:

     @Override public void enqueue(Callback responseCallback) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    

    也就是说这三点其实并没有核心区别,只是在不同的地方调用了而已,那么真正的区别就是异步请求多了一个:判断请求是否结束,然后回调。
    其实仔细想想,也不难理解,对于同步请求来说,这个请求只在当前线程进行,没有其他线程操作这个请求,那么请求结束后也不存在被取消的情况,然而异步请求不一样,在子线程请求的过程中,这个请求有可能在其他线程被取消了,所以加了这样的判断是非常合理的。

    总结

    其实没什么好总结的,之所以异步请求没有直接调用同步请求方法就是因为异步多了一个判断而已,done!

    相关文章

      网友评论

        本文标题:OkHttp源码之同步、异步的区别

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