19、网络请求:Retrofit + OkHttp、RESTful API设计、离线缓存

车载多媒体应用,说白了就是一辆车上的「信息中枢」。导航、音乐、语音助手、车辆状态……这些功能全都离不开网络请求。你想想看,用户开车时最怕什么?网络断了,地图加载不出来,音乐卡顿,那体验简直灾难。

所以这一章,我打算跟你聊聊车载场景下网络请求的那些事。我会结合我这些年做车载项目的经验,把 Retrofit + OkHttp 这套组合拳讲透,顺便聊聊 RESTful API 怎么设计才靠谱,以及离线缓存到底该怎么搞。

核心要点:车载网络请求不是简单的「发请求、收响应」。它要应对弱网、断网、高延迟、低功耗等极端场景。选对工具、设计好架构,才能让应用在各种网络条件下都「稳如老狗」。

19.1 为什么是 Retrofit + OkHttp?

Android 原生网络请求,有 HttpURLConnection、Volley、AsyncHttpClient 等等。但我个人习惯,只要不是写 Demo,一律用 Retrofit + OkHttp。为什么?

  • Retrofit:把 HTTP API 声明成 Java 接口,用注解定义请求方式、参数、路径。代码干净,可读性强。
  • OkHttp:底层网络库,负责连接、复用、超时、拦截器。性能强悍,支持 HTTP/2、WebSocket。
  • 两者结合:Retrofit 依赖 OkHttp 做实际传输,OkHttp 提供拦截器机制,方便我们做日志、缓存、重试等操作。

我在项目中遇到过一个问题:某次 OTA 升级时,大量车辆同时请求服务器,导致连接池耗尽。后来用 OkHttp 的 ConnectionPool 配置了合适的最大空闲连接数和存活时间,问题就解决了。嗯,细节决定成败。

19.2 快速集成与基础用法

先加依赖。在 build.gradle 里加上:

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'

然后定义一个 API 接口:

public interface CarApi {
    @GET("api/vehicle/status")
    Call<VehicleStatus> getVehicleStatus(@Query("vin") String vin);

    @POST("api/ota/check")
    Call<OtaResponse> checkOtaUpdate(@Body OtaRequest request);
}

创建 Retrofit 实例:

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.yourcar.com/")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

CarApi api = retrofit.create(CarApi.class);

调用接口:

api.getVehicleStatus("LSVA1234567890").enqueue(new Callback<VehicleStatus>() {
    @Override
    public void onResponse(Call<VehicleStatus> call, Response<VehicleStatus> response) {
        // 处理成功响应
    }

    @Override
    public void onFailure(Call<VehicleStatus> call, Throwable t) {
        // 处理网络错误
    }
});

小技巧:车载环境下,网络延迟波动大。建议把 connectTimeout 设短一点(5-10秒),readTimeout 设长一点(15-30秒)。这样能快速发现连接失败,同时给慢速响应留足时间。

19.3 RESTful API 设计:车载场景下的最佳实践

RESTful API 设计,说白了就是「用 HTTP 方法表达资源操作」。但在车载场景下,有几个点需要特别注意。

19.3.1 资源命名与版本控制

我建议用名词复数表示资源,比如 /api/vehicles/api/ota/packages。版本号放在 URL 里,比如 /api/v2/vehicles。为什么?因为车载系统升级周期长,不同版本的车机可能共存,API 版本控制能避免兼容性问题。

19.3.2 状态码与错误处理

不要只返回 200 和 500。我见过一个项目,所有错误都返回 200,然后在 body 里塞一个 errorCode。这种做法,说实话,很坑。调试时根本分不清是网络问题还是业务问题。

正确的做法:

  • 200:成功
  • 400:参数错误
  • 401:鉴权失败
  • 404:资源不存在
  • 429:请求太频繁(限流)
  • 500:服务器内部错误

19.3.3 分页与批量操作

车载日志上传、轨迹查询等场景,数据量可能很大。用分页参数 ?page=1&size=20 来控制。响应里带上 totalhasMore 字段,方便客户端判断是否继续请求。

注意:车载网络流量通常比较贵(尤其是通过 T-Box 走蜂窝网络)。不要频繁轮询,尽量用长连接或 WebSocket 推送。如果必须轮询,间隔建议不少于 30 秒。

19.4 离线缓存:让应用在无网时也能用

车载场景下,离线缓存不是「锦上添花」,而是「刚需」。你想想看,车辆进入隧道、地下车库,网络瞬间断开。如果应用直接崩掉或白屏,用户会怎么想?

OkHttp 内置了缓存机制,配合 Retrofit 可以轻松实现离线缓存。

19.4.1 配置 OkHttp 缓存

File cacheFile = new File(context.getCacheDir(), "okhttp_cache");
Cache cache = new Cache(cacheFile, 10 * 1024 * 1024); // 10MB 缓存空间

OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(new CacheInterceptor()) // 自定义缓存拦截器
        .build();

19.4.2 自定义缓存拦截器

我曾经踩过一个坑:默认缓存策略在弱网下会返回过期的缓存数据。后来我写了一个拦截器,根据网络状态动态调整缓存策略。

public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // 无网络时,强制使用缓存
        if (!NetworkUtil.isConnected(context)) {
            request = request.newBuilder()
                    .header("Cache-Control", "only-if-cached, max-stale=" + 60 * 60 * 24 * 7) // 7天内的缓存
                    .build();
        }

        Response response = chain.proceed(request);

        // 有网络时,设置缓存有效期
        if (NetworkUtil.isConnected(context)) {
            response = response.newBuilder()
                    .header("Cache-Control", "public, max-age=" + 60 * 10) // 10分钟缓存
                    .removeHeader("Pragma")
                    .build();
        }

        return response;
    }
}

19.4.3 缓存策略选择

场景 缓存策略 说明
车辆状态(速度、电量) 不缓存或短缓存(30秒) 实时性要求高
地图数据、POI 列表 长缓存(1天-7天) 数据变化慢,离线可用
OTA 升级包信息 缓存到下次请求 避免重复下载
用户设置、偏好 本地持久化(Room/SharedPreferences) 不依赖网络

避坑指南:我曾经在某个项目中,把所有 API 响应都缓存了 24 小时。结果用户改了车辆昵称,过了两天才刷新出来。后来我学乖了:写操作(POST/PUT/DELETE)不要缓存,读操作(GET)根据数据特性设置不同的缓存时间。

19.5 知识体系总览

下面这张图,是我梳理的本章知识体系。你可以把它当作一个「导航图」,方便回顾。

车载网络请求知识体系 网络请求核心 Retrofit + OkHttp 集成 依赖配置 API 接口声明 OkHttp 配置 RESTful API 设计 资源命名 & 版本控制 状态码 & 错误处理 分页 & 批量操作 离线缓存策略 OkHttp 缓存配置 自定义拦截器 缓存策略选择

19.6 写在最后

网络请求这块,说难不难,说简单也不简单。关键是你要理解车载场景的特殊性:网络不稳定、流量贵、实时性要求高。选对工具(Retrofit + OkHttp),设计好 API,再加上合理的离线缓存策略,你的应用就能在各种网络条件下「游刃有余」。

我个人习惯,每次写完网络层,都会做一次「弱网测试」:用 Charles 或 Fiddler 模拟 3G、2G、甚至断网场景,看看应用表现如何。嗯,这个习惯帮我避免了好几次线上事故。


公众号:蓝海资料掘金营,微信deep3321