引用链分析
假如 Activity 正在执行一个耗时任务 ,(比如handler.sendMessageDelayed(..., 10000),延迟10秒才执行),然后用户按返回键退出了Activity。此时GC要回收Activity,但被这条链卡住了:
➡️ Looper(主线程Looper)
主线程 的 ThreadLocal 强引用了 Looper(主线程初始化时初始化Looper时被引用),而主线程生命周期跟APP进程一样长,属于GC Root。
➡️ MessageQueue(消息队列)
Looper持有一个全局的消息队列,只要APP不杀,它就在。
➡️ Message(延迟10秒的那个消息对象)
这个Message还在队列里排队等待执行,没被取走。
➡️ msg.target(指向Handler,具体发送消息的那个 Handler)
Message对象内部有一个 target 字段,指向发送它的Handler。这是Android源码机制决定的。
➡️ this(指向MainActivity)
关键点来了!因为你的Handler是非静态内部类,编译器会偷偷给它加一个成员变量,叫 this$0,专门指向外部的MainActivity实例。(隐式持有)
解决方案
错误写法 (非静态内部类 ------ 会泄漏)
java
public class MainActivity extends Activity {
// ❌ 非静态匿名内部类,隐式持有外部MainActivity的引用
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
updateUI(); // 如果耗时任务未完成,Activity无法被回收
}
};
}
正确写法
java
public class MainActivity extends Activity {
// 1. 静态内部类,切断对外部类的强引用
private static class SafeHandler extends Handler {
// 2. 使用弱引用持有外部类,不阻碍GC回收
private final WeakReference<MainActivity> mActivityRef;
SafeHandler(MainActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
// 3. 使用时必须判空!如果Activity已被回收,直接return
MainActivity activity = mActivityRef.get();
if (activity == null) {
return;
}
// 安全调用外部类方法
activity.updateUI();
}
}
private final SafeHandler handler = new SafeHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
// 4. (加分项)移除所有未处理的消息,彻底阻断泄漏风险
handler.removeCallbacksAndMessages(null);
}
private void updateUI() {
// 更新界面逻辑
}
}
关键在于:
- 使用 静态内部类声明
Handler切断隐式引用- 要调用 外部类Activity 的方法,使用 弱引用的方式
允许 GC 内存紧张时回收,必要时获取外部实例,但是每次使用前需要get()并且判空- 生命周期同步清理
"弱引用只解决了'内存回收'问题,但解决不了'任务残留'问题。如果 Activity 被回收后,延迟消息还在队列里,等它执行时 get() 为 null,业务逻辑就白白浪费了。所以 remove 是为了主动取消无用任务,彻底斩断 Message 对 Handler 的持有,形成完美闭环。"