常见泄漏原因:Activity 中匿名内部类 Observer 未取消注册、Fragment 生命周期与 View 分离导致

4.1 现象识别

两种典型泄漏场景在 Android 内存分析中呈现不同的表象。通过识别以下特征,可以快速缩小排查范围。

对比维度 场景 A:Activity 匿名内部类 场景 B:Fragment View 分离
泄漏对象 Activity 实例 Fragment 及其 View 实例
触发时机 Activity 执行 finish() 后无法 GC Fragment 进入回退栈,View 被销毁后无法 GC
关键 API 误用 observeForever() 未配对 removeObserver() 使用 getActivity() 作为 LifecycleOwner
堆栈特征 Observer 匿名内部类 → 持有 Activity.this Observer 匿名内部类 → 持有 Fragment/View
泄漏速度 每次 finish 累积一个 Activity 每次 View 分离累积一个 Fragment 链
快速判断规则:
如果 Heap 中目标 Activity 实例数 > 1 且 GC Root 路径包含 Observer,优先归入场景 A;
如果 Fragment 实例存活但其 mView 为空且被 Observer 引用,优先归入场景 B。

4.2 数据采集

使用 Android Studio Memory Profiler 进行现场取证,捕获两种场景的关键证据。

  1. 复现操作:反复进入并退出目标 Activity / 反复 add+pop 目标 Fragment 至回退栈。
  2. GC 触发:在 Profiler 中手动点击 "Force Java GC",观察内存曲线是否回到基线。
  3. Heap Dump:若内存未回落,立即 Dump Heap。
  4. 实例搜索:在 Heap Viewer 中搜索 Activity 或 Fragment 类名,记录存活实例数。
  5. GC Root 分析:选中多余实例,查看 Path to GC Root,确认是否通过 Observer 匿名内部类可达。
关键证据清单:
  • 目标类实例数 > 预期存活数(通常应为 1 或 0)
  • GC Root 路径中包含 LiveData 或 Observer 节点
  • Observer 内部类持有外部类引用(this$0 字段)

4.3 原因定位 —— 故障树分析

下面的故障树从 "LiveData Observer 注册" 出发,分两条路径直达泄漏根源,帮助工程师快速判定具体场景。

两种典型 Observer 泄漏路径故障树 两种典型 Observer 泄漏路径故障树 LiveData Observer 注册 .observe() / .observeForever() Activity 匿名内部类 observeForever 未移除 Fragment View 分离 错误 LifecycleOwner 引用 Activity 实例被 Observer 持有 通过 this$0 强引用 Fragment/View 被 Observer 持有 通过匿名内部类强引用 内存泄漏(GC Root 可达) 无法被 GC 回收 判定入口:检查 Observer 注册方式 → 是 observeForever 还是错误 LifecycleOwner

4.4 修复动作

根据故障树定位的具体场景,采取对应的代码修复方案。

场景 A:Activity 匿名内部类修复

问题代码:使用 observeForever() 但未在 onDestroy() 中移除 Observer,导致匿名内部类持有 Activity 引用。

// 问题写法:observeForever 未配对 removeObserver
public class MainActivity extends AppCompatActivity {
    private LiveData<String> liveData;
    private Observer<String> observer; // 需要持有引用

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        liveData = ...;
        observer = new Observer<String>() {
            @Override
            public void onChanged(String s) {
                // 匿名内部类隐式持有 Activity.this
                textView.setText(s);
            }
        };
        liveData.observeForever(observer);
        // 缺少:在 onDestroy 中移除
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 修复:显式移除 Observer
        liveData.removeObserver(observer);
    }
}

场景 B:Fragment View 分离修复

问题代码:使用 getActivity() 作为 LifecycleOwner,导致 Observer 生命周期与 Activity 绑定而非与 Fragment View 绑定。

// 问题写法:getActivity() 作为 LifecycleOwner
public class MyFragment extends Fragment {
    private LiveData<String> liveData;
    private TextView textView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_layout, container, false);
        textView = view.findViewById(R.id.text_view);
        liveData = ...;

        // 问题:使用 getActivity() 作为 LifecycleOwner
        liveData.observe(getActivity(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                // 匿名内部类持有 Fragment 引用(通过 textView)
                textView.setText(s);
            }
        });
        return view;
    }
    // Fragment 进入回退栈 → View 销毁 → Observer 仍存活 → 泄漏
}

// 修复写法:使用 getViewLifecycleOwner()
public class MyFragment extends Fragment {
    private LiveData<String> liveData;
    private TextView textView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_layout, container, false);
        textView = view.findViewById(R.id.text_view);
        liveData = ...;

        // 修复:使用 getViewLifecycleOwner()
        liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                // View 销毁时自动移除 Observer
                textView.setText(s);
            }
        });
        return view;
    }
}
⚠ 修复要点:
  • 场景 A 必须保证 removeObserver() 与 observeForever() 成对出现,推荐在 onDestroy() 中调用。
  • 场景 B 优先使用 getViewLifecycleOwner();若需在 Fragment 全局范围内观察,使用 Lifecycle.Event.ON_DESTROY 手动移除。
  • 匿名内部类始终持有外部类引用,无法避免时改用静态内部类 + WeakReference。

4.5 复测验证

修复后必须通过以下步骤确认泄漏已消除。

步骤 操作 预期结果
1 安装修复版 APK,打开目标页面 页面正常显示,无异常
2 反复进入并退出 Activity / 反复 add+pop Fragment 操作流畅,无卡顿
3 Memory Profiler 中触发 Force GC 内存曲线回到基线
4 Dump Heap 并搜索目标类实例数 Activity / Fragment 实例数 ≤ 1
5 检查 GC Root 路径 不再出现 Observer 引用链
验证判断规则:
若 Dump Heap 中目标类实例数 > 1,且 Path to GC Root 仍包含 Observer → 修复未生效,返回 4.4 重新检查代码;
若实例数 ≤ 1 且无 Observer GC Root → 泄漏已修复,进入 4.6 预防方案。

4.6 预防方案

从编码规范、静态检查和团队流程三个层面防止同类问题再次发生。

  • 编码规范:
    • 禁止在 Activity/Fragment 中使用 observeForever() 注册匿名内部类 Observer;确需使用时,必须声明为字段并在 onDestroy() 中移除。
    • Fragment 中观察 LiveData 必须使用 getViewLifecycleOwner(),禁止使用 getActivity() 作为 LifecycleOwner。
    • 匿名内部类优先改为静态内部类 + WeakReference 持有外部引用。
  • 静态检查:
    • 在 Android Lint 配置中启用 LiveDataObserverWithoutLifecycleOwner 检查。
    • 使用 LeakCanary 集成测试,在 CI 流程中自动检测泄漏。
  • 代码审查清单:
    • 所有 observe() 调用是否传入正确的 LifecycleOwner?
    • 所有 observeForever() 是否有对应的 removeObserver()?
    • Fragment 中的 Observer 是否可能持有已销毁的 View 引用?
推荐实践:
在团队项目中推广 “LiveData 观察三原则”——
① 能用 observe() 绝不用 observeForever();
② Fragment 永远使用 getViewLifecycleOwner();
③ 所有 Observer 优先写成静态内部类或 lambda(非匿名内部类)。