OkHttp源码分析 - 异步请求过程

61 阅读5分钟

1. 开启异步请求

通过OkHttpClient对象和Request,创建RealCall对象,调用RealCallenqueue方法开启异步请求。代码如下:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("https://example.com")
    .build();
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // Handle failure
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // Handle response
    }
});

2. 创建AsyncCall对象,将请求交给调度器

enqueue方法中,创建AsyncCall对象,并将该对象交给调度器。代码如下:

@Override
public void enqueue(Callback responseCallback) {
    // 检查是否重复调用enqueue方法
    if (!executed.compareAndSet(false, true)) {
        throw new IllegalStateException("Already Executed");
    }

    callStart();
    // 调用dispatcher的enqueue方法,参数为AsyncCall对象
    client.dispatcher.enqueue(new AsyncCall(responseCallback));
}

3. 调度器将异步请求加入队列

在分析Dispatcherenqueue方法前,需要先了解下Dispatcher。它主要用于控制并发的请求,无论是同步请求还是异步请求,都会通过Dispatcher来处理。Dispatcher主要维护了以下变量:

class Dispatcher {

    // 最大任务请求数
    @Synchronized var maxRequests: Int = 64

    // 每个主机的最大任务请求数
    @Synchronized var maxRequestsPerHost: Int = 5

    // 准备运行的异步请求队列
    private val readyAsyncCalls = ArrayDeque<AsyncCall>()
    // 正在运行的异步请求队列
    private val runningAsyncCalls = ArrayDeque<AsyncCall>()
    // 正在运行的同步请求队列
    private val runningSyncCalls = ArrayDeque<RealCall>()
    
    // 执行异步请求的线程池
    private var executorServiceOrNull: ExecutorService? = null

    // 获取或创建默认线程池
    @get:Synchronized
    @get:JvmName("executorService") 
    val executorService: ExecutorService
        get() {
            if (executorServiceOrNull == null) {
                executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60L, TimeUnit.SECONDS,
                    SynchronousQueue(), threadFactory("OkHttp Dispatcher", false))
            }
            return executorServiceOrNull!!
        }

    // Dispatcher的构造方法,可以设定线程池
    constructor(executorService: ExecutorService) {
        this.executorServiceOrNull = executorService
    }
}

它的enqueue方法将AsyncCall添加到准备运行的异步请求队列中readyAsyncCalls,然后调用promoteAndExecute方法,如下:

fun enqueue(call: AsyncCall) {
    synchronized(this) {
        readyAsyncCalls.add(call)
    }
    promoteAndExecute()
}

promoteAndExecute方法中,会遍历readyAsyncCalls,如果符合条件则将AsyncCallreadyAsyncCalls中移除,并添加到runningAsyncCalls中,然后调用executeOn方法,开启异步任务。代码如下:

private fun promoteAndExecute() {
    //记录可执行请求的AsyncCall
    val executableCalls = ArrayList<AsyncCall>()
    synchronized(this) {
        //获取准备运行的异步请求队列的迭代器,然后开始遍历readyAsyncCalls
        val iterator = readyAsyncCalls.iterator()
        while (iterator.hasNext()) {
            val asyncCall = iterator.next()
            
            //如果正在运行的异步请求数大于或等于最大任务请求数,则直接结束循环
            if (runningAsyncCalls.size >= maxRequests) break
            
            //如果asyncCall中的主机数大于或等于同一主机的最大任务数,则跳过这个请求
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue
            
            iterator.remove()
        
            //记录可执行请求的AsyncCall
            executableCalls.add(asyncCall)
        
            //正在运行的异步请求队列
            runningAsyncCalls.add(asyncCall)
        }
    }
    for (asyncCall in executableCalls) {
        //取出每个可执行的AsyncCall,调用其executeOn方法
        asyncCall.executeOn(executorService)
    }
}

4. AsyncCall开启异步任务

AsyncCallRealCall的内部类,实现了Runnable接口,在其run方法中通过拦截器链得到网络响应,源码如下:

internal inner class AsyncCall(private val responseCallback: Callback) : Runnable {

    fun executeOn(executorService: ExecutorService) {
        client.dispatcher.executed(this@RealCall)
        executorService.execute(this)
    }

    override fun run() {
        try {
            val response = getResponseWithInterceptorChain()
            responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
            responseCallback.onFailure(this@RealCall, e)
        } finally {
            client.dispatcher.finished(this)
        }
    }
}

