3、OkHttp进阶:Post请求(Form表单、Json)、文件上传与下载、拦截器(Interceptor)机制
好,咱们接着聊。上一章我们把 OkHttp 的 Get 请求讲透了,这一章我带你深入 Post 请求的世界。说实话,Post 请求才是我们日常开发中的重头戏。登录、注册、提交订单、上传头像……几乎每个 App 都离不开它。
我个人习惯把 Post 请求分成三类:表单提交、JSON 提交、文件上传。这三类场景,OkHttp 都提供了非常优雅的 API。咱们一个一个来。
3.1 Form表单提交:最传统的 Post 方式
Form 表单,说白了就是 application/x-www-form-urlencoded 这种格式。数据以键值对的形式拼接在请求体里,像这样:username=admin&password=123456。
OkHttp 里怎么做?用 FormBody 构建器。我直接上代码:
OkHttpClient client = new OkHttpClient();
FormBody formBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
Request request = new Request.Builder()
.url("https://api.example.com/login")
.post(formBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 网络异常处理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String result = response.body().string();
// 解析结果
}
}
});
嗯,这里要注意一点:FormBody 默认使用 UTF-8 编码。如果你遇到中文乱码,先检查服务端是不是也用的 UTF-8。我在项目中遇到过几次,最后发现是服务端用了 GBK,那叫一个坑。
3.2 JSON 提交:现代 API 的主流选择
现在大部分 RESTful API 都要求用 JSON 格式提交数据。OkHttp 没有内置 JSON 构建器,但我们可以用 RequestBody 配合 MediaType 来实现。
我个人习惯先构建一个 JSON 字符串,然后封装成 RequestBody:
// 构建 JSON 字符串
String json = "{ \"username\": \"admin\", \"password\": \"123456\" }";
// 或者用 Gson/Jackson 序列化对象
// Gson gson = new Gson();
// String json = gson.toJson(user);
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody requestBody = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url("https://api.example.com/login")
.post(requestBody)
.build();
你想想看,为什么现在大家都用 JSON 而不是 Form 表单?因为 JSON 支持嵌套结构,表达能力更强。比如你要提交一个订单,里面包含商品列表、收货地址、优惠券信息,用 Form 表单你得拼一堆奇怪的参数名,用 JSON 就清晰多了。
RequestBody.create() 时忘了指定 MediaType,OkHttp 默认会使用 application/octet-stream,服务端解析不了,直接返回 415 错误。所以,一定要显式指定 MediaType。
3.3 文件上传与下载:Multipart 的实战
文件上传,用的是 MultipartBody。它模拟了 HTML 表单的 multipart/form-data 格式。每个文件作为一个 Part 上传,还可以附带普通字段。
我直接给你看一个上传头像的例子:
File file = new File("/sdcard/avatar.jpg");
RequestBody fileBody = RequestBody.create(file, MediaType.parse("image/jpeg"));
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("user_id", "12345")
.addFormDataPart("avatar", "avatar.jpg", fileBody)
.build();
Request request = new Request.Builder()
.url("https://api.example.com/upload")
.post(multipartBody)
.build();
这里有个细节:addFormDataPart 的第二个参数是文件名,服务端通常用它来保存文件。如果你传 null,服务端可能不知道文件叫什么名字。
至于文件下载,OkHttp 本身不提供断点续传,但我们可以结合 ResponseBody 的流式读取来实现。我习惯这样写:
Request request = new Request.Builder()
.url("https://example.com/file.zip")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) return;
// 获取输入流
InputStream inputStream = response.body().byteStream();
FileOutputStream outputStream = new FileOutputStream("/sdcard/file.zip");
byte[] buffer = new byte[8192];
int bytesRead;
long totalBytes = response.body().contentLength();
long downloadedBytes = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
downloadedBytes += bytesRead;
// 这里可以回调进度,比如用接口通知 UI 更新
}
outputStream.flush();
outputStream.close();
inputStream.close();
}
});
WRITE_EXTERNAL_STORAGE 权限(Android 10 以上要用分区存储)。
3.4 拦截器(Interceptor)机制:OkHttp 的灵魂
拦截器,说白了就是请求和响应的「中间人」。它可以在请求发出前修改请求头、添加日志、重试失败请求,也可以在响应返回后修改响应体、缓存数据。
OkHttp 的拦截器分两种:
- Application Interceptor:在应用层拦截,可以修改原始请求和最终响应。
- Network Interceptor:在网络层拦截,可以看到重定向和重试后的请求。
我平时用得最多的是日志拦截器。自己写一个也不难:
class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 记录请求信息
long startTime = System.nanoTime();
System.out.println("请求 URL: " + request.url());
System.out.println("请求方法: " + request.method());
System.out.println("请求头: " + request.headers());
// 执行请求
Response response = chain.proceed(request);
// 记录响应信息
long endTime = System.nanoTime();
System.out.println("响应码: " + response.code());
System.out.println("耗时: " + (endTime - startTime) / 1e6 + " ms");
return response;
}
}
// 使用
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
你想想看,有了拦截器,我们可以在不修改业务代码的情况下,统一给所有请求加上 Token、统一处理错误码、统一打印日志。这就是 AOP(面向切面编程)的思想。
intercept 方法里加个循环。但要注意,重试次数不能太多,否则会雪崩。
还有一个常见的场景:添加公共请求头。比如每个请求都要带上 Authorization 和 App-Version:
class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + token)
.header("App-Version", "1.0.0")
.build();
return chain.proceed(newRequest);
}
}
嗯,这里要注意:header() 方法会覆盖同名头,而 addHeader() 是追加。如果你需要保留原有的头,用 addHeader。
3.5 总结一下
这一章我们讲了 Post 请求的三种形式、文件上传下载、以及拦截器机制。说白了,OkHttp 的核心就是 Request + OkHttpClient + Callback 这个铁三角。掌握了这些,你已经能应对 90% 的网络请求场景了。
下一章,我会带你看看 OkHttp 的缓存策略和连接池管理。这些东西平时用不到,但一旦遇到性能问题,它们就是救命稻草。
ConnectivityManager 判断网络状态。