Android中Handler使用不当引起内存泄漏分析

237 阅读4分钟

Handler主要用于在Android中实现线程间的通信,特别是用于在子线程中处理数据后更新UI线程。它通过发送消息(Message)到消息队列(MessageQueue)中,然后由Looper不断循环取出消息并分发到相应的Handler进行处理。
Handler通常作为Activity或Fragment的内部类存在,这样它可以方便地访问外部类的成员变量和方法。然而,这也意味着Handler默认持有创建它的外部类(如Activity)的隐式引用。
内部类持有外部类的引用是因为内部类虽然和外部类写在同一个文件中,但是编译后还是会生成不同的class文件,其中内部类的构造函数中会传入外部类的实例,然后就可以通过this$0访问外部类的成员。
引起内存泄漏的原因:
(1) 当Handler发送延迟消息时,如果Activity在消息执行前被销毁(例如用户旋转屏幕导致Activity重建),但消息队列中仍然有待处理的消息,这些消息会持有Activity的引用,阻止其被垃圾回收器回收。
(2) 如果Handler在子线程中工作(如网络请求后更新UI),并且子线程在Activity生命周期结束后仍然运行,那么子线程中的Handler也会持有Activity的引用,导致内存泄漏。
规避内存泄漏的方法:
(1) 在Activity的onDestroy方法中,确保移除Handler的所有待处理消息和Runnable,以避免内存泄漏。
(2) 将Handler定义为静态内部类,并通过弱引用持有Activity的引用。这样,即使Activity被销毁,Handler也不会因为持有Activity的强引用而阻止其被垃圾回收。
(3) 如果Handler不需要访问Activity特有的资源或方法,可以考虑使用ApplicationContext来创建Handler,而不是使用Activity上下文。这样做的好处是ApplicationContext是全局的,不会随着Activity的销毁而销毁。

下面从代码的角度分析: 在一个Activity内部,创建一个Handler的匿名内部类实例。

public class MyActivity extends Activity {
	private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_WHAT:
                    // do something
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
	
	mHandler.sendEmptyMessageDelayed(MSG_WHAT, 1000 * 60 * 5);
}

对于上面的代码分析如下:
由于这个Handler持有其外部类MyActivity的隐式引用(这是Java和Kotlin中匿名内部类的一个特性),因此当消息队列中仍有待处理的消息或回调时,MyActivity实例可能不会被垃圾回收器回收,从而导致内存泄漏。
通过mHandler.sendEmptyMessageDelayed(MSG_WHAT, 1000 * 60 * 5);发送了一条延迟5分钟处理的空消息。若在这5分钟内MyActivity被销毁(例如,用户旋转了屏幕或按下了返回键),且未采取任何措施清除Handler中的消息, 那么MyActivity实例将继续被Handler持有,进而引发内存泄漏。 那么,规避内存泄漏的方法如下,即在Activity的onDestroy方法中调用如下代码:

mHandler.removeCallbacksAndMessages(null);

移除Handler消息队列中的所有消息和回调,从而消除了内存泄漏的隐患。传递null作为参数意味着您将移除该Handler所持有的所有消息和回调,而不仅仅是与特定what值相关联的消息。
另一个解决办法是,将Handler声明为静态的,,并通过弱引用WeakReference的方式持有activity.

private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            Context ctx = reference.get();
            if (!(ctx instanceof MainActivity)) {
                return;
            }
            MainActivity activity = (MainActivity) ctx;
            activity.loadStart();
        }
    }
    
    private final MyHandler myHandler = new MyHandler(this);
	
	myHandler.removeCallbacksAndMessages(null); //在onDestroy中执行

在上面的代码中,使用了WeakReference来避免内存泄漏,并且重写了handleMessage方法来处理接收到的消息。
上面的代码定义了一个私有静态内部类MyHandler,它继承自Handler。由于它是静态的,因此它不会隐式持有外部类的引用,这有助于减少内存泄漏的风险。
定义弱引用变量reference用于持有Context的引用,弱引用允许Context(可能是Activity或其他类型的Context)在不再需要时被垃圾回收器回收。
构造函数接受一个Context对象,并使用这个对象创建一个弱引用。这样,即使MyHandler对象仍然存在,它也不会阻止垃圾回收器回收传入的Context对象。
通过instanceof执行重要的类型检查,可以防止在类型转换时发生ClassCastException异常。

使用kotlin代码如下:

class MyHandler(context : Context) : Handler(Looper.getMainLooper()) {
    private var reference : WeakReference<Context> = WeakReference(context)
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        msg.let {
            val ctx = reference.get()
            if (ctx is MainActivity) {
                (ctx as MainActivity).loadStart()
            }
        }
    }
}
private val myHandler = MyHandler(this)
myHandler.removeCallbacksAndMessages(null)