安卓网络请求详解:Retrofit + OkHttp 高效通信方案
在移动应用开发中,网络请求是连接客户端与服务器的核心环节。无论是数据同步、用户登录还是资源获取,都离不开高效、稳定的网络通信。Android 开发中,Retrofit 与 OkHttp 组合已成为网络请求的事实标准 ——OkHttp 负责底层网络通信,Retrofit 则封装了请求接口,简化了开发流程。
一、网络请求框架选型:为什么选择 Retrofit + OkHttp?
1. 原生网络请求的痛点
Android 原生提供的 HttpURLConnection 和 HttpClient(已废弃)存在诸多问题:
- 代码冗余:每次请求需手动处理输入输出流、线程切换、异常捕获。
- 功能薄弱:不支持拦截器、缓存、连接池等高级特性。
- 扩展性差:难以集成数据解析(如 JSON 转对象)、请求重试等功能。
- 性能一般:缺乏连接复用、压缩等优化,影响请求效率。
2. Retrofit + OkHttp 的优势
- OkHttp:Square 公司开发的底层网络库,负责实际的 HTTP 通信,支持连接池、拦截器、缓存、HTTPS、压缩等核心功能,性能优异。
- Retrofit:基于 OkHttp 的封装库,通过注解定义接口,自动生成请求代码,支持多种数据解析(Gson、Jackson 等),将网络请求转化为面向接口的编程,极大简化代码。
两者分工明确:OkHttp 处理 “如何发送请求”,Retrofit 处理 “如何定义请求”,结合使用可兼顾开发效率与运行性能。
二、环境配置:集成 Retrofit 与 OkHttp
1. 添加依赖
在 app/build.gradle 中添加以下依赖(版本号可根据官网更新):
dependencies {// OkHttp(网络通信核心)implementation 'com.squareup.okhttp3:okhttp:4.11.0'// Retrofit(接口封装)implementation 'com.squareup.retrofit2:retrofit:2.9.0'// Gson 转换器(将 JSON 解析为对象)implementation 'com.squareup.retrofit2:converter-gson:2.9.0'// 日志拦截器(调试用)implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
}
2. 声明网络权限
在 AndroidManifest.xml 中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
对于 Android 9.0(API 28+),默认禁止明文 HTTP 请求,如需支持需在 res/xml 目录下创建 network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config><base-config cleartextTrafficPermitted="true" />
</network-security-config>
并在清单文件中引用:
<application...android:networkSecurityConfig="@xml/network_security_config">...
</application>
三、Retrofit 核心用法:从定义到请求
Retrofit 的核心思想是 “用接口定义请求”,通过注解描述 HTTP 方法、URL、参数等信息,再由 Retrofit 动态生成实现类,最终发起请求。
1. 定义实体类(与接口返回数据对应)
假设服务器返回的 JSON 格式如下(以用户信息为例):
{"code": 200,"message": "success","data": {"id": 1,"name": "张三","age": 25}
}
定义对应的实体类:
// 基础响应类(通用结构)
public class BaseResponse<T> {private int code;private String message;private T data;// getter 和 setterpublic int getCode() { return code; }public void setCode(int code) { this.code = code; }public String getMessage() { return message; }public void setMessage(String message) { this.message = message; }public T getData() { return data; }public void setData(T data) { this.data = data; }
}// 用户信息类
public class User {private int id;private String name;private int age;// getter 和 setterpublic int getId() { return id; }public void setId(int id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }
}
2. 定义请求接口(核心)
通过 Retrofit 注解定义接口,常用注解如下:
- HTTP 方法:
@GET、@POST、@PUT、@DELETE等。 - URL 路径:
@Path(路径参数)、@Query(查询参数)、@Url(完整 URL)。 - 请求体:
@Body(POST 请求体)、@FormUrlEncoded+@Field(表单提交)。 - 请求头:
@Header、@Headers。
示例接口(用户相关接口):
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;public interface UserService {// 1. GET 请求:根据 ID 获取用户信息// URL 示例:https://api.example.com/user/1?type=1@GET("user/{id}")Call<BaseResponse<User>> getUserById(@Path("id") int userId, // 路径参数(替换 URL 中的 {id})@Query("type") int type // 查询参数(拼接在 URL 后 ?type=1));// 2. POST 表单请求:用户登录// URL 示例:https://api.example.com/login(表单参数:username=xxx&password=xxx)@FormUrlEncoded // 表示表单提交@POST("login")Call<BaseResponse<User>> login(@Field("username") String username, // 表单字段@Field("password") String password);// 3. POST JSON 请求:创建用户// 请求体为 JSON 格式:{"name":"张三","age":25}@POST("user")Call<BaseResponse<User>> createUser(@Body User user); // @Body 自动将对象转为 JSON
}
3. 创建 Retrofit 实例并发起请求
(1)初始化 Retrofit
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;public class RetrofitClient {private static final String BASE_URL = "https://api.example.com/";private static Retrofit retrofit;// 单例模式获取 Retrofit 实例public static Retrofit getInstance() {if (retrofit == null) {// 1. 配置 OkHttp 客户端(可添加拦截器、超时等)OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();// 添加日志拦截器(调试用,生产环境可移除)HttpLoggingInterceptor logging = new HttpLoggingInterceptor();logging.setLevel(HttpLoggingInterceptor.Level.BODY); // 打印完整日志okHttpBuilder.addInterceptor(logging);// 设置超时时间okHttpBuilder.connectTimeout(10, TimeUnit.SECONDS); // 连接超时okHttpBuilder.readTimeout(10, TimeUnit.SECONDS); // 读取超时okHttpBuilder.writeTimeout(10, TimeUnit.SECONDS); // 写入超时// 2. 构建 Retrofitretrofit = new Retrofit.Builder().baseUrl(BASE_URL) // 基础 URL(必须以 / 结尾).client(okHttpBuilder.build()) // 关联 OkHttp.addConverterFactory(GsonConverterFactory.create()) // 添加 Gson 解析器.build();}return retrofit;}// 获取 Service 接口实例public static UserService getUserService() {return getInstance().create(UserService.class);}
}
(2)发起同步 / 异步请求
Retrofit 支持同步(execute())和异步(enqueue())两种请求方式,主线程中必须使用异步请求(避免 ANR)。
异步请求示例:
public class MainActivity extends AppCompatActivity {private UserService userService;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取接口实例userService = RetrofitClient.getUserService();// 示例 1:获取用户信息getUserInfo(1, 1);// 示例 2:用户登录userLogin("zhangsan", "123456");// 示例 3:创建用户createUser(new User(0, "李四", 22));}// 1. 获取用户信息private void getUserInfo(int userId, int type) {Call<BaseResponse<User>> call = userService.getUserById(userId, type);call.enqueue(new Callback<BaseResponse<User>>() {// 请求成功回调(子线程)@Overridepublic void onResponse(Call<BaseResponse<User>> call, Response<BaseResponse<User>> response) {if (response.isSuccessful()) { // 状态码 200-300BaseResponse<User> baseResponse = response.body();if (baseResponse != null && baseResponse.getCode() == 200) {User user = baseResponse.getData();// 更新 UI(需切换到主线程)runOnUiThread(() -> {Toast.makeText(MainActivity.this, "用户:" + user.getName(), Toast.LENGTH_SHORT).show();});} else {// 业务逻辑错误(如 code != 200)String msg = baseResponse != null ? baseResponse.getMessage() : "未知错误";showError(msg);}} else {// HTTP 错误(如 404、500)showError("请求失败:" + response.code());}}// 请求失败回调(子线程,如网络异常)@Overridepublic void onFailure(Call<BaseResponse<User>> call, Throwable t) {showError("网络异常:" + t.getMessage());}});}// 2. 用户登录private void userLogin(String username, String password) {Call<BaseResponse<User>> call = userService.login(username, password);call.enqueue(new Callback<BaseResponse<User>>() {@Overridepublic void onResponse(Call<BaseResponse<User>> call, Response<BaseResponse<User>> response) {// 处理逻辑类似上面}@Overridepublic void onFailure(Call<BaseResponse<User>> call, Throwable t) {showError(t.getMessage());}});}// 3. 创建用户private void createUser(User user) {Call<BaseResponse<User>> call = userService.createUser(user);call.enqueue(new Callback<BaseResponse<User>>() {@Overridepublic void onResponse(Call<BaseResponse<User>> call, Response<BaseResponse<User>> response) {// 处理逻辑类似上面}@Overridepublic void onFailure(Call<BaseResponse<User>> call, Throwable t) {showError(t.getMessage());}});}// 显示错误信息(切换到主线程)private void showError(String msg) {runOnUiThread(() -> {Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();});}
}
同步请求示例(仅能在子线程中使用):
new Thread(() -> {try {Call<BaseResponse<User>> call = userService.getUserById(1, 1);Response<BaseResponse<User>> response = call.execute(); // 同步执行(阻塞当前线程)if (response.isSuccessful()) {// 处理结果}} catch (IOException e) {e.printStackTrace();}
}).start();
四、OkHttp 核心特性:拦截器与缓存
OkHttp 作为底层网络库,提供了拦截器、缓存、连接池等高级功能,是优化网络请求的关键。
1. 拦截器(Interceptor)
拦截器用于在请求发送前或响应返回后插入自定义逻辑(如添加统一请求头、打印日志、Token 验证等),分为应用拦截器和网络拦截器:
- 应用拦截器:拦截应用发起的请求(包括缓存命中的请求),适合添加通用逻辑。
- 网络拦截器:仅拦截实际发送到网络的请求,适合监控网络传输细节。
(1)添加统一请求头(应用拦截器)
public class HeaderInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {// 原始请求Request originalRequest = chain.request();// 构建新请求(添加统一头信息)Request newRequest = originalRequest.newBuilder().addHeader("App-Version", "1.0.0") // 应用版本.addHeader("OS", "Android") // 系统类型.addHeader("Token", getToken()) // 用户 Token(从本地获取).build();// 继续执行请求return chain.proceed(newRequest);}// 获取本地存储的 Tokenprivate String getToken() {// 实际项目中从 SharedPreferences 或其他地方获取return "user_token_123456";}
}
在 OkHttp 中添加拦截器:
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder.addInterceptor(new HeaderInterceptor()); // 添加应用拦截器
// okHttpBuilder.addNetworkInterceptor(...); // 添加网络拦截器
(2)Token 过期自动刷新(拦截器高级用法)
public class TokenInterceptor implements Interceptor {private static final String TOKEN_EXPIRED_CODE = "401"; // Token 过期状态码private final Object lock = new Object(); // 同步锁,避免并发刷新 Token@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Response response = chain.proceed(request);// 判断 Token 是否过期if (isTokenExpired(response)) {synchronized (lock) { // 同步处理,避免多次刷新// 再次检查(防止已刷新过)Response newResponse = chain.proceed(request);if (isTokenExpired(newResponse)) {// 刷新 Token(同步请求)String newToken = refreshToken();if (newToken != null) {// 用新 Token 重新发起请求Request newRequest = request.newBuilder().header("Token", newToken).build();newResponse.close(); // 关闭旧响应return chain.proceed(newRequest);}}return newResponse;}}return response;}// 判断响应是否为 Token 过期private boolean isTokenExpired(Response response) {try {if (response.code() == 401) { // 通常 401 表示未授权String body = response.body().string();BaseResponse<?> baseResponse = new Gson().fromJson(body, BaseResponse.class);// 重置响应体(因为 body().string() 只能调用一次)ResponseBody newBody = ResponseBody.create(body, response.body().contentType());response = response.newBuilder().body(newBody).build();return baseResponse.getCode() == Integer.parseInt(TOKEN_EXPIRED_CODE);}} catch (Exception e) {e.printStackTrace();}return false;}// 刷新 Token(同步请求)private String refreshToken() throws IOException {// 发起刷新 Token 请求(使用 OkHttp 直接调用,避免依赖 Retrofit 导致循环)OkHttpClient client = new OkHttpClient();Request refreshRequest = new Request.Builder().url(RetrofitClient.BASE_URL + "refreshToken").post(RequestBody.create("", MediaType.parse("application/json"))).build();Response response = client.newCall(refreshRequest).execute();if (response.isSuccessful()) {String body = response.body().string();BaseResponse<TokenBean> tokenResponse = new Gson().fromJson(body, new TypeToken<BaseResponse<TokenBean>>(){}.getType());if (tokenResponse.getCode() == 200) {String newToken = tokenResponse.getData().getToken();// 保存新 Token 到本地saveToken(newToken);return newToken;}}return null;}private void saveToken(String token) {// 保存到 SharedPreferences}
}
2. 缓存策略
OkHttp 支持 HTTP 缓存(基于 Cache-Control 头),可减少重复请求,提升离线体验。
(1)配置缓存
// 缓存目录(应用私有目录,避免被清理)
File cacheDir = new File(context.getCacheDir(), "http_cache");
int cacheSize = 10 * 1024 * 1024; // 10MB 缓存大小OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder().cache(new Cache(cacheDir, cacheSize)) // 设置缓存.addInterceptor(new CacheInterceptor()); // 自定义缓存策略
(2)自定义缓存拦截器
public class CacheInterceptor implements Interceptor {private static final int MAX_AGE = 60; // 在线时缓存有效期(60秒)private static final int MAX_STALE = 60 * 60 * 24 * 7; // 离线时缓存有效期(7天)@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Response response = chain.proceed(request);if (isNetworkAvailable(context)) {// 有网络时,设置缓存有效期为 MAX_AGEresponse = response.newBuilder().header("Cache-Control", "public, max-age=" + MAX_AGE).removeHeader("Pragma") // 清除干扰缓存的头.build();} else {// 无网络时,设置缓存有效期为 MAX_STALEresponse = response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + MAX_STALE).removeHeader("Pragma").build();}return response;}// 判断网络是否可用private boolean isNetworkAvailable(Context context) {ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);NetworkInfo info = cm.getActiveNetworkInfo();return info != null && info.isConnected();}
}
五、Retrofit 高级特性
1. 自定义数据解析器
Retrofit 默认支持 Gson、Jackson 等解析器,若需自定义(如解析 XML 或特殊格式),可实现 Converter.Factory:
// 示例:简单的 JSON 解析器(实际项目用 Gson 即可)
public class CustomConverterFactory extends Converter.Factory {@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {return responseBody -> {String json = responseBody.string();// 自定义解析逻辑(如用 FastJson 解析)return new Gson().fromJson(json, type);};}
}// 在 Retrofit 中添加
retrofit = new Retrofit.Builder()....addConverterFactory(new CustomConverterFactory()).build();
2. 支持 RxJava(响应式编程)
Retrofit 可与 RxJava 结合,实现更灵活的异步处理(如请求合并、线程切换):
- 添加依赖:
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
- 定义接口返回
Observable:
public interface UserService {@GET("user/{id}")Observable<BaseResponse<User>> getUserByIdRx(@Path("id") int userId);
}
- 构建 Retrofit 时添加 RxJava 适配器:
retrofit = new Retrofit.Builder()....addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build();
- 使用 RxJava 发起请求:
userService.getUserByIdRx(1).subscribeOn(Schedulers.io()) // 订阅在 IO 线程.observeOn(AndroidSchedulers.mainThread()) // 观察在主线程.subscribe(baseResponse -> {// 成功处理},throwable -> {// 错误处理});
3. 取消请求
当页面销毁或不再需要请求结果时,需取消请求以避免内存泄漏:
// 保存 Call 对象
private Call<BaseResponse<User>> userCall;// 发起请求时赋值
userCall = userService.getUserById(1, 1);
userCall.enqueue(...);// 页面销毁时取消
@Override
protected void onDestroy() {super.onDestroy();if (userCall != null && !userCall.isCanceled()) {userCall.cancel();}
}
六、网络请求最佳实践
封装网络工具类:将 Retrofit 初始化、Service 创建、请求处理等逻辑封装为单例工具类,避免重复代码。
统一异常处理:封装
Callback子类,统一处理网络异常、HTTP 错误、业务错误(如 Token 过期),减少冗余代码。避免主线程阻塞:所有网络请求必须在子线程执行,Retrofit 的
enqueue()已默认处理线程切换,无需手动创建线程。合理设置超时:根据业务需求设置连接超时(如 10s)、读写超时(如 15s),避免请求长期无响应。
加密敏感数据:对密码、Token 等敏感信息,在请求前加密(如 AES),避免明文传输。
适配无网络场景:结合缓存策略,在无网络时展示缓存数据,提升用户体验。
生产环境移除日志:日志拦截器会泄露敏感信息且影响性能,生产环境需关闭或设置为
NONE级别。
七、面试常见问题
Retrofit 和 OkHttp 的关系是什么?
- OkHttp 是底层网络库,负责建立连接、发送 / 接收数据、管理连接池等。
- Retrofit 是基于 OkHttp 的封装库,通过注解定义接口,自动生成请求代码,简化开发。
- 关系:Retrofit 将网络请求抽象为接口,实际请求由 OkHttp 执行。
Retrofit 的工作原理是什么?
- 步骤 1:通过注解定义接口,描述请求方法、URL、参数等。
- 步骤 2:Retrofit 使用动态代理(
Proxy)为接口生成实现类。 - 步骤 3:调用接口方法时,实现类将注解解析为 OkHttp 的
Request对象。 - 步骤 4:通过 OkHttp 发送请求,获取响应后由
Converter解析为 Java 对象。
OkHttp 的拦截器有几种?区别是什么?
- 两种:应用拦截器(
addInterceptor)和网络拦截器(addNetworkInterceptor)。 - 区别:
- 应用拦截器:拦截所有请求(包括缓存命中的请求),不关心网络细节,适合添加通用头、Token 等。
- 网络拦截器:仅拦截实际网络请求,可获取 DNS、重定向等信息,适合监控网络传输。
- 两种:应用拦截器(
如何处理 Token 过期问题?
- 方案:通过拦截器检测响应中的 Token 过期标识(如 401 状态码),自动刷新 Token 后重试请求。
- 注意:需加同步锁避免并发刷新,重试时用新 Token 重建请求。
Retrofit 如何支持 RxJava?
- 添加
RxJava3CallAdapterFactory依赖。 - 接口方法返回
Observable/Flowable等 RxJava 类型。 - 通过
subscribeOn和observeOn切换线程,简化异步处理。
- 添加
OkHttp 的缓存机制是如何实现的?
- 基于 HTTP 协议的
Cache-Control头(如max-age、no-cache)。 - 通过
Cache类管理本地缓存文件,键为请求 URL 的哈希,值为响应数据。 - 拦截器可自定义缓存策略(如在线 / 离线时的缓存有效期)。
- 基于 HTTP 协议的
Retrofit 中
@Path、@Query、@Field、@Body的区别?@Path:替换 URL 中的路径参数(如user/{id}中的id)。@Query:添加 URL 后的查询参数(如?type=1)。@Field:表单提交的字段(需配合@FormUrlEncoded)。@Body:将对象转为请求体(如 JSON 格式,适用于 POST 请求)。
如何取消 Retrofit 的请求?
- 保存
Call对象,调用call.cancel()方法取消。 - 在
Activity的onDestroy()中取消未完成的请求,避免内存泄漏。
- 保存
Retrofit 与 OkHttp 组合是安卓网络请求的最优解:Retrofit 以接口化编程简化了请求定义,OkHttp 则以高性能和丰富特性保障了底层通信。本文从基础用法到高级特性,详细讲解了接口定义、拦截器、缓存、RxJava 集成等核心技术,并提供了最佳实践指南。掌握这两个框架,不仅能提升网络请求的开发效率,还能应对复杂场景(如 Token 刷新、离线缓存),是安卓开发者的必备技能。在实际项目中,需结合业务需求合理配置拦截器和缓存策略,同时注重异常处理和性能优化,打造稳定、高效的网络层。
