第3章 数据采集准备:OkHttp日志拦截器、Charles抓包、Heap Dump、缓存目录快照

缓存问题的根源往往隐藏在多个层面:应用层日志、网络请求与响应、JVM内存快照、文件系统缓存目录。单一数据源不足以还原完整链路,必须通过四种互补手段同时采集,才能建立“现象 → 链路 → 内存 → 磁盘”的完整证据链。本章围绕“现象识别 → 数据采集 → 原因定位 → 修复动作 → 复测 → 预防”中的数据采集环节,逐一展开每个工具的配置方法、产出物格式和适用场景。

3.1 现象识别:从哪里怀疑缓存异常?

在动手采集之前,先确认以下典型异常现象,以便选择对应工具:

现象可能原因优先采集工具
缓存命中率骤降(日志中大量Cache Miss)拦截器未正确写入响应、Key冲突、过期策略错误OkHttp日志拦截器
响应内容与服务器不一致(过时数据)服务端Cache-Control配置错误、客户端强制缓存覆盖Charles抓包
App内存暴涨、频繁GC缓存对象引用泄漏、LRU队列未释放Heap Dump
缓存文件占用磁盘远超预期缓存目录残留旧文件、未做LRU清理缓存目录快照

3.2 数据采集工具详解

3.2.1 OkHttp日志拦截器

在OkHttp的Builder链中加入HttpLoggingInterceptor,可输出每个请求/响应的完整头部和缓存决策标识。配置示例:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
new OkHttpClient.Builder()
        .addInterceptor(interceptor)
        .cache(cache)
        .build();

输出的日志片段(已去敏):

--> GET https://api.example.com/user/profile
--> Headers: Accept: application/json
              If-None-Match: "abc123"
<-- 304 Not Modified
<-- Headers: Cache-Control: public, max-age=3600
              ETag: "abc123"
              OkHttp-Sent-Millis: 1722000000000
              OkHttp-Received-Millis: 1722000000500
              OkHttp-Response-Source: NETWORK 304

关键标识:OkHttp-Response-Source 可取值 NETWORK 200(网络)、NETWORK 304(条件验证)、CACHE 200(纯缓存)等,帮助判断缓存来源。

避坑提醒:生产环境应仅开启 Level.HEADERS,避免泄露Body敏感数据;且需配合日志分级,只在DEBUG级别输出。

3.2.2 Charles抓包

用于观察“客户端最终发出的HTTP请求”与“服务端返回的真实响应”,尤其适合验证Cache-Control、Expires、ETag、Last-Modified等缓存头。配置要点:

  • 手机/模拟器设置代理指向Charles(默认8888端口)。
  • 安装Charles根证书(仅需一次),Android 7.0+需在App的network_security_config.xml中信任用户证书。
  • 在Charles的Structure视图中筛选目标域名,关注Response面板的Headers。

抓包所得典型缓存头示例:

HTTP/1.1 200 OK
Cache-Control: public, max-age=300
Age: 42
ETag: "v2.1"
Date: Sat, 27 Jul 2024 10:00:00 GMT

通过Age头可判断资源在CDN/代理层的缓存时长,对比客户端本地缓存决策是否存在偏差。

3.2.3 Heap Dump(堆快照)

当怀疑缓存对象内存泄漏或LRU队列异常时,通过jmap或Android Studio的Profiler导出.hprof文件。操作步骤:

  1. 确保进程运行在debuggable模式;
  2. 执行命令:adb exec-out jmap -dump:format=b,file=/sdcard/cache.hprof <pid>;
  3. pull文件并用MAT或Eclipse Memory Analyzer打开。

重点关注以下类及相关对象:

  • okhttp3.Cache —— 检查其internalCache的size与entry数。
  • okhttp3.internal.cache.DiskLruCache —— 检查journal文件中的条目状态。
  • java.util.LinkedHashMap —— 作为LRU队列,查看是否出现意外强引用。
重要:Heap Dump会暂停JVM线程,生产环境需在流量低谷或特定采样窗口执行,并控制Dump文件大小(通常100~500MB)。

3.2.4 缓存目录快照

定期拷贝App内部缓存目录(/data/data/<package>/cache/ 或自定义路径)的目录结构和文件元信息,用于分析文件残余、大小异常、过期文件滞留。推荐结合tree命令或自定义Python脚本采集:

adb shell
run-as com.example.app
ls -la /data/data/com.example.app/cache/okhttp/
# 输出示例
-rw------- 1 u0_a123 u0_a123  20480 2024-07-27 10:00 0000000000000001.0
-rw------- 1 u0_a123 u0_a123      512 2024-07-27 08:00 journal
-rw------- 1 u0_a123 u0_a123  14336 2024-07-26 22:00 0000000000000002.0

通过journal文件可解析缓存条目是否标记为DIRTY或CLEAN,结合文件最后修改时间判断是否存在“僵尸文件”。

3.3 故障树:多数据源交叉验证

数据采集架构 · 四层证据采集 缓存异常现象 OkHttp日志拦截器 Charles抓包 Heap Dump 缓存目录快照 产出: • HTTP头部与缓存状态码 • OkHttp-Response-Source • 请求/响应时间戳 产出: • 真实服务端响应头 • Age、Cache-Control、ETag • 请求重定向/代理链路 产出: • Cache对象引用树 • DiskLruCache条目状态 • LinkedHashMap容量/遍历 产出: • 文件列表与大小、时间 • journal文件脏状态 • 残留/僵尸文件清单 交叉验证:时序对齐 → 路径追踪

3.4 采集顺序与协同策略

同一时刻的采集数据才具有对比价值。建议按以下顺序在短时间内连续完成:

  1. 开启Charles抓包(开始记录);
  2. 同步开启OkHttp日志拦截器(输出到logcat或文件);
  3. 触发缓存操作(多次请求、刷新、离线等);
  4. 立即导出Heap Dump(暂停写操作后);
  5. 最后执行缓存目录快照(防止Dump过程改变文件状态)。

采集完毕后,将每个工具的时间轴对齐,通过请求URL和时间戳关联四份证据,即可还原“请求发出 → 拦截器决策 → 网络通信 → 缓存写入/命中 → 内存对象 → 磁盘文件”的完整路径。

3.5 判断规则:何时启用哪个工具?

问题维度首选工具辅助工具判断规则
缓存命中逻辑错误OkHttp日志拦截器Charles抓包查看Response-Source若为NETWORK 200,但服务端返回了Cache-Control,则跟踪拦截器实现
缓存过期策略失效Charles抓包日志拦截器对比服务端返回的max-age 与客户端本地读取的Age 计算是否一致
缓存OOM或内存泄漏Heap Dump日志(看GC频率)查找Cache对象保留路径,确认LRU队列是否超过maxSize
磁盘缓存膨胀或清理失效缓存目录快照Heap Dump(检查DiskLruCache)对比journal中条目数与实际文件数,查找DIRTY遗留条目
实践总结: 四种工具并非每次全用,但每次排查至少应使用日志拦截器+另一种。初次排查推荐“日志拦截器+Charles”,因为成本最低且能覆盖80%的缓存头问题;当涉及内存或磁盘异常时再补充Heap Dump和目录快照。