第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文件。操作步骤:
- 确保进程运行在
debuggable模式; - 执行命令:
adb exec-out jmap -dump:format=b,file=/sdcard/cache.hprof <pid>; - pull文件并用MAT或Eclipse Memory Analyzer打开。
重点关注以下类及相关对象:
okhttp3.Cache—— 检查其internalCache的size与entry数。okhttp3.internal.cache.DiskLruCache—— 检查journal文件中的条目状态。java.util.LinkedHashMap—— 作为LRU队列,查看是否出现意外强引用。
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 故障树:多数据源交叉验证
3.4 采集顺序与协同策略
同一时刻的采集数据才具有对比价值。建议按以下顺序在短时间内连续完成:
- 开启Charles抓包(开始记录);
- 同步开启OkHttp日志拦截器(输出到logcat或文件);
- 触发缓存操作(多次请求、刷新、离线等);
- 立即导出Heap Dump(暂停写操作后);
- 最后执行缓存目录快照(防止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遗留条目 |