一篇搞定okHttp二

本文深入剖析了OKHttp的核心组件Dispatcher的运行逻辑,包括同步和异步请求的处理方式。同时,介绍了OKHttp的拦截器链,包括重定向、桥接、适配、缓存等拦截器的作用和工作原理。最后,探讨了CacheInterceptor如何实现缓存策略,优化请求性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OKHttp系列六、OKHttp的核心Dispatcher源码与运行逻辑解析

1、同步请求的用法:

Request mRequest = new Request.Builder().url("www.baidu.com").get().build(); 
Call call = client.newCall(mRequest); 
Response  response = call.execute();

上面Call是一个接口,execute()是一个抽象的方法,由RealCall.java具体实现该方法,它默认运行在主线程里面,RealCall中execute()源码如下:

public Response execute() throws IOException {
    synchronized (this) {
        if (executed) {
            throw new IllegalStateException("Already Executed");
        }
        executed = true;
    }
    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);
    }
}

我们注意到上面添加注释的一行代码:client.dispatcher().executed(this)——他是Dispatcher里面的一个方法,client.dispatcher()实际上就是一个Dispatcher对象,里面非常简单,就是将一个RealCall添加到正在运行的队列中去:

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

runningSyncCalls——就是我们前面提到的 正在执行、还没有执行完成以及取消的同步请求队列
每当我们发出一个同步请求时,Dispatcher就直接将他加入到执行队列里面去并开始执行,他并不需要像异步请求那样考虑各种场景和影响因素。

2.异步请求的用法:

Request mRequest = new Request.Builder().url("www.baidu.com").get().build(); 
Call call = client.newCall(mRequest);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
    }
});

上面加粗的Response部分就是我们网络请求到的结果。同样的,上面Call是一个接口,enqueue ()是一个抽象的方法,由RealCall.java具体实现该方法,它默认运行在子线程里面,但是经过Dispatcher内部Handler的调度又将运行线程切换回了主线程。RealCall中enqueue ()源码如下:

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

我们注意到上面加粗的一行代码:

——client.dispatcher().enqueue(new AsyncCall(responseCallback)),client.dispatcher()实际上就是一个Dispatcher对象;

——enqueue(new AsyncCall(responseCallback))方法是Dispatcher里面的一个方法, AsyncCall是RealCall.java里面的一个内部类,他是NamedRunnable的子类,NamedRunnable又实现了Runnable,所以AsyncCall的本质上就是一个Runnable接口,具体的请求业务就在Runnable接口的run方法里面实现。

——responseCallback是回调接口Callback的对象,用于对请求结果的处理。
在Dispatcher中enqueue()的源码如下:

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

If判断的意思是:当异步请求的队列长度小于允许的最大并发请求数量64并且主机允许的最大并发请求数量小于5的时候,就将该请求加入到正在执行、还没有执行完成以及取消的同步请求队列里面去,并立即开始执行请求操作。否则,只是将该请求加入到准备就绪将要执行的异步请求队列里面去等待执行。

executorService()就是获取到当前线程池线程池,通过execute方法执行请求任务。源码如下:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
        executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
}

上面ThreadPoolExecutor构造方法里面需要注意一点:Integer.MAX_VALUE:这里将线程池的最大数量设置为无限大,实质上是无法实现的,因为他同时受maxRequests=64和maxRequestsPerHost=5两个变量限制的,上面的if判断语句中,比较大小的时候,用的是“<”符号,因此线程池这里实际上最大能够实现的线程数量是63×4=252个。

——有个问题:call执行完成后,需要在runningAsyncCalls队列中移除这个线程,那么队列readyAsyncCalls中的线程在哪里执行呢?什么时候才会执行呢?
首先,execute方法的具体执行是在RealCall.java的内部类AsnycCall.java里面实现的。

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

这里,try里面的代码很好理解,就是如果发现拦截器里面的isCanceled方法为true,就表示请求任务被取消,那么调请求失败的方法,异常标记flag=“Canceled”;否则调用onResponse方法返回请求结果。Catch方法都是对失败的处理。最关键的一个:finally语句块里面的代码:

——client.dispatcher().finished(this)——client.dispatcher()实质上就是一个Dispatcher对象。finished方法裏面的逻辑才是我们这里最关心的问题:

void finished(AsyncCall call) {
    //关键调用代码
    finished(runningAsyncCalls, call, true);
}

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

上面代码中,第一个if判断好理解,抛出无法移除已经执行完毕的请求移除;最后一个if判断也好理解,开始执行新的请求逻辑;第二if逻辑稍微复杂点,主要作用是调整我们的异步队列数据的:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_深巷的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值