[Android] 主流网络请求框架 OkHttp 全方位详析

《主流网络请求框架 OkHttp 全方位详析》掘金地址

1、OkHttp介绍

OkHttp 是一个开源的 HTTP 客户端库,由 Square 公司开发并维护。它提供了简洁、高效的 API,用于处理网络请求和响应。OkHttp 基于 Java 编写,同时也提供了对 Kotlin 的良好支持。
OkHttp 提供了以下主要特性:

  • 简洁易用的 API:OkHttp 提供了简洁而强大的 API,使得进行 HTTP 请求变得非常容易。通过构建 Request 对象和使用 Call 对象来发起同步或异步的网络请求。
  • 支持 HTTP/2 和 SPDY:OkHttp 支持现代的 HTTP 协议,包括 HTTP/2 和 SPDY,从而提供更快速和更有效率的网络通信。
  • 连接池和缓存:OkHttp 内置了连接池和响应缓存,可以有效地管理和复用网络连接,并提供可配置的缓存机制,减少重复的网络请求。
  • 拦截器:OkHttp 提供了拦截器的机制,允许开发者在发送请求和接收响应的过程中进行自定义处理,例如添加公共参数、日志记录等。
  • 支持 GZIP 压缩:OkHttp 支持接受和解压 GZIP 压缩的响应数据,减小网络传输的数据量,提升网络性能。
  • 适配 Android 平台:OkHttp 在 Android 开发中得到广泛应用,它提供了专门针对 Android 平台的优化,包括性能、安全和稳定性方面的考虑。

2、OkHttp基本使用与请求流程

基本使用

val client = OkHttpClient.Builder()
        .callTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
        .connectTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
        .readTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
        .writeTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
        .retryOnConnectionFailure(true)
        .followRedirects(true)
        .followSslRedirects(true)
        .cache(null) // 设置缓存
        .authenticator(null) // 设置身份验证器
        .certificatePinner(null) // 设置证书锁定器
        .connectionPool(null) // 设置连接池
        .connectionSpecs(listOf()) // 设置连接规范
        .cookieJar(null) // 设置 Cookie 管理器
        .dispatcher(null) // 设置分发器
        .dns(null) // 设置 DNS 解析器
        .eventListenerFactory(null) // 设置事件监听器工厂
        .proxy(null) // 设置代理
        .protocols(listOf()) // 设置支持的协议
        .proxyAuthenticator(null) // 设置代理身份验证器
        .proxySelector(null) // 设置代理选择器
        .socketFactory(null) // 设置 Socket 工厂
        .sslSocketFactory(null) // 设置 SSL Socket 工厂
        .hostnameVerifier(null) // 设置主机名验证器
        .proxy(proxy) // 设置代理
        .build()

val request = Request.Builder()
        .url(url)
        .header("xxx", "xxx")
        .addHeader("xxx", "xxx")
        .post(RequestBody.create(null, "XXX")) // 使用 POST 方法并传入请求体,不写默认为 GET 方法
        .cacheControl(okhttp3.CacheControl.FORCE_NETWORK) // 设置缓存控制
        .tag("custom-tag") // 设置标记
        .build()

val call = client.newCall(request)

// 构造 Call 对象之后就可以同步或异步请求,并处理结果了
// 1、同步
client.newCall(call).execute().use { response ->
    if (response.isSuccessful){
        Log.v("同步请求响应:${response.body?.string()}")
    }else{
        Log.e("同步请求失败")
    }
}
// 2、异步
client.newCall(call).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        Log.e("异步请求失败: ${e.message}")
    }

    override fun onResponse(call: Call, response: Response) {
        Log.v("异步请求响应:${response.body?.string()}")
    }
})

请求流程

OkHttp请求流程.jpg

3、分发器Dispatcher

3.1、异步请求分发流程

首先我们要知道异步请求有两个队列,ready 队列和 running 队列,前者用来记录等待执行的请求,后者用来记录正在执行的请求:

// ready 队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()

// running 队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()

我们来看看 enqueue() 方法:

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

        // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
        // the same host.
        if (!call.call.forWebSocket) {
            val existingCall = findExistingCallWithHost(call.host)
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
        }
    }
    promoteAndExecute()
}

其中 synchronized() 代码段中,先将请求加入 ready 队列,随后判断当前请求是否为 WebSocket,如果不是就调用 findExistingCallWithHost() 方法,在 running 和 ready 队列中查找与当前请求 Host 相同的请求,如果找到了,就让相同 Host 请求中的 callsPerHost 变量共享同一个对象,这个对象是 AtomicInteger 类型,用于对请求做一些限制,在下文有解释,可以先往下看。

synchronized() 代码段之外,执行的 promoteAndExecute() 方法是分发器分发异步请求的关键,先来看看源码:

private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
        val i = readyAsyncCalls.iterator()
        while (i.hasNext()) {
            val asyncCall = i.next()

            if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
            if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

            i.remove()
            asyncCall.callsPerHost.incrementAndGet()
            executableCalls.add(asyncCall)
            runningAsyncCalls.add(asyncCall)
        }
        isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
        val asyncCall = executableCalls[i]
        asyncCall.executeOn(executorService)
    }

    return isRunning
}

synchronized() 代码段中主要完成了检查 ready 队列,并将符合条件可执行的任务添加到 running 队列和 executableCalls 队列中,这个 executableCalls 保存的才是这次要执行的请求,同步代码块之外,就遍历 executableCalls 执行请求。

主要过程可以用下图表示:
分发器异步请求分发流程.jpg

其中 ThreadPool 阶段较为重要,是请求效率提升的关键,我们先来回顾一下 Java 线程池的机制:

当一个任务通过 execute(Runnable) 方法添加到线程池时,有两种情况:

  • 一、线程数量小于 corePoolSize,则新建线程(核心)来处理被添加的任务。
  • 二、线程数量大于或等于 corePoolSize,则新任务被添加到等待队列,若添加失败:
    • 线程数量小于 maximumPoolSize,新建线程执行新任务。
    • 线程数量等于 maximumPoolSize,使用 RejectedExecutionHandler 拒绝策略。

了解了 Java 线程池机制后,我们来看下方 OkHttp 源码,下方的 ExecutorService 对象就是 OkHttp 中的线程池,其中为了提升请求效率,则不能让新任务被添加到等待队列,而是要新建线程执行新任务,因此将

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值