4. 泄漏根因分类:未解绑、静态内部类隐式持有、匿名内部类、单例持有Context

动态注册的 BroadcastReceiver 泄漏,本质上都是 Activity / Context 引用被外部对象长周期持有,导致生命周期结束时无法回收。根据引用持有方式的不同,可归纳为以下四类根因:

核心判断规则:只要 BroadcastReceiver 注册时的 Context 是一个短生命周期对象(如 Activity),并且该 Receiver 被一个生命周期更长的对象(如 Application、静态变量、单例)持有引用,则必然发生泄漏。反之,如果 Receiver 与 Context 生命周期一致且及时解绑,则安全。
BroadcastReceiver 泄漏根因故障树 BroadcastReceiver 泄漏 未解绑 onPause/onDestroy 未调用 unregisterReceiver 静态内部类隐式持有 静态内部类 + 成员变量 隐式引用外部Activity 匿名内部类 new BroadcastReceiver() {...} 持有外部Activity引用 单例持有Context 单例对象持有Activity Context 内存无法释放

4.1 未解绑(Missing unregisterReceiver)

阶段操作要点
现象识别Activity 退出后,BroadcastReceiver 仍能收到广播;或反复进入退出界面后发生 OutOfMemoryError。
数据采集通过 adb shell dumpsys meminfo [package] 观察 Activity 实例计数;使用 MAT / Profile 查看 BroadcastReceiver 对象引用路径。
原因定位检查 onPause() / onDestroy() 中是否调用 unregisterReceiver();生命周期配对规则:一次 registerReceiver 必须对应一次 unregisterReceiver。
修复动作在 onPause() 中执行 unregisterReceiver(mReceiver),建议使用 ContextWrapper 的 unregisterReceiver 方法。
复测快速进出 Activity 5~10 次,使用 dumpsys meminfo 确认 Activity 实例数不增长,且 Receiver 对象无泄漏。
预防使用基类 BaseActivity 统一处理注册/解绑;或采用生命周期感知组件(如 LifecycleObserver)自动解绑。
典型错误代码: 在 onResume() 注册,但 onPause() 中漏掉 unregisterReceiver;或异常路径未解绑(如 onSaveInstanceState 后未恢复解绑)。
// ❌ 错误示例:注册后没有对应的解绑
@Override
protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    registerReceiver(mBatteryReceiver, filter);
    // 漏掉了 onPause 中的 unregisterReceiver
}

// ✅ 正确示例:onPause 中解绑
@Override
protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    registerReceiver(mBatteryReceiver, filter);
}

@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mBatteryReceiver);
}

4.2 静态内部类隐式持有

当 BroadcastReceiver 被定义为 static inner class 时,它本身不持有外部 Activity 引用。但若该静态内部类中使用了外部类的成员变量(如 TextView、View),则会隐式地持有外部类的 this 引用,导致 Activity 无法 GC。

阶段操作要点
现象识别Memory Analyzer 中看到 Activity 对象存在一条引用链:StaticInnerClassReceiver → Activity.this。
数据采集使用 Eclipse MAT 的 “Path to GC Roots” 功能,排除弱引用后查看引用链;或 Android Studio Profiler 的堆转储分析。
原因定位静态内部类中凡是访问外部类的 非静态 字段或方法,都会生成一个 this$0 字段引用外部实例。
修复动作将静态内部类改为 非静态内部类 并使用弱引用 WeakReference<Activity>;或改为顶层类通过构造函数传入 Context。
复测重复打开/关闭 Activity,用 adb shell dumpsys meminfo 观察 Activity 实例数保持为零或 1(合法状态)。
预防坚持静态内部类不访问外部类实例成员;若必须访问,使用 WeakReference 或 Application Context。
// ❌ 错误:静态内部类访问了外部 Activity 的字段(形成隐式持有)
public class MainActivity extends Activity {
    private TextView mStatusText;

    static class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 实际持有 MainActivity.this
            mStatusText.setText("Received");
        }
    }
}

// ✅ 修复:使用 WeakReference 解除强引用
public class MainActivity extends Activity {
    private TextView mStatusText;

    static class MyReceiver extends BroadcastReceiver {
        private WeakReference textRef;
        MyReceiver(TextView tv) { textRef = new WeakReference<>(tv); }

        @Override
        public void onReceive(Context context, Intent intent) {
            TextView tv = textRef.get();
            if (tv != null) tv.setText("Received");
        }
    }
}

4.3 匿名内部类

在 registerReceiver 中直接 new BroadcastReceiver() {...} 创建的匿名内部类会隐式地持有外部 Activity 的引用。如果该 Receiver 被注册为 静态变量 或 持久化的成员变量,则会导致泄漏。

