android 线程池应用(2) - okhttp

android 线程池应用(2) - okhttp_第1张图片
好喜欢~

我们学习线程池的应用 okhttp 这个使用最多的网络库是跑不了的,不难啊,只要大家前面的线程池的内容都了解了,这里没什么难度,都是线程池的应用,谁也比不谁高大上多少,又不是让你写一个,看懂总
是没问题的


寻找 线程池 在 okhttp 的入口

okhttp 使用如下

        OkHttpClient().newCall(null).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
      
            }

            override fun onResponse(call: Call?, response: Response?) {

            }
        })

看到 onFailure,onResponse 大家熟悉不,想到什么没,AsyncTask 看着和这个熟悉不。看到 callback 大家就没联想到 runnable 吗,多线程,futrueTask 都是把我们传进来的 callback 包装在自己的 runnable 中调用,获取数据后回调我们传入 callback 的相关方法,都是这个套路,looper 也是如此,区别是 looper 使用 handle 把任务切换到 mian 线程取去执行

enqueue 添加任务的方法显然是 newCall 出来的对象提供的,我们继续跟进去

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

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

  // 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));
  }

在这里大家看见 dispatcher 这个角色了,dispatcher 也叫线程调度器,我们继续深入

  // dispatcher.enqueue()
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

  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;
  }

到这里我们就找到跟了吧,okhttp 内部也是线程池实现的,线程池配置是自己做的,为的还不就是用自己的线程类型呗,要不直接用 Executors.newCachedThreadPool() 了

下面我们继续深入,看到这里可不算完事呢


okhttp Dispatcher 线程调度器设计

Dispatcher 内容不多,看看代码,大家就会霍然开朗的感觉,也不是很难嘛,至少看还是能看懂的

public final class Dispatcher {

  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  // 3个存储任务的容器,并非阻塞队列
  private final Deque readyAsyncCalls = new ArrayDeque<>();
  private final Deque runningAsyncCalls = new ArrayDeque<>();
  private final Deque runningSyncCalls = new ArrayDeque<>();

  // 线程池对象和初始化线程对象
  private @Nullable ExecutorService 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;
  }

  // 添加多线程任务
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

  // 
  private  void finished(Deque 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();
    }
  }

基本代码都在上面了,我们从 线程池构建 -> 3个任务队列 -> 异步任务自行过程 ->

1. 线程池构建
  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;
  }

这段就是,这个线程池的配置看着是不是和 CachedThreadPool 一样,为什么这么写呢,重点就在于 okhttp 为了使用自己的 Thread 线程类型,所以需要配自己的 threadFactory,那么就得换个写法了

2. 3个任务队列
private final Deque readyAsyncCalls = new ArrayDeque<>();
private final Deque runningAsyncCalls = new ArrayDeque<>();
private final Deque runningSyncCalls = new ArrayDeque<>();
  • runningAsyncCalls - 进行中的异步任务对类
  • runningSyncCalls - 进行中的同步任务对类
  • readyAsyncCalls - 预备的异步任务对类

大家想问为什么这里把任务存储到队列里,直接添加近线程池里执行不就完了吗,okhttp 这里搞个对类出来就是为了用户可以取消任务,FutrueTask 大家知道里面可以用 cancle 取消任务,这里也是同样的思路,所以异步、同步队列的存在大家就可以理解了,但是 readyAsyncCalls 呢,为啥还要有个预备任务队列,这里就得看 okhttp.Dispatcher.maxRequests 这个参数了,虽然线程池是不限线程数量的,但是考虑到移动平台的性能特性,在 Thread 到达 CPU 核心的一定数量时,再开更多的线程反而会带来性能的下降,这是个性能的阀值,maxRequests = 64 就是这个意思,再同时进行超过 64 个任务后,你再添加任务的话就得放到 readyAsyncCalls 里面了,然后在合适的时机添加到线程池里执行任务

3. 多线程任务执行过程
client.dispatcher().enqueue(new AsyncCall(responseCallback));

okhttp 添加任务实际就是这段代码,可以看见 okhttp 把任务包装成 AsyncCall 这个类

final class AsyncCall extends 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();
}

AsyncCall 我们没看到 run、callback 这写关键的地方,那么关键点在哪里呢,就在于 AsyncCall 继承自 NamedRunnable,而 NamedRunnable 则实现了 Runnable 接口,其 run 方法里面核心就是执行了 AsyncCall 的 execute 方法,好了找到地方了,我们看看 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);
      }
    }
  }

获取 Response 我们就看成执行异步任务的过程,完事后就是 onResponse、onFailure 这2个回调了,成功还是失败,熟悉不熟悉,AsyncTask 不就是这个思路吗。最后则是 finished 方法,finished 方法干啥了我们看看

  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

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

  private  void finished(Deque 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();
    }
  }

上面使用的一直是 AsyncCall ,所以实际上调的是 finished(runningAsyncCalls, call, true),因为 promoteCalls = true,所以会执行 promoteCalls() 这个方法

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

promoteCalls 干啥了呢,遍历一遍 readyAsyncCalls 预备任务队列,把每个预备任务都添加到线程池里去执行,一直到没有预备任务了或是到最大链接数了,这样就形成了任务循环,这样大家就能明白 okhttp 设计3个任务队列的初衷了

4. 取消任务

Dispatcher 里面只有 cancelAll 关闭所有请求

  public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.get().cancel();
    }

    for (AsyncCall call : runningAsyncCalls) {
      call.get().cancel();
    }

    for (RealCall call : runningSyncCalls) {
      call.cancel();
    }
  }

我们平时都是给任务添加一个 tag ,通过这个 tag 来吵到 call 来关闭任务

    OkHttpUtils.post().url(Const.DATAURL).params(requestMap).tag(getActivity()).build().execute(.....)

    public void cancelTag(Object tag) {
        if (tag == null) return;
        for (Call call : getOkHttpClient().dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
        for (Call call : getOkHttpClient().dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
    }

然后经过代码,最后找到远程链接的 sokcet 才能取消请求

  public void cancel() {
    canceled = true;
    StreamAllocation streamAllocation = this.streamAllocation;
    if (streamAllocation != null) streamAllocation.cancel();
  }

  public void cancel() {
    HttpCodec codecToCancel;
    RealConnection connectionToCancel;
    synchronized (connectionPool) {
      canceled = true;
      codecToCancel = codec;
      connectionToCancel = connection;
    }
    if (codecToCancel != null) {
      codecToCancel.cancel();
    } else if (connectionToCancel != null) {
      connectionToCancel.cancel();
    }
  }

  public static void closeQuietly(Socket socket) {
    if (socket != null) {
      try {
        socket.close();
      } catch (AssertionError e) {
        if (!isAndroidGetsocknameError(e)) throw e;
      } catch (RuntimeException rethrown) {
        throw rethrown;
      } catch (Exception ignored) {
      }
    }
  }

好了基本就这样,我们看的只是线程池在 okhttp 里面的使用,最后我们在取消任务时可以看到 okhttp 的确是非常复杂的大型库,类很多,角色很多,这里就不是本篇关心的了,后面会详细分析 okhttp 这个库的

你可能感兴趣的:(android 线程池应用(2) - okhttp)