Android okhttp源码调用流程分析

前言

okhttp 是一套专门为 JVM、Android 设计的 HTTP 客户端,支持同步阻塞调用和带有回调的异步调用,可以非常简单的在 Android 项目中实现网络请求。那么 okhttp 到底是如何做到异步请求?如何实现 HTTP 协议的?本文将通过简单案例对 okhttp 的主要源码进行解读。

代码实现

val client = OkHttpClient.Builder().build()
val request = Request.Builder()
    .url("https://www.baidu.com")
    .build()
val call = client.newCall(request)
// 同步阻塞调用
val response = call.execute()
println(response.code)
println(response.body?.string())
// 带回调的异步调用
call.enqueue(object : okhttp3.Callback{
    override fun onFailure(call: okhttp3.Call, e: IOException) {
        e.printStackTrace()
    }
    override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
        println(response.code)
        println(response.body?.string())
    }
})

这是一段最基本的 okhttp 使用,分别实现了同步、异步两种方式去请求 www.baidu.com 并且打印输出响应状态码、响应结果。

异步调用

仅仅是调用了 Call 的 enqueue 方法传入回调就实现了在后台进行请求,那么是如何实现线程切换的?带着这个问题去看源码,从 enqueue 方法入手:
在这里插入图片描述
点进去是 Call 接口,那么回到实现代码点进去 newCall 的源码,看一下 Call 对象到底是哪个实现类:
在这里插入图片描述
从这里看出使用的 Call 对象是一个 RealCall,接着点进去看一下 RealCall 的 enqueue 方法是如何实现的:
在这里插入图片描述
ReallCall 又调用到了 dispatcher 的 enqueue 并且新建了一个 AsyncCall 对象传入,继续查看 dispatcher 的 enqueue 方法:
在这里插入图片描述
在 Dispatcher.enqueue 方法最后调用了 promoteAndExecute 方法:
在这里插入图片描述
最终是调用了 AsyncCall 的 executeOn 方法,并且传入了一个 executorService,先来看一下这个 executorService 是什么:
在这里插入图片描述
executorService 就是一个线程池,回过头接着看 AsyncCall 的 executeOn 方法:
在这里插入图片描述
继续查看 AsyncCall 的 run 方法实现:
在这里插入图片描述
看到这里问题就算明白了,当使用 okhttp 请求时,调用 enqueue 传入回调后,okhttp 最终会生成一个 AsyncCall 对象,并且通过自身的线程池去执行他的 run 方法,实现后台线程发起请求的效果。

同步调用

由于上面跟踪异步调用的源码,得知 newCall 返回的是一个 RealCall 对象,可以直接去看 RealCall 的 execute 方法:
在这里插入图片描述
这部分源码非常简单,直接将 getResponseWithInterceptorChain 得到的 Response 返回,dispatcher 两行的调用分别是将当前请求的 call 对象放入、移除 runningSyncCalls 容器。
同步调用的代码逻辑比较简单就不多赘述了,接下来就该重头戏了。

getResponseWithInterceptorChain

通过面对同步、异步调用源码分析得出,getResponseWithInterceptorChain 方法是发起请求获取响应结果的核心,当然这个方法也是 okhttp 方法的核心,是一套完整的 http 协议的封装。直接从它的源码看起:
在这里插入图片描述在这里插入图片描述
这里需要注意一下容器中添加的拦截器,这里是重点)可以看出,这个方法的核心就是 RealInterceptorChain.proceed,通过这个调用获得了响应结果。那么就先从 RealInterceptorChain 入手,再看一下他初始化:
在这里插入图片描述
接着去看 RealInterceptorChain.proceed 方法实现:
在这里插入图片描述
这个方法主要逻辑就是取出拦截器然后调用,注意调用 indercept 方法时传入的 next ,这个 next 就是上一步中 copy 方法复制的 RealInterceptorChain 对象区别在于 index + 1 了。根据 getResponseWithInterceptorChain 方法源码得知拦截器的顺序是优先将我们写的自定义拦截器添加进容器,示例代码中并没有添加,所以默认第一个拦截器是 RetryAndFollowUpInterceptor,先看一下他的 intercept 方法:
在这里插入图片描述
最终又调回了 RealInterceptorChain 中:
在这里插入图片描述
到这里基本可以推断出 okhttp 的 interceptor 的调用链,可以把每个 interceptor 中的逻辑分成三部分,以 realChain.proceed(request) 方法为分界线,之前的代码为一部分暂且称作前置逻辑,之后的代码暂且称为后置逻辑,realChain.proceed(request) 这一行代码作为一部分,推进 interceptor 的调用,捋一下 interceptor 链条的调用逻辑就是这样的:
在这里插入图片描述
从第一个 interceptor 的前置逻辑开始执行,执行到 proceed 时回调到 RealInterceptorChain 取出下一个 interceptor 继续执行 反复这一流程,直到最后一个 interceptor 执行结束 结果返回给 上一个 interceptor 执行后置逻辑 反复执行这一流程,直到链条的第一个 interceptor 的 后置逻辑执行完成,整个流程完成。
所以,跟踪这一部分代码,只需要按顺序阅读每一个 interceptor 的逻辑即可,上面已经跟踪到了 RetryAndFollowUpInterceptor 的前置逻辑,它执行完后 proceed 取出了下一个 interceptor 也就是 BridgeInterceptor,看一下它的前置逻辑:
在这里插入图片描述
BridgeInterceptor 的逻辑很简单,主要完成了http请求的header部分,存在了 request 的map 里面,接着看下一个 CacheInterceptor 的前置逻辑,CacheInterceptor 看名字也能看出来是处理缓存的,示例代码中并没有配置 cache() 这里并不会发挥作用,这里的缓存实现也是非常强大,逻辑也很多,后面有机会再另写一篇博客专门讲解,在这里就不赘述了,跳过这个拦截器继续看下一个 ConnectInterceptor :
在这里插入图片描述
代码很少,并且没有后置逻辑,看起来只有 initExchange 方法需要跟踪:
在这里插入图片描述
先看一下 exchangeFinder.find 干了什么事情:
在这里插入图片描述
最终是返回了一个 ExchangeCodec 对象,这个对象主要负责 HTTP 请求的报文读写,再回到拦截器代码中:
在这里插入图片描述
接着再跳到下一个拦截器中,CallServerInterceptor,这也是最后一个拦截器,它主要负责对服务端进行网络调用也就是真正的读写报文调用是在这里调用:
在这里插入图片描述在这里插入图片描述
这里面的代码也是比较复杂,如果深挖的话需要大量的精力,本篇博客主要为了搞明白 okhttp 的工作流程,就不一一深挖了。到这里为止所有的 Interceptor 的前置逻辑看完了,该逆序往回一点点查看后置逻辑了,CallServerInterceptor 的上一个拦截器 ConnectInterceptor 也是没有后置逻辑的,直接跳过,再往回看 CacheInterceptor :
在这里插入图片描述
主要处理了缓存逻辑这里也就不多深挖了,继续往回跳到 BridgeInterceptor :
在这里插入图片描述
最后回到第一个拦截器 RetryAndFollowUpInterceptor,这里还有一大串的后置逻辑,来一点点看:
在这里插入图片描述
在这里插入图片描述
到这里 okhttp 的核心方法 getResponseWithInterceptorChain 大体流程就跟踪完了。这部分的核心就在于各个 interceptor 基于责任链模式的调用,分别完成了各自的逻辑,实现了 HTTP 协议。

