一、准备步骤
1.1 添加权限
<uses-permission android:name="android.permission.INTERNET" />
1.2 引入框架
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
1.3 配置网络地址
很显然,我们可以定义一个Config类里,配置网络接口信息。

OkHttp的特点
默认情况下OkHttp具备以下特性:
• 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接。
• 连接池减少请求延时。
• 透明的GZIP压缩减少响应数据的大小。
• 缓存响应内容,避免一些完全重复的请求。
• 网路出现问题后,OkHttp会保持不变,自动从问题中恢复。
OkHttp的基本使用
OkHttp 分为同步请求以及异步请求,接下来我们分别介绍一下。
同步:execute()
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//ViewBinding相关这里就不介绍
//Builder构建者模式 可参考https://www.jianshu.com/p/afe090b2e19c
//创建 okHttpClient 实例
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//创建 Request 实例
Request request = new Request.Builder()
.url("写入你的 URL")
.get()
.build();
//同步请求
new Thread( new Runnable(){
@Override
public void run() {
//发起同步请求的方式,返回一个 Response类的值
Response response = null;
try {
//🌟发起同步请求
response = okHttpClient.newCall(request).execute();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
String result = response.body().string();
//如果需要更新主线程的 UI 如果使用 rxjava 以及 retrofit 就不用这样处理了
runOnUiThread(new Runnable() {
@Override
public void run() {
binding.title.setText(result);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
异步:enqueue()
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//ViewBinding相关这里就不介绍
//Builder构建者模式 可参考https://www.jianshu.com/p/afe090b2e19c
//创建 okHttpClient 实例
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//创建 Request 实例
Request request = new Request.Builder()
.url("写入你的 URL")
.get()
.build();
//🌟异步请求发起
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
}
});
}
post请求
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
AppContext.getInstance().onInit();
//Builder构建者模式 可参考https://www.jianshu.com/p/afe090b2e19c
//创建 okHttpClient 实例
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//Form表单格式的参数传递
FormBody formBody = new FormBody
.Builder()
.add("a","1")//设置参数名称和参数值
.build();
// RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf/8"))
//还可以传入 RequestBody 待补充
//创建 Request 实例
Request request = new Request.Builder()
.url("写入你的 URL")
.post(formBody)
.build();
//post 请求表单数据
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
}
});
}
上传文件
自定义拦截器
我们在了解、使用okhttp的过程中,不可避免地都听到过拦截器。那么拦截器有什么用,在哪里使用,怎么样自定义,下面逐步分析。

OkHttpClient okHttpClient = new OkHttpClient.Builder()
//加入自定义拦截器
.addInterceptor(new LogIntercept))
.build();
这里先介绍一下自定义拦截器,具体拦截器责任链之类的,后面会再详细介绍原理。
Okhttp原理分析
双任务队列机制
前面我们了解过,Okhttp可以执行同步请求以及异步请求,这里的源码分析我们以异步为例。
//🌟异步请求发起
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
}
});
核心点:
- newCall
- enqueue
我们从源码去探索!! 首先进入 okHttpClient.newCall()方法中,

该方法接收一个 Request 对象,返回一个 Call 类型的对象,并调用 RealCall.newRealCall方法。
又是 Call 、 newCall 、 RealCall,是不是已经晕了~~~~~
其实,我们可以先明白一个前提,就是 okhttp 发起网络请求,在后续把网络请求放进准备队列、运行队列里的对象,就是Call 对象。可以理解成一个 Call 对象,包含了本次网络请求的属性。好,我们继续。
newCall,顾名思义就是去获取一个新的 Call 。Call 是一个接口,而 RealCall 是Call 的实现类,调用 RealCall 里的 newRealcall 方法,获取一个 Call 对象。



参考以上的源码,可见一个 RealCall 对象,保存了 okhttpclient 的实例、 request 实例。也就是上面说的包含了本次网络请求的属性。
我们接着看,okHttpClient.newCall(request).enqueue中发生了什么。 从上面可以知道,这实际上调用了RealCall 实例中的 enqueue 方法。

其中,关注client.dispatcher().enqueue(new AsyncCall(responseCallback))
注意这里又出现了一个带有 Call 字眼的类:AsyncCall 。 我们来认识一下这个 AsyncCall,从名字来看,意思是一个异步的 Call。
先从继承关系来看,AsyncCall是 RealCall 的一个内部类,其继承自 NamedRunnable 。
再看看NamedRunnable实际就是 Runnable 接口的一个实现类,
我们都知道,在新建一个线程时,需要重写一个 run 方法代表希望在子线程具体执行的逻辑,而AsyncCall是 Runnable 类,所以可以明确AsyncCall与线程执行有关,线程中执行的具体逻辑是AsyncCall中重写的 run 方法。
仔细看看里面重写的 run 方法,最关键的部分是其执行了 execute 方法,而其是一个抽象方法,具体的实现显然在AsyncCall中。
特别的,因为AsyncCall是RealCall的内部类,自然也拥有了RealCall的属性(包括request)
将网络请求的完整逻辑(包括重试、拦截器链执行、回调触发)封装为一个独立的可执行单元(Runnable),便于线程池调度。
所以我们可以知道,在AsyncCall中重写的 execute 方法,就是该网络请求的具体逻辑
来,我们看看重写的execute 方法在干些什么。
里面最重要的一行代码就是红色框起来的,getResponseWithInterceptorChain();通过该方法的返回值,获取到网络请求的 response 。
接下来跳回,关注client.dispatcher().enqueue(new AsyncCall(responseCallback)) client.dispatcher()会返回一个 Dispatcher类的实例对象,该类的具体作用我们可以理解成由他来调度那些个 Call应该怎么样安排(因为可能有许多个网络请求等待执行)?
然后调用Dispatcher中的enqueue 方法(注意区分 RealCall 中的 enqueue 方法)。
- ReadyAsyncCalls 等待队列
- runningAsyncCalls 正在执行任务队列
将该 AsyncCall 对象加入等待对象,然后执行promoteAndExecute()

