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):

静态回调泄漏故障树 静态回调泄漏 存在静态字段持有 Retrofit Callback 实例 静态内部类持有外部类 匿名回调赋值给 static 字段 回调中直接访问 Activity 修复建议:弱引用/软引用包装 目标:静态引用不阻止外部实例回收 通过 WeakReference / SoftReference 间接持有

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 复测

执行以下验证步骤:

  1. 静态引用检查:在页面退出后,通过 adb shell dumpsys meminfo 观察 Activity 实例计数是否为 0。
  2. LeakCanary 验证:触发 GC 后确认无泄漏通知。
  3. 边界测试:快速连续进出页面 10 次,观察堆内存是否稳定。
测试项预期结果验证工具
静态字段不持有强引用候选实例数量=0MAT / Android Profiler
回调执行但不崩溃无 NullPointerException日志监控
内存不增长堆内存曲线平缓Profiler 内存图

4.6 预防

最佳实践:建立团队代码规范,在 Code Review 中增加静态引用检查规则;使用 Android Lint 自定义检测(例如 StaticFieldLeak 检测项)。
  • 不允许在静态字段中直接存储Callback类型实例。
  • 如果必须使用静态内部类,确保其只通过WeakReference持有外部实例。
  • 在 BaseActivity/BaseFragment 中提供模板方法,统一处理 WeakReference 包装和生命周期检查。
  • 在 CI/CD 流水线中加入 LeakCanary 自动化测试,每次提交后运行。