总结一下

okhttp 的源码比较庞大,阅读完通过几个问题来总结一下

okhttp 使用时,enqueue 的回调可以直接更新界面UI吗?

回忆一下博客分析异步调用流程:call.enqueue 所谓的 call 是一个 RealCall,而 RealCall 的 enqueue 方法最终调用了 AsyncCall.executeOn 并且传入了线程池,AsyncCall 是 Runnable 的子类,他的 executeOn 方法再线程池中执行了自身, 且并没有做切线程的操作,所以回调发生在子线程,需要更新UI最好是切换到主线程去更新。
不要忘了之前 Retrofit 博客中,Retrofit 实现了线程切换。

调用 enqueue 方法后请求会立刻执行吗?

不一定,调用 enqueue 方法后请求最终会被存储到 Dispatcher 的 readyAsyncCalls 中,这里存放的是待执行的请求,需要判断是否超过最大请求限制,符合条件后才会被取出执行并且放入 runningAsyncCalls 中。

自定义的 Interceptor 执行顺序?

在 getResponseWithInterceptorChain 方法中 interceptors 容器初始化后会优先将自定义的 Interceptor 添加进去,所以自定义的 Interceptor 会优先执行。

最后

本篇博客对于 okhttp 的很多底层源码并没有深挖,只是跟踪了一次完整请求流程的大体逻辑,足以感受到 okhttp 的强大,其源码还有很多值得学习的地方。
本篇博客写的也略有混乱,本人写作功底略差,整篇读起来更像是一篇个人阅读笔记,希望大家多多见谅。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,你需要运行一个ChitGPT的服务器,然后通过Android应用程序与该服务器进行通信。以下是一个简单的Android应用程序示例,可以与ChitGPT进行通信: 1. 首先,在Android Studio中创建一个新的项目,并添加以下依赖项: ``` implementation 'com.squareup.okhttp3:okhttp:4.9.0' implementation 'com.google.code.gson:gson:2.8.6' ``` 2. 创建一个新的Java类,用于与ChitGPT服务器通信。以下是一个简单的实现: ```java import com.google.gson.Gson; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class ChitGPTClient { private final OkHttpClient client; private final Gson gson; public ChitGPTClient() { client = new OkHttpClient(); gson = new Gson(); } public String generateText(String prompt) throws IOException { Map<String, String> requestData = new HashMap<>(); requestData.put("text", prompt); String requestBody = gson.toJson(requestData); Request request = new Request.Builder() .url("http://localhost:5000/generate") .post(RequestBody.create(requestBody, okhttp3.MediaType.parse("application/json"))) .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } return response.body().string(); } } ``` 3. 在Android应用程序的主要活动中,创建一个新的线程,用于与ChitGPT服务器通信。以下是一个简单的示例: ```java import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.io.IOException; public class MainActivity extends AppCompatActivity { private EditText promptEditText; private Button generateButton; private TextView generatedTextView; private ChitGPTClient chitGPTClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); promptEditText = findViewById(R.id.promptEditText); generateButton = findViewById(R.id.generateButton); generatedTextView = findViewById(R.id.generatedTextView); chitGPTClient = new ChitGPTClient(); generateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { try { String prompt = promptEditText.getText().toString(); String generatedText = chitGPTClient.generateText(prompt); runOnUiThread(new Runnable() { @Override public void run() { generatedTextView.setText(generatedText); } }); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }); } } ``` 4. 最后,启动ChitGPT服务器,并在Android设备上运行应用程序。您应该能够输入一个提示,并从ChitGPT服务器接收生成的文本。 希望这可以帮助你。如果你需要更多的帮助,请告诉我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值