App下载 微信公众号

为什么Handler可能导致内存泄露?

简答题 /  作者【吾非言】/ 发布于2022-8-27/ 2.17k次浏览
2022 8/27 5:19

一、匿名内部类或非静态内部类默认持有外部类引用对象。

通常使用Handler会有如下几种方式:

// 1.匿名内部类
Handler handler = new Handler(Looper.getMainLooper()) {
    // 监听处理Message
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};

// 2.非静态内部类
MyHandler myHandler = new MyHandler();
class MyHandler extends Handler {
    // 监听处理Message
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
}

// 3.直接执行匿名内部类或非静态内部类
Handler handler = new Handler();
handler.post(new Runnable() {
    // 持有外部类对象
    @Override
    public void run() {}
});

这几种方式无论哪一种都将导致Handler对象持有外部类对象。

假如在Activity中定义了一个Handler用来执行更新UI操作事件,当Activity关闭/销毁时,Handler也会随之销毁,那么就不会存在内存泄露这种说法。但是当Activity关闭/销毁时,由于Handler持有Activity的引用对象,导致Activity需要进行GC回收时无法进行回收,这个时候就会出现内存泄露。

二、Handler相关引用链

Activity -> Handler -> Message -> MessageQueue -> ThreadLocal -> Thread。

刚刚讲到Handler持有Activity的引用对象导致Activity无法进行GC回收(可达性分析),从而引发内存泄露。那么为什么会导致Activity无法进行回收呢?

当然引发内存泄露还有一个重要条件,就是Handler中有还未执行完的Message。

Message类中有一个Handler target属性,用来指明消费Message消息是哪个Handler,而在以上场景中,这个target变量自然指向的是Activity中自定义的Handler。

我们知道Message消息执行是需要通过Looper不断地轮询MessageQueue,而MessageQueue是用来保存Message的容器,当然MessageQueue也是Looper的属性变量。这样未执行完的Message必然会被MessageQueue所持有,而MessageQueue又被Looper所持有。

而这里的Looper对象实际上是static Looper sMainLooper,该变量是在ActivityThread中被创建的静态变量,静态变量可作为GC Root 对象,所以Looper对象不可能被回收,这就间接导致MessageQueue、Message以及Handler不可能被回收,这便是导致Activity内存泄露的原因。

如果该Looper是开发者自定义的非静态对象,也会导致内存泄漏。
原因是Looper对象中存在ThreadLocal<Looper> sThreadLocal静态变量,该变量使用其静态内部类 ThreadLocalMap 类似键值对的方式保存着Looper对象和Thread对象,而Thread对象也会持有ThreadLocal对象,所以Thread如果不会被回收,那么Looper对象肯定也不会被回收。

public class ThreadLocal<T> {
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    // 创建 Map 并放到了 Thread 中
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

三、如何避免Handler导致的内存泄露?

两种方式可以避免Handler导致的内存泄露:

  1. 采用静态内部类。
    静态内部类不会持有外部类的引用对象。
MyHandler myHandler = new MyHandler(this);

private static class MyHandler extends Handler {
    private WeakReference<Activity> mWeakRef;

    public MyHandler(Activity activity) {
        mWeakRef = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
}
  1. 在Activity销毁时移除所有Message。
onDestroy() {
    handler.removeCallbacksAndMessages(null);
    handler = null;
}
感谢您使用伴职平台,如有侵权,请投诉删除!

全部评价

最新
查看更多评论 加载