阶段操作要点
现象识别Profiler 中看到 anonymous class $0 持有 Activity;并且该 Receiver 在 Activity 销毁后仍被全局引用链可达。
数据采集使用 adb shell dumpsys activity broadcasts 查看已注册的 Receiver 列表;MAT 查找 $AppBroadcastManager 中的引用。
原因定位匿名内部类实例被赋值给成员变量或静态变量,且未在 onPause 中解绑,导致 Activity 引用链保持。
修复动作将匿名内部类提取为 非静态内部类 或 顶层类,并确保生命周期管理正确;避免将 Receiver 实例存入静态集合。
复测编写单元测试模拟 Activity 重建,检查堆转储中 Activity 对象数,确保无泄漏。
预防禁止在 Application 或 Activity 中以匿名类形式注册动态 Receiver,除非明确它不会逃逸。
// ❌ 错误:匿名内部类 + 静态变量持有导致泄漏
public class MainActivity extends Activity {
    private static BroadcastReceiver sReceiver; // 静态变量长生命周期

    @Override
    protected void onResume() {
        super.onResume();
        sReceiver = new BroadcastReceiver() {  // 匿名内部类持有 MainActivity.this
            @Override
            public void onReceive(Context context, Intent intent) {
                // do something
            }
        };
        registerReceiver(sReceiver, new IntentFilter("com.example.CUSTOM"));
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 如果有解绑,但 sReceiver 仍被静态变量持有
        unregisterReceiver(sReceiver);
    }
    // 即使解绑,静态变量仍引用了这个匿名 Receiver 对象,阻止其回收
}

// ✅ 修复:避免静态变量;或使用非匿名类并在 onDestroy 中置 null
public class MainActivity extends Activity {
    private BroadcastReceiver mReceiver;

    @Override
    protected void onResume() {
        super.onResume();
        mReceiver = new MyReceiver();
        registerReceiver(mReceiver, new IntentFilter("com.example.CUSTOM"));
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(mReceiver);
        mReceiver = null; // 帮助 GC
    }

    private static class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) { }
    }
}

4.4 单例持有 Context

单例模式中持有 Activity 类型的 Context,会使 Activity 及其关联的 BroadcastReceiver 永远无法被回收。这是最常见且影响最严重的泄漏类型。

阶段操作要点
现象识别App 内存持续上涨,退出所有 Activity 后仍有大量 Activity 实例残留;dumpsys meminfo 中 Activity 数量异常。
数据采集使用 MAT 的 “Biggest Objects” 查看单例对象;检查 Context 字段的引用路径。
原因定位单例类的 static 字段保存了 Activity.this 或 getApplicationContext() 以外的非应用级 Context。
修复动作单例中只持有 Application 类型的 Context(context.getApplicationContext()),或使用 WeakReference 包装。
复测反复进出 Activity,触发 GC 后使用 adb shell dumpsys meminfo 确认 Activity 对象数归零。
预防代码审查中禁止单例类直接存储 Activity 或 Service 类型的 Context;推荐使用 Application Context 简化生命周期。
// ❌ 错误:单例持有 Activity Context
public class NetworkManager {
    private static NetworkManager sInstance;
    private Context mContext;

    private NetworkManager(Context context) {
        mContext = context; // 如果传入的是 Activity,则 Activity 泄漏
    }

    public static NetworkManager getInstance(Context context) {
        if (sInstance == null) sInstance = new NetworkManager(context);
        return sInstance;
    }

    public void registerNetworkReceiver() {
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        mContext.registerReceiver(new NetworkReceiver(), filter);
    }
}

// ✅ 修复:单例使用 Application Context
public class NetworkManager {
    private static NetworkManager sInstance;
    private Context mAppContext;

    private NetworkManager(Context context) {
        mAppContext = context.getApplicationContext(); // 安全
    }

    // ...
}

四类泄漏根因对比速查表

根因持有方式典型特征最简修复
未解绑Activity 本身持有 Receiver缺少配对 unregisterReceiver在 onPause 中解绑
静态内部类隐式持有静态类中的 this$0 指向外部实例静态内部类访问了外部类成员使用 WeakReference 或改为非静态
匿名内部类匿名类实例 + 长生命周期持有者注册到静态变量/全局集合中提取为命名内部类并避免静态
单例持有 Context单例对象的字段单例存储了 Activity 实例替换为 Application Context
排查捷径: 一旦在 MAT 中看到 Context 的引用链指向 AppBroadcastManager.mReceivers,优先怀疑 未解绑;如果引用链指向 static 字段,则检查 单例 或 静态内部类;如果匿名类出现在堆中且无解绑,则定位 匿名内部类。