首先从等待队列拿出来一个 Runnable(AsyncCall),然后去执行检查。
首先检查正在执行任务队列的大小,并发最大是 64,如果超过 64 循环直接 break;
然后再判断该任务的 host 对应的主机,同一个主机的最大请求数是 5.
满足条件之后,该任务从等待任务队列里删除,并加到正在执行任务队列以及可执行任务队列。
0
为什么要这样设计成64呢?
1. 客户端资源保护 (自我保护)
每个并发请求都会消耗客户端的资源,主要包括:
- 内存:每个请求都有其相关的缓冲区、头信息、响应体等。
- CPU:用于处理网络 I/O、序列化/反序列化数据。
- 文件描述符 (Socket):每个 TCP 连接都会占用一个文件描述符。操作系统对单个进程可打开的文件描述符数量有软限制。
如果不加限制地发起并发请求,一个疯狂的循环或错误逻辑可能会瞬间创建成千上万个请求,最终耗尽客户端的所有资源,导致应用崩溃或无响应。64 是一个安全且充裕的上限,足以满足绝大多数应用的并发需求,同时避免了资源耗尽的风险。
2. 对服务器的礼貌 (防止被当成攻击)
从服务器的角度看,来自同一个客户端 IP 的瞬间海量请求看起来非常像 DDoS 攻击 或恶意的爬虫程序。即使服务器能处理,这种行为也是不礼貌的。
OkHttp 将自己定位为一个“良好的网络公民”。默认限制并发数是一种礼貌的行为,表明客户端不会试图用请求淹没服务器。这有助于维护健康的客户端-服务器关系。
3. 与 TCP 连接复用的协同
OkHttp 的另一个重要特性是连接池和连接复用。限制并发请求数实际上也间接限制了需要同时维护的物理 TCP 连接数。
- HTTP/1.x:一个连接只能处理一个请求 at a time。64 个并发请求可能需要打开 ~64 个连接(尽管连接池会复用它们)。
- HTTP/2:一个连接可以多路复用,同时处理大量请求。64 个并发请求可能只需要 1 个连接。
因此,这个限制在不同协议下都能很好地工作。
接下来的 for 循环中,获取到 AsyncCall 对象,并执行 executeOn,证明这里用到了线程池。
该 executorService 就是一个线程池,executorService.execute(this),执行这个方法,就会执行Runnable 的 run 方法,上面我们说过,AsyncCall 的 run 方法的逻辑在执行 AsyncCall 中的 execute 方法。
(这里可以补充一下线程池的教程)
总结一下,就是线程池执行需要传入 Runnable 对象,去执行里面的 run 方法。RealCall 中保存了当前网络请求的属性(request),而 AsyncCall 是一个 Runnable 对象,是 RealCall 里的内部类,自然也可以获取到 RealCall 中的属性(成员变量)。线程池执行的时候,执行AsyncCall中的 run 方法,run 方法中调用执行AsyncCall中重写的 execute 方法。所以具体的网络请求实现逻辑就在execute方法中,其中getResponseWithInterceptorChain包含了责任链相关的内容,我们继续讲解。
责任链模式与拦截器
上图中,就是拦截器链中的各个拦截器。该链是从上往下的。
我们进去看看getResponseWithInterceptorChain。
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
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)
}
}
}
okhttp优秀的地方是通过设置拦截器可以很方便的实现网络请求的打印、添加一些公共参数等等。而拦截器真正的运行其实就是在这个方法中。
-
首先在interceptors中添加用户自定义的拦截器
-
然后按顺序添加各种系统内置的拦截器
-
通过RealInterceptorChain方法获取一个chain对象
-
通过chain.proceed获取response
chain 为RealInterceptorChain的实例,里面保存了成员属性interceptors、 Request ,还有一些其他额外的参数。
其中还有一个参数 index,用来标记目前在执行第 index 个拦截器
val response = chain.proceed(originalRequest)。通过调用proceed 方法开始执行拦截器, 回到我们上面自己实现的拦截器中查看:

在执行完该拦截器中想要实现的效果后,继续执行 chain.proceed(request),会根据 index 继续查找下一个拦截器。
RetryAndFollowUpInterceptor(重试和重定向拦截器) 第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求;完成两件事情:重试与重定向。 BridgeInterceptor(桥接拦截器) 补全请求,并对响应进行额外处理 CacheInterceptor(缓存拦截器) 请求前查询缓存,获得响应并判断是否需要缓存 ConnectInterceptor(链接拦截器) 与服务器完成TCP连接 (Socket) CallServerInterceptor(请求服务拦截器) 与服务器通信;封装请求数据与解析响应数据(如:HTTP报文) ...
问题:
- 一个线程只能处理一个网络请求吗?