Android Handler内存泄露

前言

由于Android采取了单线程UI模型,开发者无法在子线程中更新UI,为此Android为我们提供了Handler这个工具,帮助开发者切换到主线程更新UI。在我们开发Android应用程序进行异步操作时,我们经常会使用到Handler类。通常会写出如下代码
 private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg){
            //do something
        }
 }
其实上面的代码是会产生内存泄漏的,如果你有使用Android lint工具的话,它会给我们提示一个警告
In Android, Handler classes should be static or leaks might occur
翻译过来就是:在android中,Handler这个类应该被定义成静态的,否则可能出现内存泄漏的情况
先说下什么是内存泄漏:
内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。


发生内存泄漏的原因

说是内存泄漏,那到底如何发生内存泄漏的呢?又在哪里发生的内存泄漏?接下来我们探究一下到底是如何发生内存泄漏的。
1.当一个Android应用程序启动的时候,frameworks会自动为这个应用程序在主线程创建一个Looper对象。这个被创建的Looper对象它的主要的工作就是不断地处理消息队列中的消息对象。在Android应用程序中,所有主要的框架事件(例如Activity的生命周期方法,按钮的点击事件等等)都包含在消息对象里面,然后被添加到Looper要处理的消息队列中,主线程的Looper一直存在于整个应用程序的生命周期中。
2.当一个Handler在主线程中被初始化。那它就一直都和Looper的消息队列相关联着。当消息被发送到Looper关联的消息队列的时候,会持有一个Handler的引用,以便于当Looper处理消息的时候,框架可以调用Handler的handleMessage(Message msg)。
3.在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。静态内部类则不会持有外部类的引用。
上面的代码确实是很难以发现内存泄漏的问题的。那我们来看看下面的代码,会更加容易发现问题。

public class MainActivity extends AppCompatActivity {

    private Handler mLeakHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //发送延时消息
        mLeakHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1000 *60 *10);
        finish();
    }
}

解决办法

要解决这样的一个问题,有如下两种方式:
方法一:通过程序
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler方法:removeCallbacks(Runnable r)和removeMessages(int what),在页面销毁时把消息对象从消息队列移除就行了

代码如下:

@Override
public void onDestroy() {
   // 移除所有消息
   handler.removeCallbacksAndMessages(null);
   // 或者移除单条消息
   handler.removeMessages(what);
}

方法二:将Handler声明为静态类
静态类不持有外部类的对象,所以你的Activity可以随意被GC回收。代码如下:

static class NoLeakHander extends Handler {
    @Override
    public void handleMessage(Message msg) {
        
    }
}
以上代码中Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以就没法在Hander中操作UI了,你需要在Handler中增加一个对Activity的弱引用(WeakReference):

首先理解一下相关概念:
强引用(Strong Reference):默认引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。在内存空 间不足时, Java虚拟机宁愿
抛出OutOfMemory的错误,使程序异常终止,也不会强引用的对象来解决内存不足问题。
软引用(SoftReference):如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
弱引用(WeakReference):在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用(PhantomReference):如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

加入弱引用后的代码:
public class MainActivity extends AppCompatActivity {
    private NoLeakHandler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new NoLeakHandler(this);
        Message message = Message.obtain();
        mHandler.sendMessageDelayed(message, 10 * 60 * 1000);
    }


    private static class NoLeakHandler extends Handler {
        //持有弱引用MainActivity,GC回收时会被回收掉.
        private WeakReference<MainActivity> mActivity;
        public NoLeakHandler(MainActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

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

猜你喜欢

转载自blog.csdn.net/l707941510/article/details/80641444