一、OkHttp3:Android 网络请求的事实标准
OkHttp 是 Square 公司开发的高性能网络请求框架,在 Android 开发中几乎是必选方案。其核心优势如同 "智能快递系统":
-
HTTP/2 支持:像多车道高速公路,同一主机的所有请求共享连接,减少握手开销
-
连接池:类似快递分拣中心,复用连接避免重复建立(默认保持 5 分钟)
-
GZIP 压缩:数据打包前压缩,减少传输体积(类似快递装箱前折叠物品)
-
智能重试:请求失败时自动尝试其他 IP,如同快递地址错误时自动查找备用地址
数据对比:相比 Java 原生 HttpURLConnection,OkHttp 在典型场景下可减少 30% 的网络延迟
二、快速入门:从依赖到第一个请求
2.1 环境准备
groovy
// 引入核心依赖(3.14.x为Java版本,4.0+为Kotlin)
implementation 'com.squareup.okhttp3:okhttp:3.14.7'
// 底层数据处理库
implementation 'com.squareup.okio:okio:1.17.5'
xml
<!-- 必须申请的权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 如需缓存,添加读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2.2 GET 请求:获取百度首页
java
OkHttpClient client = new OkHttpClient(); // 创建客户端
Request request = new Request.Builder()
.url("https://www.baidu.com")
.get() // 明确声明GET方法(可省略,默认即为GET)
.build();
// 同步请求(必须在子线程执行)
new Thread(() -> {
try {
Response response = client.newCall(request).execute();
Log.d("OkHttp", "响应内容:" + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 异步请求(推荐方式,自动处理线程)
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("OkHttp", "请求失败", e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 注意:回调在子线程,更新UI需切换主线程
String html = response.body().string();
runOnUiThread(() -> textView.setText(html));
}
});
核心要点:
- 每个
Call只能执行一次,重复调用会抛出异常 - 同步请求
execute()会阻塞线程,必须在子线程调用 - 异步请求
enqueue()内部维护线程池,回调在子线程
三、POST 请求:从简单文本到复杂文件上传
3.1 提交普通文本 / JSON
java
// 提交Markdown文本
MediaType markdownType = MediaType.parse("text/x-markdown; charset=utf-8");
RequestBody body = RequestBody.create(markdownType, "**Hello World**");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(body)
.build();
// 提交JSON数据
String json = "{"name":"OkHttp","version":3.14}";
RequestBody jsonBody = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"), json);
3.2 表单提交(最常用场景)
java
// 构建表单数据
RequestBody formBody = new FormBody.Builder()
.add("username", "hfy")
.add("password", "123456")
.add("remember", "true")
.build();
Request request = new Request.Builder()
.url("https://example.com/login")
.post(formBody)
.build();
3.3 复杂请求体:同时上传表单和文件
java
// 准备文件RequestBody
File file = new File(Environment.getExternalStorageDirectory(), "avatar.jpg");
RequestBody fileBody = RequestBody.create(
MediaType.parse("image/jpeg"), file);
// 构建多部分请求体
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM) // 声明为表单类型
.addFormDataPart("username", "hufeiyang")
.addFormDataPart("phone", "13800138000")
// 第三个参数是文件RequestBody
.addFormDataPart("avatar", "profile.jpg", fileBody)
.build();
Request request = new Request.Builder()
.url("https://example.com/upload")
.post(multipartBody)
.build();
抓包分析:
http
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 123456
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"
hufeiyang
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="phone"
13800138000
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="profile.jpg"
Content-Type: image/jpeg
[文件二进制数据...]
四、高级配置:从超时到拦截器的全场景优化
4.1 全局客户端配置
java
OkHttpClient client = new OkHttpClient.Builder()
// 连接超时:建立TCP连接的最大等待时间
.connectTimeout(15, TimeUnit.SECONDS)
// 读取超时:接收响应数据的最大等待时间
.readTimeout(20, TimeUnit.SECONDS)
// 写入超时:发送请求数据的最大等待时间
.writeTimeout(10, TimeUnit.SECONDS)
// 缓存配置(500MB缓存空间)
.cache(new Cache(getExternalCacheDir(), 500 * 1024 * 1024))
// 日志拦截器(关键功能!)
.addInterceptor(chain -> {
Request request = chain.request();
long start = System.currentTimeMillis();
// 记录请求信息
Log.d("OkHttp", "请求开始: " + request.url());
Log.d("OkHttp", "请求头: " + request.headers());
// 执行实际请求
Response response = chain.proceed(request);
long end = System.currentTimeMillis();
// 记录响应信息
Log.d("OkHttp", "请求结束: " + response.code());
Log.d("OkHttp", "耗时: " + (end - start) + "ms");
return response;
})
// 构建全局唯一的客户端实例
.build();
4.2 单个请求配置
java
Request request = new Request.Builder()
.url("https://example.com/data")
// 添加自定义请求头
.addHeader("Authorization", "Bearer YOUR_TOKEN")
.addHeader("Accept", "application/json")
// 缓存策略(覆盖全局设置)
.cacheControl(CacheControl.FORCE_NETWORK) // 强制走网络
// .cacheControl(CacheControl.FORCE_CACHE) // 强制走缓存
.get()
.build();
4.3 缓存策略详解
| 策略 | 行为描述 |
|---|---|
FORCE_NETWORK | 直接请求网络,不读取缓存,响应会写入缓存 |
FORCE_CACHE | 只读取缓存,若缓存不存在则报错 |
CACHE_IF_AVAILABLE | 先查缓存,若缓存过期(根据响应头max-age)则请求网络 |
NO_CACHE | 不使用缓存,每次都请求网络 |
NO_STORE | 不使用缓存,且不保存响应到缓存 |
五、实战技巧:从异常处理到架构整合
5.1 优雅的异常处理
java
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 网络异常分类处理
if (e instanceof SocketTimeoutException) {
showToast("网络超时,请重试");
} else if (e instanceof ConnectException) {
showToast("连接失败,请检查网络");
} else {
showToast("网络错误: " + e.getMessage());
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
// 服务器错误处理
showToast("请求失败: " + response.code());
return;
}
// 处理成功响应
String data = response.body().string();
// 注意:response.body().string()只能调用一次,多次调用会报错
}
});
5.2 与 MVVM 架构整合
java
public class UserViewModel extends ViewModel {
private final OkHttpClient client;
private final MutableLiveData<User> userLiveData = new MutableLiveData<>();
private final MutableLiveData<String> errorLiveData = new MutableLiveData<>();
public UserViewModel() {
client = createOkHttpClient(); // 创建配置好的OkHttpClient
}
public LiveData<User> getUser(String userId) {
Request request = new Request.Builder()
.url("https://api.example.com/users/" + userId)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful() && response.body() != null) {
String json = response.body().string();
User user = GsonUtil.fromJson(json, User.class);
userLiveData.postValue(user);
} else {
errorLiveData.postValue("获取用户失败: " + response.code());
}
}
@Override
public void onFailure(Call call, IOException e) {
errorLiveData.postValue("网络错误: " + e.getMessage());
}
});
return userLiveData;
}
}
5.3 大文件下载优化
java
// 断点续传下载(关键在请求头Range)
Request request = new Request.Builder()
.url("https://example.com/bigfile.apk")
.addHeader("Range", "bytes=" + downloadedBytes + "-") // 已下载字节数
.build();
Response response = client.newCall(request).execute();
if (response.code() == 206) { // 206表示部分内容
try (InputStream is = response.body().byteStream();
FileOutputStream fos = new FileOutputStream(file, true)) { // true表示追加写入
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
}
六、核心原理:OkHttp 的拦截器机制
拦截器是 OkHttp 的 "灵魂",其工作原理如同快递分拣流水线:
-
应用拦截器:处理应用层逻辑(如日志、请求头添加)
-
网络拦截器:处理网络层逻辑(如缓存、重试)
-
重试与重定向拦截器:处理请求失败重试
-
桥接拦截器:处理 HTTP 头转换
-
缓存拦截器:处理缓存策略
-
连接拦截器:管理连接池
java
// 自定义拦截器示例
Interceptor logInterceptor = chain -> {
Request request = chain.request();
long start = System.nanoTime();
// 请求前处理(添加公共参数、日志记录)
Request newRequest = request.newBuilder()
.addHeader("App-Version", BuildConfig.VERSION_NAME)
.addHeader("Device-ID", getDeviceId())
.build();
Response response = chain.proceed(newRequest); // 调用下一个拦截器
// 请求后处理(响应解析、错误处理)
long end = System.nanoTime();
long duration = (end - start) / 1000000; // 转换为毫秒
Log.d("OkHttp", "请求耗时: " + duration + "ms");
Log.d("OkHttp", "响应码: " + response.code());
return response;
};
七、性能优化与陷阱规避
-
连接池优化:
java
// 自定义连接池(保持100个连接,存活时间5分钟) .connectionPool(new ConnectionPool(100, 5, TimeUnit.MINUTES)) -
GZIP 压缩:
java
// 自动压缩请求体(节省30-50%流量) .addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY)) -
陷阱规避:
- 响应体必须关闭:
response.body().close()(OkHttp3.8 + 已自动处理,但显式关闭更安全) - 避免重复读取响应体:
response.body().string()只能调用一次 - 注意内存泄漏:Activity 销毁时取消未完成的请求
call.cancel()
- 响应体必须关闭:
八、总结:OkHttp 的设计哲学
OkHttp 的强大源于其 "可扩展的管道架构",通过拦截器机制让开发者可以无缝介入请求的每个环节。在实际开发中,建议:
-
创建全局唯一的 OkHttpClient 实例,避免重复创建开销
-
用拦截器统一处理公共逻辑(日志、认证、参数添加)
-
结合 ViewModel 和 LiveData 实现响应式网络请求
-
针对不同场景设置合理的缓存策略
记住:OkHttp 不仅是一个网络请求工具,更是理解现代 Android 架构的重要入口。掌握其核心原理,能帮助你在处理复杂网络场景时更加游刃有余。