Handler内存泄漏问题解决方案(Android,第一行代码,This Handler class should be static or leaks might occur)

1 问题背景

博主最近在复习《第一行代码》的第10.2.2章节——在子线程中更新UI,书中给出的在UI主线程中用匿名内部类实现Handler的写法如下:

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case UPDATE_TEXT:
                //在这里可以进行UI操作
                mTermsView.setText("Nice to meet you");
                break;
            default:
                break;
        }
    }
};

如果你不熟悉Java的匿名内部类,可参考这篇文章Java中内部类详解—匿名内部类,否则你将无法理解为什会出现内存泄漏。

但是细心的小伙伴们肯定都注意到了Android Studio对这段代码标黄警告,并给出了提示,其截图如下:
在这里插入图片描述
其警告和提示内容如下:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)
Inspection info:Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object. Issue id: HandlerLeak

把这段警告和提示翻译过来如下:

这个Handler类应该是static静态的,否则可能会发生内存泄漏。


检查信息:由于这个Handler被声明为一个内部类,因此它会阻止外部类被垃圾回收。如果Handler对主线程以外的线程使用Looper或MessageQueue,那么就没有问题。


如果处理程序正在使用主线程的Looper或MessageQueue,你需要修正你的Handler声明,修正步骤如下:
1.声明Handler为static静态类;
2.在外部类中,实例化一个WeakReference到外部类,并在实例化处理程序时将该对象传递给处理程序;
3.使用WeakReference对象对外部类的成员进行所有引用。


问题ID:HandlerLeak。

2 问题体现

根据上述的Android Studio的警告和提示,我们按照上述的代码来定义Handler会导致内存泄漏,但是我们都有疑问:什么是内存泄漏,内存泄漏是什么样子,它会导致什么问题?

其中百度百科中对内存泄漏的定义如下:

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

首先我们运行上述会导致内存泄漏的代码的APP,再在Android Studio的Profiler性能监测工具中开启该APP的监测,然后我们在会导致内存泄漏的Handler所在的Activity不停地做横竖屏切换,我们可以观察到如下的APP内存监测结果:
在这里插入图片描述
根据上图,我们可明显观察到,在手机不停地横竖屏切换时,APP的内存占用明显地升高。

3 问题分析和理解

在上述问题中,为什么横竖屏切换会导致APP内存占用大小不断地升高呢?博主我先给出原因:

  1. 手机横竖屏切换会销毁并重建当前显示的Activity;
  2. 由于上面的 Handler 匿名内部类定义在UI主线程中,因此使用了UI主线程的 Looper 和 MessageQueue;
  3. MessageQueue 中的 Message 会持有 Handler 对象;
  4. 而Handler匿名内部类对象又持有着外部 Activity 的强引用;

以上四点导致当有 Message 未被处理之前, 外部类 Activity 会一直被强引用,这就导致Activity即使被销毁,也无法被垃圾收集器回收。而这些无法被回收的Activity会一直占用着内存,从而导致APP内存占用大小不断升高。最重要的是,这些占用着内存的Activity已经被销毁了,也就不能提供服务了,却还占用着内存而不释放给其他在提供服务的进程或组件,这不是程序员和用户想看到的。而这就是内存泄漏的具体表现。

如果你不太理解为什么MessageQueue 中的 Message 会持有 Handler 对象,以及为什么Handler匿名内部类对象又持有着外部 Activity 的强引用,你可能需要阅读以下文章:
[1]Android-消息机制
[2]Java:强引用,软引用,弱引用和虚引用

4 问题解决

现在我们已经知道上述代码导致内存泄漏的原因和表现了,那么我们怎么解决这个问题呢?让我们再次回顾以下Android Studio给出的建议和警告:

如果处理程序正在使用主线程的Looper或MessageQueue,你需要修正你的Handler声明,修正步骤如下:
1.声明Handler为static静态类;
2.在外部类中,实例化一个WeakReference到外部类,并在实例化处理程序时将该对象传递给处理程序;
3.使用WeakReference对象对外部类的成员进行所有引用。

按照上述建议,我们可以将代码优化成如下所示:

static class MyHandler extends Handler {
    //注意下面的LoginActivity类是MyHandler类所在的外部类,即所在的Activity
    WeakReference<LoginActivity> loginActivityWeakReference;

    MyHandler(LoginActivity loginActivity) {
        loginActivityWeakReference = new WeakReference<>(loginActivity);
    }

    @Override
    public void handleMessage(Message msg) {
        LoginActivity loginActivity = loginActivityWeakReference.get();
        switch (msg.what) {
            case UPDATE_TEXT:
                loginActivity.mTermsView.setText("Terms was changed");
                break;
            default:
                break;
        }
    }
}

//实例化一个MyHandler对象
private Handler handler = new MyHandler(this);

在上述代码中,我们采用静态内部类的方式定义了Handler,而静态内部类默认不会持有外部类的引用,接着我们通过WeakReference实现Handler持有外部类Activity的弱引用,这样Handler既能访问外部类Activity的成员,又不影响Activity被垃圾回收期回收。

我们再在Android Studio中观察下这段新编写的代码,发现它再也没有被标黄警告了,因此我们便修复了Handler内存泄漏的问题。

本文参考文献:
[1] [smali] This Handler class should be static or leaks might occur
[2]Android“This Handler class should be static or leaks might occur”警告的处理方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐李同学(李俊德-大连理工大学)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值