【内存泄露】由Handler引发的内存泄漏的思考

前面Looper与Handler解析提到过下面这段代码会出现内存泄漏,其实在Android Studio中会提示这个问题:
这里写图片描述
在编辑器里面会自动将这段代码标出来,我们看看提示信息是什么?
This Handler class should be static or leaks might occur (null) less… (Ctrl+F1)
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.

大致意思就是应该让Handler类为静态的,不然就会产生内存泄漏。
原因也说的很清楚,Handler被声明为一个非静态内部类或者匿名类可能会阻止外部类的垃圾回收,处理方法就是把Handler声明为静态内部类,当实例话Hanlder的时候把外部类对象传递给handler,并且在handler里面把外部类对象实例化成一个弱引用;把外部类成员变量的引用都使用弱引用。
下面分三方面来进行分析:

一、什么是java的内存泄漏
Java内存泄漏指的是进程中某些对象已经没有使用价值并且想将其释放回收掉,但是它们却可以直接或间接地被其他对象强引用,导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏。

二、为什么我们上面的代码会产生内存泄漏
1、在程序启动的时候就在主线程中创建了一个Looper 对象,它内部维护着一个消息队列,并且一条一条的对消息进行处理。
2、当我们发送消息的时候,在Message的target里面存放着发送该消息的handler对象,即消息里面包含了一个Handler实例的引用,并且就是通过这个handler实例来回调handleMessage方法进行处理。
如果对上面两点不明白的话,可以看看Looper与Handler解析
3、在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。
理解了上面的三点,应该就差不多清楚了为什么上面的代码会出现内存泄漏。
Handler通过发送Message与其他线程交互,Message发出之后是存储在目标线程的MessageQueue中的,而有时候Message 也不是马上就被处理的,可能会驻留比较久的时间。在Message类中存在一个成员变量 target,它强引用了handler实例,如果Message在Queue中一直存在,就会导致handler实例无法被回收,如果handler对 应的类是非静态内部类或者匿名类 ,它持有外部Activity的引用,则当使用finish销毁Activity的时候,会出现外部类实例Activity并不会被回收,这就造成了外部类实例的泄露。
现在应该清楚了吧,所以从中我们可以知道一点:如果内部类的存在的时间比Activity的生命周期长的时候,应该避免在Activity中使用非静态的内部类,因为内部类持有外部类的引用,如果持有的时间要长于外部类实例,当外部类结束销毁的时候,它并不能被回收掉,因为它还被内部类持有,这样就会导致内存泄漏。

三、如何解决这个问题?
也很简单,上面说到要避免使用非静态的内部类,那我们就使用静态的内部类,或者把内部类单独写个文件,让它成为一个单独的类。另外,我们可以在里面增加一个成员变量来弱引用外部类实例,就可以调用外部类的方法。
第一种改进方法:使用静态内部类

public class MainActivity extends Activity {
    //定义一个静态的内部类
    static private class MyHandler extends Handler{
        //定义一个外部类的弱引用
        private WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity){
            mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            super.handleMessage(msg);
        }
    }
    //Runnable也要定义为静态的,因为它如果定义为匿名内部类的话同样也持有外部类的引用
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() { /* ... */ }
    };


    private final MyHandler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Thread(sRunnable).start();

    }
}

第二种改进方法:单独定义一个类

public class MyHandler extends Handler {
    private WeakReference<MainActivity> mActivity;

    public MyHandler(MainActivity activity){
        mActivity = new WeakReference<MainActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        MainActivity activity = mActivity.get();
        //进行一些操作
        super.handleMessage(msg);
    }
}
public class MainActivity extends Activity {

    private final MyHandler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.sendEmptyMessage(0);

    }
}

在前面的HandlerThread与IntentService原理解析 中,我也讲到过HandlerThread的用法,前面的代码都没有考虑内存泄漏的问题,做了一些改动后如下:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        HandlerThread mThread = new HandlerThread("demo");
        mThread.start();
        MyHandler mHandler = new MyHandler(mThread.getLooper());
        mHandler.sendEmptyMessage(0);
    }
}
public class MyHandler extends Handler {

    public MyHandler(Looper looper){
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
}

这里面仍然可能存在内存泄漏的问题,因为HandlerThread实现的run方法是一个无限循环,它不会自己结束,线程的生命周期超过了activity生命周期,当横竖屏切换,HandlerThread线程的数量会随着activity重建次数的增加而增加。应该在onDestroy时将线程停止掉:mThread.getLooper().quit();
另外,对于不是HandlerThread的线程,也应该确保activity消耗后,线程已经终止,可以这样做:在onDestroy时调用mThread.join();

参考文献:
Android内存泄漏分析及调试
Android中Handler引起的内存泄露

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值