可以看到,通过拦截器链得到网络响应后,无论响应成功还是失败,最终都会调用Dispatcherfinished方法,如下:

fun finished(call: AsyncCall) {
    synchronized(this) {
        runningAsyncCalls.remove(call)
    }
    promoteAndExecute()
}

当一个请求完成后调用该finished方法,移除当前AsyncCall并继续调用promoteAndExecute,处理下一个异步请求。

5. 拦截器链

拦截器链是OkHttp的核心逻辑,在AsyncCall的异步任务中,通过拦截器链来得到网络响应,即调用getResponseWithInterceptorChain方法。源码如下:

@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
    // 创建拦截器集合
    val interceptors = mutableListOf<Interceptor>()
    // 添加用户设置的应用拦截器
    interceptors += client.interceptors
    // 负责重试和重定向
    interceptors += RetryAndFollowUpInterceptor(client)
    // 用于桥接应用层和网络层的请求数据
    interceptors += BridgeInterceptor(client.cookieJar)
    // 用户处理缓存
    interceptors += CacheInterceptor(client.cache)
    // 网络连接拦截器,用于获取一个连接
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
        // 添加用户设置的网络连接器
        interceptors += client.networkInterceptors
    }
    // 用于请求网络并获取响应
    interceptors += CallServerInterceptor(forWebSocket)

    // 创建职责链
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )

    var calledNoMoreExchanges = false
    try {
        // 启动职责链
        val response = chain.proceed(originalRequest)
        if (isCanceled()) {
            response.closeQuietly()
            throw IOException("Canceled")
        }
        return response
    } catch (e: IOException) {
        calledNoMoreExchanges = true
        throw noMoreExchanges(e) as Throwable
    } finally {
        if (!calledNoMoreExchanges) {
            noMoreExchanges(null)
        }
    }
}

getResponseWithInterceptorChain方法主要处理了两件事:

  1. 创建拦截器集合,并将所有拦截器添加到集合中。
  2. 创建职责链,并启动它。

各拦截器作用

  • client.interceptors:应用拦截器,通过OkHttpClient设置。
  • RetryAndFollowUpInterceptor:重试拦截器,负责网络请求中的重试和重定向。比如网络请求过程中出现异常,就会重试请求。
  • BridgeInterceptor:桥接拦截器,用于桥接应用层和网络层的请求数据。请求时将应用层的数据类型转换为网络层的数据类型,响应时则将网络层返回的数据类型转换为应用层的数据类型。
  • CacheInterceptor:缓存拦截器,负责读取和更新缓存。可以配置自定义的缓存拦截器。
  • ConnectInterceptor:网络连接拦截器,其内部会获取一个连接。
  • client.networkInterceptors:网络连接拦截器,通过OkHttpClient设置。
  • CallServerInterceptor:请求服务拦截器。拦截器链中处于末尾的拦截器,用于向服务器发送数据并获取响应。

职责链

职责链采用职责链模式,使得每个拦截器都有机会处理请求,这些拦截器形成了拦截器链。网络请求经过拦截器链的处理然后发送出去;同样,网络响应也经过拦截器的处理返回给应用层。职责链启动方式如下:

@Throws(IOException::class)
override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++

    if (exchange != null) {
        check(exchange.finder.sameHostAndPort(request.url)) {
            "network interceptor ${interceptors[index - 1]} must retain the same host and port"
        }
        check(calls == 1) {
            "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
        }
    }

    // 调用RealInterceptorChain的copy方法,其内部会创建一个RealInterceptorChain,
    // 通过参数index+1来循环处理inter

ceptors中的拦截器
    val next = copy(index = index + 1, request = request)
    
    // 获取当前要执行的拦截器
    val interceptor = interceptors[index]

    // 运行当前的拦截器,并设置了下一个拦截器。
    // 其内部逻辑是,当前拦截器处理完成后,会接着执行下一个拦截器的proceed方法
    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    if (exchange != null) {
        check(index + 1 >= interceptors.size || next.calls == 1) {
            "network interceptor $interceptor must call proceed() exactly once"
        }
    }

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
}

通过以上代码,可以看到每个拦截器在职责链中的位置,通过proceed方法依次调用下一个拦截器,直到最后一个拦截器处理请求并返回响应。