Android内存泄露之Handler

对于一个初级程序员来说,内存泄露这种东西压根想都没想过,以前也总听一些大牛再说,可能是现在手机的内存都比较大,所以还没什么深刻的体会,但是作为一个负责的程序员,现在还是有必要了解和预防一下了。

本节说的是Handler引发的内存泄漏,这种场景也是很多的,比如需要去网上获取一张图片,然后显示在ImageView上面,然后还没拿到的时候就退出了Activity了,这个时候系统就不会去回收Activity了,直接导致内存泄漏。

public class TestActivity extends Activity{
    ImageView mImageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mImageView=(ImageView) findViewById(R.id.end);
        //模拟网络请求60s
        handler.sendEmptyMessageDelayed(0, 60000);
        //立即关闭Activity(模拟退出,此刻网络请求还在跑)
        finish();
    }
    Handler handler=new Handler(){
        public void handleMessage(android.os.Message msg) {
            mImageView.setImageResource(R.drawable.ic_launcher);
            Toast.makeText(TestActivity.this, "dddd", 0).show();
        };
    };
}

IDE会报一个黄色的错误。

Android内存泄露之Handler_第1张图片
image.png

This Handler class should be static or leaks might occur

因为:
在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
如果外部类是Activity,则会引起Activity泄露 。

当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

Handler 的生命周期与Activity 不一致
当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。

上面代码可以发现,ImageView持有一个Activity的引用,Toast持有一个,而Handler跟Activity关联又持有一个引用,因此这样很容易就内存泄漏了,

内存泄露的危害

只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,如果这个Activity干的活比较多的话(也就是操作的东西比较多),那么不经意间就jj了。

既然让我们声明为静态的,那我们就改改代码:

static Handler handler=new Handler(){
        public void handleMessage(android.os.Message msg) {
            mImageView.setImageResource(R.drawable.ic_launcher);
            Toast.makeText(TestActivity.this, "dddd", 0).show();
        };
    };

顿时有尴尬了:

在静态类中不能引用非静态的变量,

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

好了,我们再改改代码:

 public class TestActivity extends Activity{
    ImageView mImageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mImageView=(ImageView) findViewById(R.id.end);
        handler.sendEmptyMessageDelayed(0, 60000);
        finish();
    }
    final MyHandler handler=new MyHandler(this);
    static class MyHandler extends Handler{
        private final WeakReference mActivity;  
        public MyHandler(TestActivity activity) {
            mActivity=new WeakReference(activity);
        }
        public void handleMessage(android.os.Message msg) {
            TestActivity activity=mActivity.get();
            if(activity!=null){
                activity.mImageView.setImageResource(R.drawable.ic_launcher);
                Toast.makeText(activity, "dddd", 0).show();
            }
        };
    };
}

这样总算完美了?????

当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,还是会触发回调。

那到底怎样才算完美呢?
看看Handler的api中我们发现有这么几个方法:


Android内存泄露之Handler_第2张图片
image.png

所以我们在onDestory方法的时候,移除所有的Handler回调跟Message。

@Override
    protected void onDestroy() {
        if(handler!=null){
            handler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }

到此才勉强算是完美了,所以如果想要当一个好的程序员要考虑的东西还不是那么简单的,就一个看似简单的Handler

你可能感兴趣的:(Android内存泄露之Handler)