OkHttp 深度解析(一) : 从一次完整请求看 OkHttp 整体架构

这是 OkHttp 深度解析系列文章

OkHttp深度解析(一) : 从一次完整请求看 OkHttp整体架构

OkHttp深度解析(二) : OkHttpClient 你没见过的那些属性

OkHttp深度解析(三) : 拦截器?连接复用&合并?Http2?

前言

OkHttp 是一个非常优秀的网络请求框架,几乎每个 Android 开发人员应该都使用过它。

本文深入介绍了 Okhttp 的整体框架结构,OkhttpClient重点属性深度解释,并且深度分析一次完整的的 Http 请求在Okhttp中是如何实现的 ,我个人习惯系统性了解一项技术而不是单纯的罗列源码通篇讲解,因为只有建立出系统,才能理解的更透彻,这样记忆会非常深刻。因此我侧重使用画图的方式来分析 Okhttp(只有关键源码),图是重点,文字是辅助(画图好纍😅)。

这篇文章应该是你从未见过的OkHttp 全流程网络请求的深入解读,它同时也可能解惑你在其它博客中找不到答案的疑问。

OkHttp 发展背景

  • 诞生背景

    在OkHttp出现之前,Android开发者主要使用Apache的HttpClient和Google自家的HttpURLConnection进行网络请求。但这两者多少都会存在一些问题。美国的移动支付公司Square在开发过程中,深感这些原生网络库难以满足其需求,便在2013年开源了OkHttp

  • 发展之路: 从包装到替代

    OkHttp并非一开始就完全另起炉灶,其发展经历了几个阶段:

    1. 初期包装 :最初,OkHttp是对原生HttpClientHttpURLConnection的封装,旨在提供更友好、统一的API。
    2. 剥离与独立 :随着Apache宣布弃用HttpClient,OkHttp也移除了对其的支持。后来,Square团队认为HttpURLConnection也不够好用,索性去掉了对它的依赖,转而直接从 Socket 层完全重写整个 Http 网络请求**,实现了真正的独立。
    3. 反向影响 :OkHttp因其出色的设计和性能,受到了广大开发者的欢迎。Google也注意到了这一点,并在Android 4.4 (KitKat) 系统中,将HttpURLConnection的底层实现替换成了OkHttp**。这意味着,即使你在代码中使用的是标准的HttpURLConnection,其底层实际使用的 Okhttp的代码。
    4. 向Kotlin迈进:从OkHttp 4版本开始,Square开始使用Kotlin语言进行重写

OkHttp 整体框架

首先我们先看一下使用 Okhttp 如何完成一个网络请求:

kotlin 复制代码
//okhttp 网络请求示例(异步)
val client = OkHttpClient()
val request = Request.Builder()
    .url("")
    .build()
client.newCall(request)
    .enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            //处理错误
        }
        override fun onResponse(call: Call, response: Response) {
            //处理 response
        }
    })

代码非常简单,这得益于 Okhttp 优秀的框架设计,以下是以一次网络请求流程的视角来系统的了解 Okhttp 的整体结构:

