4. 静态回调与弱引用模式:WeakReference 包装、静态内部类 + 软引用
4.1 现象识别
本节聚焦于一类特殊的 Retrofit 回调泄漏:静态回调。当 Activity 或 Fragment 中将一个Callback对象赋值给某个静态字段(或通过静态内部类持有),即使 UI 已经销毁,该静态引用仍会阻止回调对象及其所属整个外部类(如 Activity)被 GC 回收。
| 泄漏特征 | 典型表现 |
|---|---|
| 静态字段持有 Retrofit Callback | 退出页面后,LeakCanary 报告静态引用链:静态类 → Callback → 匿名内部类 → Activity |
| 回调仍继续执行但 UI 已不可见 | 日志中出现 onResponse/onFailure,但 Activity.isDestroyed() 为 true |
| 内存抖动或 OOM 风险 | 每次进入页面都会创建新的静态回调,旧的回调及其外部类无法回收 |
4.2 数据采集
使用以下工具与命令定位静态引用来源:
- MAT (Memory Analyzer Tool):使用 OQL 查询
select * from instance of com.example.MyCallback where static - Android Studio Profiler:Capture Heap → 搜索回调类名 → 查看 GC Root 路径
# 示例:LeakCanary 堆栈路径
┬───
│ GC Root: Static field
│ └─ com.example.MyApplication.sCallback
│ └─ retrofit2.Callback (匿名实现)
│ └─ com.example.MainActivity (被持有)
│ └─ 泄露 2.3 MB
4.3 原因定位
故障树分析(FTA):
4.4 修复动作:WeakReference 包装与静态内部类+软引用
两种核心修复模式,优先推荐 WeakReference 模式(配合生命周期检查):
// 模式 A:WeakReference 包装 (推荐)
public class SafeCallback implements Callback<ResponseBody> {
private final WeakReference<Activity> activityRef;
public SafeCallback(Activity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Activity act = activityRef.get();
if (act == null || act.isDestroyed() || act.isFinishing()) {
return; // 安全退出,不更新 UI
}
// 更新 UI ...
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Activity act = activityRef.get();
if (act == null) return;
// 处理失败...
}
}
// 使用方式:不声明为静态字段,或只存 WeakReference
// 若必须设为静态,则只存储 WeakReference 而非直接存储 SafeCallback
// 模式 B:静态内部类 + SoftReference (适用于需要缓存回调对象的场景)
public class MainActivity extends AppCompatActivity {
private static SoftReference<MainActivity> sActivityRef = new SoftReference<>(null);
// 清理方法:onDestroy 时主动置空
@Override
protected void onDestroy() {
super.onDestroy();
sActivityRef.clear();
}
private static class NetworkCallback implements Callback<ResponseBody> {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
MainActivity activity = sActivityRef.get();
if (activity == null || activity.isFinishing()) return;
// ...
}
// onFailure 同理
}
private void startRequest() {
sActivityRef = new SoftReference<>(this);
RetrofitClient.getApi().getData().enqueue(new NetworkCallback());
}
}
| 模式 | 引用类型 | 适用场景 | 注意 |
|---|---|---|---|
| WeakReference 包装 | 弱引用 | 大多数回调场景,UI 层回调 | 需配合 isDestroyed() 检查 |
| 静态内部类 + SoftReference | 软引用 | 需要使用静态方法或缓存回调对象 | 软引用内存不足时会被回收,需处理 null |
关键修复原则:无论哪种模式,都必须避免 直接强引用 Activity 或 Fragment。同时,在
onDestroy 或 onStop 中主动取消网络请求(call.cancel())可以尽早结束回调生命周期。4.5 复测
执行以下验证步骤:
- 静态引用检查:在页面退出后,通过
adb shell dumpsys meminfo观察 Activity 实例计数是否为 0。 - LeakCanary 验证:触发 GC 后确认无泄漏通知。
- 边界测试:快速连续进出页面 10 次,观察堆内存是否稳定。
| 测试项 | 预期结果 | 验证工具 |
|---|---|---|
| 静态字段不持有强引用 | 候选实例数量=0 | MAT / Android Profiler |
| 回调执行但不崩溃 | 无 NullPointerException | 日志监控 |
| 内存不增长 | 堆内存曲线平缓 | Profiler 内存图 |
4.6 预防
最佳实践:建立团队代码规范,在 Code Review 中增加静态引用检查规则;使用 Android Lint 自定义检测(例如
StaticFieldLeak 检测项)。- 不允许在静态字段中直接存储
Callback类型实例。 - 如果必须使用静态内部类,确保其只通过
WeakReference持有外部实例。 - 在 BaseActivity/BaseFragment 中提供模板方法,统一处理
WeakReference包装和生命周期检查。 - 在 CI/CD 流水线中加入 LeakCanary 自动化测试,每次提交后运行。