第2章:Handler与Activity生命周期绑定链

现象识别

泄漏表象:Activity退出后,Activity.onDestroy()正常执行,但该Activity实例仍被Handler和MessageQueue引用,导致GC无法回收。常见表现为:

  • 多旋回泄漏:反复旋转屏幕或跳转退出Activity,Heap中Activity实例数量持续增长。
  • 延迟Message滞留:Handler发送了sendMessageDelayed()(如延迟10秒、30秒),在延迟期间退出Activity,Message仍在队列中,导致泄漏。
  • 隐式this泄漏:内部匿名Handler类持有外部Activity的隐式引用(this$0),链路上无法断开。
判断规则(P0级泄漏):若Activity退出后3分钟,Memory Profiler中仍存在该Activity的强引用链,且根引用指向android.os.Handler或android.os.MessageQueue,则判定为Handler生命周期绑定链泄漏。

数据采集

通过Android Memory Profiler + Dump HPROF采集以下关键数据:

  1. 抓取Heap快照,筛选Activity子类实例,确认数量与期望值(应当只有1个)不符。
  2. 对泄漏的Activity实例执行“到GC根的引用链”分析(Path to GC Roots → exclude weak references)。
  3. 记录引用链路中的关键节点:MessageQueue → Message → Handler → Activity。
// 典型泄漏日志(ART GC 日志)
D/dalvikvm: GC_CONCURRENT freed 204K, 3% free 12348K/12640K, paused 2ms+2ms, total 20ms
// 无异常,但Heap中Activity实例数异常增多
// Dump HPROF 后可看到:
// GC Root: Thread (main)
//   → MessageQueue (mMessages)
//     → Message (target)
//       → Handler (mCallback = null)
//         → MainActivity (this$0)  // 隐式引用
采集清单:
  • 泄漏Activity的完整引用链长度(通常≥5级)
  • 延迟Message的when时间戳(判断是否未过期)
  • Handler发送方式:sendMessage / postDelayed / sendEmptyMessageDelayed

原因定位

引用链的完整路径为:

  • MessageQueue.mMessages(主线程Looper队列)→ Message(持有target字段指向Handler)
  • Handler(内部匿名类)→ 隐式this$0(指向Activity实例)
  • Activity(持有大量View、Bitmap等资源)

根本原因:非静态内部类或匿名Handler类自动持有外部Activity的引用;当Message尚未被处理时(尤其是延迟消息),该引用链无法切断,导致Activity无法被GC回收。

常见误区:即使没有延迟消息,若Handler在子线程中执行handleMessage()且子线程未终止,Message可能永远不被处理,造成永久泄漏。
Handler与Activity生命周期绑定链故障树 Activity 无法回收 Handler 持有 Activity MessageQueue 滞留 隐式 this 引用 匿名 Handler 类 sendMessageDelayed Message.target 强引用 Looper 线程未结束 最终后果:Activity 无法回收 → OOM 风险 尤其当 Activity 持有大量 View / Bitmap / 硬件资源时

修复动作

标准修复策略包含以下四种,按优先级排序:

方案操作适用场景
静态Handler + 弱引用声明Handler为static class MyHandler extends Handler,内部使用WeakReference<Activity>访问外部所有需访问Activity的Handler
Activity销毁时移除消息在onDestroy()中调用handler.removeCallbacksAndMessages(null)延迟消息或重复发送的场景
Lifecycle感知绑定使用LiveData或LifecycleObserver自动取消HandlerAndroidX项目、MVVM架构
替换为协程/Flow用viewModelScope.launch或lifecycleScope替代HandlerKotlin项目、新开发
// 修复示例:静态Handler + 弱引用
public class MainActivity extends AppCompatActivity {
    private static class SafeHandler extends Handler {
        private final WeakReference activityRef;

        SafeHandler(MainActivity activity) {
            activityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityRef.get();
            if (activity != null && !activity.isFinishing()) {
                // 安全操作
            }
        }
    }

    private SafeHandler mHandler = new SafeHandler(this);

    @Override
    protected void onDestroy() {
        mHandler.removeCallbacksAndMessages(null); // 额外保险
        super.onDestroy();
    }
}

复测

修复后进行泄漏复现还原测试:

  1. 启动Activity → 发送延迟30秒的Message → 立即退出Activity(旋转屏幕或返回)。
  2. 等待1分钟后,抓取Heap快照,统计Activity实例数(预期剩余0个)。
  3. 使用adb shell dumpsys meminfo package_name检查ActivityCount。
通过标准:Activity退出后3次GC(Runtime.getRuntime().gc())后,Heap中该Activity实例引用链长度为0,Memory Profiler无android.os.Handler强引用指向该Activity。

预防

  • 编码规范:禁止使用匿名Handler或非静态内部Handler;若必须使用,声明为static并配合WeakReference。
  • 静态分析工具:集成Lint规则HandlerLeak(默认开启)或SpotBugs的DM_HANDLER_LEAK。
  • 架构约束:使用Lifecycle-Aware组件(如LiveData、lifecycleScope)替代原始Handler。
  • Review清单:代码审查时检查所有sendMessageDelayed的调用是否有对应的removeCallbacks。
// 预防Lint配置(build.gradle)
android {
    lintOptions {
        disable 'HandlerLeak'  // 强烈不建议禁用!应修复而非关闭
    }
}