文字说明:(请在看完文字说明后回来再看一次上面的图,理解会更深刻,而且不容易忘记)

  1. 通过 OkHttpClient.Builder 来构造出一个 OkHttpClient 实例(这个实例在绝大部分场景都应该是单例的),后面会详细介绍 OkHttpClient 中各个属性配置的作用

  2. 通过 Request.Builder 来构造出一个 Request 实例,这表示一个 originalRequest(原始请求),开发者需要配置一个请求的基础参数

  3. 通过 newCall 方法(携带 request 参数)创建一个 Call 对象,实际为 RealCall 实例,它包含了以下参数:

    kotlin 复制代码
    class RealCall(
      val client: OkHttpClient,//开发者创建出来的 OkHttp Client 实例
      /** 一个未经重定向或者没有添加认证头的原始请求,
    	  也就是未经OkHttp内部处理的请求,并不是实际发给服务器的请求 
      */
      val originalRequest: Request,
      val forWebSocket: Boolean //发起的是否是一个WebSocket请求,因为WebSocket也是基于Http的协议
    ) : Call {
    	......
    }

    也就是说我们创建的这个原始请求对象,被转化为 了一个 RealCall,后面的每个 RealCall 就代表了一个请求

  4. 开始使用 execute(同步)/enqueue(异步)的方式发起一个 Http 请求

  5. enqueue 方式: enqueue 需要传入一个 callback 对象来接收返回结果,整体逻辑如下:

    kotlin 复制代码
    override fun enqueue(responseCallback: Callback) {
    	//检查请求的执行状态
      check(executed.compareAndSet(false, true)) { "Already Executed" }
      //事件记录,标记开始执行请求
      callStart()
      //初始化一个异步的 Call,并入队
      client.dispatcher.enqueue(AsyncCall(responseCallback))
    }

    💡 PS: 关于check 方法,这是 Kotlin 标准库中的方法,它内部使用的是 Kotlin Contract 这个高级特性

    此处简单介绍下这个方法,因为在 Okhttp 中大量使用到了它

    kotlin 复制代码
    @kotlin.internal.InlineOnly
    
    public inline fun check(value: Boolean, lazyMessage: () -> Any): Unit {
    
    contract {
    
    returns() implies value
    
    }
    
    if (!value) {
    
    val message = lazyMessage()
    
    throw IllegalStateException(message.toString())
    
    }
    
    }

    contract 表示契约,implies 表示 暗示,这个方法的作用是 开发者 告诉 编译器 如果 check 方法能够正常返回(也就是不会抛出异常),则暗示检查成功,否则抛出异常信息。

    enqueue 方法中 check 的作用就是请求执行状态的检查

    • **executed**是一个 AtomicBoolean,用于标记这个 Call是否已被执行过。compareAndSet(false, true)尝试将其值从 false设置为 true

    • check函数 :如果传入的表达式结果为 false(即设置失败,说明 executed已经是 true,意味着这个 Call已经被执行过),check函数就会抛出一个 IllegalStateException,异常信息为 "Already Executed"。

    • 这保证了 一个 Call实例只能被执行一次,这是 OkHttp 防止重复请求的重要设计。

    🔑 关于 Contract 的更深入理解,请移步此处

    keep on :

    kotlin 复制代码
    internal fun enqueue(call: AsyncCall) {
        synchronized(this) {
    	    //将这个请求添加到准备执行的队列中
          readyAsyncCalls.add(call)
    			/**下面这两行代码的意思是:找到与当前的这个请求主机名相同的Call,
    				然后使它们共享「同一个主机请求个数」这个变量。
    				为何共享?因为 Okhttp 要求同一个主机的请求个数不能大于5个(默认)
    			*/
          if (!call.call.forWebSocket) {
            val existingCall = findExistingCallWithHost(call.host)
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
          }
        }
        //开始推举并执行请求
        promoteAndExecute()
      }
    kotlin 复制代码
    //promote 表示推动,意思就是对readyAsyncCalls中 Call 进行进一步的挑选,然后执行
    private fun promoteAndExecute(): Boolean {
        this.assertThreadDoesntHoldLock()
    		//遍历 readyAsyncCalls,挑选出不会导致超负载的 Call 来添加至 executableCalls
    		//同时也会添加到 runningAsyncCalls中(runningAsyncCalls中也包含已取消单但未执行完的Call)
    		//runningAsyncCalls 主要用于记录正在运行的 Call
        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
        }
    		//开始遍历 executableCalls 然后执行每个 AsyncCall
        for (i in 0 until executableCalls.size) {
          val asyncCall = executableCalls[i]
          asyncCall.executeOn(executorService)
        }
        return isRunning
      }

    由于 AsyncCall 继承了 Runnable ,因此它表示一个需要放进线程池中执行的具体任务,这是异步请求的核心(此处仅贴出核心代码)

    kotlin 复制代码
    internal inner class AsyncCall(
        private val responseCallback: Callback
      ) : Runnable {
        @Volatile var callsPerHost = AtomicInteger(0)
          private set
    
        fun reuseCallsPerHostFrom(other: AsyncCall) {
          this.callsPerHost = other.callsPerHost
        }
        ...
        
        fun executeOn(executorService: ExecutorService) {
          ...
          executorService.execute(this)
          ...
        }
    
        override fun run() {
          ...
          //这是异步任务的核心,通过拦截器链的链式调用方式执行一个请求并返回结果
          val response = getResponseWithInterceptorChain()
          ...
          //把返回的 response 传给 callback
          responseCallback.onResponse(this@RealCall, response)
          ...
        }
      }
相关推荐
安卓理事人3 小时前
安卓图表MpAndroidChart使用
android
奋斗的小鹰4 小时前
在已有Android工程中添加Flutter模块
android·flutter
介一安全5 小时前
【Frida Android】实战篇13:企业常用非对称加密场景 Hook 教程
android·网络安全·逆向·安全性测试·frida
lin62534225 小时前
Android右滑解锁UI,带背景流动渐变动画效果
android·ui
鹏多多8 小时前
Flutter输入框TextField的属性与实战用法全面解析+示例
android·前端·flutter
2501_916008898 小时前
iOS 开发者工具全景图,构建从编码、调试到性能诊断的多层级工程化工具体系
android·ios·小程序·https·uni-app·iphone·webview
Winter_Sun灬9 小时前
CentOS 7 编译安卓 arm64-v8a 版 OpenSSL 动态库(.so)
android·linux·centos
柯南二号9 小时前
【大前端】【Android】用 Python 脚本模拟点击 Android APP —— 全面技术指南
android·前端·python
龚礼鹏9 小时前
图像显示框架六——SurfaceFlinger的初始化以及任务调度(基于Android 15源码分析)
android
壮哥_icon9 小时前
Android 使用 PackageInstaller 实现静默安装,并通过 BroadcastReceiver 自动重启应用
android·gitee·android-studio·android系统