Handler为什么可能会造成内存泄漏

在Android系统中,Handler是一个消息发送和处理机制的核心组件之一,与之配套的其他主要组件还有Looper和Message,MessageQueue。

根据官网的描述

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Handler有两个主要作用:

1.安排调度(scheule)消息和可执行的runnable,可以立即执行,也可以安排在某个将来的时间点执行。

2.让某一个行为(action)在其他线程中执行。

上面翻译的意思也就是Handler主要作为一种消息收发的机制。

这个消息可以是单纯的基本类型,也可以是某个类,或者一个可执行的行为(runnable)。Message和Runnable类是消息的载体。MessageQueue是消息等待的队列。Looper则负责从队列中取消息。

在官网描述中,有一段描述很重要:Each Handler instance is associated with a single thread and that thread's message queue。意思是一个Handler的实例和单个的线程和这个线程的MessageQueue相关联。这得出了一个结论:如果这个MessageQueue中的消息是有某个Handler的instance(实例)的引用的。

关于这一点,其实不难理解:Looper处理消息Message类的时候,需要调用Handler的handleMessage吧,这就需要知道是哪个Handler的实例,才能调用Handler.handleMessge()

现在回到题目的问题上。Handler为什么可能造成内存泄漏。这里的内存泄漏,常常指的是泄漏了Activity等组件。可能引起泄漏的操作是这种格式的代码:

扫描二维码关注公众号,回复: 3345339 查看本文章
public class TestActivity extends Activity{

    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

注意这是上面的代码是有问题的代码,但不一定会引起内存泄漏,只是有可能,泄漏对象的是SampleActivity实例。

这有什么问题呢。问题在于该Handler的实例采用了内部类的写法,它是SampleActivity这个实例的内部类,在Java中,关于内部类有一个特点:在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。所以,该handler实例持有了SampleActivity的一个引用。到这里,是不是有点头绪了呢。

关于内存泄漏,在android中一个通用的说法是:生命周期较短的组件引用了生命周期较长的组件。Handler就是一种典型的示例,以上面的代码举例。SampleActivity可能会被泄漏,也就是该组件没有用了,比如调用了finish()后,垃圾回收器却迟迟没有回收该Activity。原因出在该实例的handler内部类引用了它,而该handler实例可能被MessageQueue引用着。比如发送了一个延时消息到队列中,那么就可能在队列中存在很长时间,而消息队列(MessageQueue)的生命周期等于它所在的线程。当大到Activity被finish()了后还在队列中时,就满足了上面的短生命周期引用长生命周期的条件。根据Java GC的规则,SampleActivity的引用计数不为0,故不会回收,回收的时机在handler发送的消息出队列时。

从上面的说法中,可以思考得到相应的解决方法:

1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。

2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)

3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)

4.将Handler放到一个单独的顶层类文件中。

最好的方法是哪一种呢?其实前三种方法都差不多,第四种如果是一些轻量的操作就太多余了。不过要说通用性,第三种是最为通用的。

如果用第一种,其具体的解决方法是当组件销毁时,在恰当的时机调用handler的removeCallbacksAndMessages(null),如果是在Activity中,则是在onDestroy()的生命周期回调中调用。如果是Activity等具有明确生命周期的组件时可以这么做,但要是在自定义的类中,比如一个单例中,往往不能找好释放的时机。而且开发人员有时会忘记调用remove消息的方法。

如果用第二种,当在handler内部需要调用外部类的非静态方法时就达不到要求了。因为在Java中,静态的内部类中不能调用外部非静态的方法。

第三种,需要一些额外的代码,但方法最为通用。

 public class TestActivity extends Activity {

    private static class MyHandler extends Handler {
    private final WeakReference<TestActivity> mActivity;
    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<TestActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      TestActivity activity = mActivity.get();
      if (activity != null) {
         //do Something
      }
    }
 }

采用哪种方法其实都是可以的,具体看实际情况。

参考资料:

https://developer.android.com/reference/android/os/Handler 官方Reference。

https://blog.csdn.net/lqw_student/article/details/52954837

猜你喜欢

转载自www.cnblogs.com/chitanta/p/9675139.html