Android 内存泄漏之handler

问题引出

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myHandler.sendEmptyMessageDelayed(1,60000);
    }

    Handler myHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    break;
            }
        }
    };
}

这是我们通常使用handler的写法。
然而IDE一般会警告:

This Handler class should be static or leaks might occur (anonymous android.os.Handler) 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 myHandler = new Handler(){  

也就是Handler myHandler = new Handler()这句话存在问题。假设Handler刚刚sendEmptyMessageDelayed,立马关闭Activity,那么就会造成内存泄漏

问题原因
Handler 的生命周期与Activity 不一致
当Android应用启动的时候,会先创建一个UI主线程的Looper对象(即Looper对象的生命周期与Application同步),Looper实现了一个简单的消息队列(MessageQueue),一个一个的处理里面的Message对象(使用死循环遍历处理Message)。主线程Looper对象在整个应用生命周期中存在。

当当前Activity(比如上面的mainActivity)在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
此时就形成了一个关系链:
Application–>Looper–>MessageQueue–>Message–>Handler–>mainActivity
Activity销毁时,想要把mainActivity GC 掉,但是由于Message还没有处理,导致handler仍然保持着mainActivity的引用,handler 引用 Activity 阻止了GC对Acivity的回收

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

补充一:内存泄漏的原因:
Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

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

解决方案:

  • 方法一:通过程序逻辑来进行保护。

1.总的来说(Activity关闭的时候可能存在其他形式的线程仍然引用着Activity)在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了线程r和外部连接的线,Activity自然会在合适的时候被回收。

2.针对Handler的GC如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

代码实现 在Activity销毁时把消息对象从消息队列移除,解除引用。

public void onDestroy() {
    //  If null, all callbacks and messages will be removed.
    mHandler.removeCallbacksAndMessages(null);
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
以上属于代码维护,虽然解决了问题,但是警告依然存在。有代码洁癖的请看第二种方法。

  • 方法二:将Handler声明为静态类。

    分二步

    1 静态类不持有外部类的对象,所以你的Activity可以随意被回收。改成静态类
    2 使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference),另外非静态方法todo不能在静态类调用

第一步: 改静态

public class MainActivity extends AppCompatActivity {
    MyHandler myHandler = new MyHandler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myHandler.sendEmptyMessageDelayed(1, 60000);
    }

    public void todo() {
    }

    static class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            todo();
        }
    }
}

第二步: 添加弱引用

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {
     MyHandler myHandler = new MyHandler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myHandler.sendEmptyMessageDelayed(1, 60000);
    }

    public void todo() {
    }

    static class MyHandler extends Handler {
        WeakReference<MainActivity> mWeakReference;
        public MyHandler(MainActivity activity)
        {
            mWeakReference=new WeakReference<MainActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mWeakReference.get().todo();
        }
    }
}

这样两个问题一下子解决了,而且没有警告

当然保险起见,两种方法同时使用是最保险的。

参考链接(内容都差不多)
http://blog.csdn.net/zhuanglonghai/article/details/38233069
http://blog.csdn.net/jdsjlzx/article/details/51386440
http://www.cnblogs.com/xujian2014/p/5025650.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值