• 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等组件。可能引起泄漏的操作是这种格式的代码:

    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

  • 相关阅读:
    PowerDesigner使用教程(一)
    为自己的博客园增加打赏功能[只需五步]
    [Python自学] DRF (4) (权限组件和频率组件)
    [Python自学] DRF (3) (认证组件)
    [Python自学] DRF (2) (视图类的封装)
    [踩坑] Django "OverflowError: Python int too large to convert to C long" 错误
    [Python自学] DRF (1)
    [Python自学] 设计模式之单例模式
    [Python自学] Django的admin组件
    [Python自学] Django的认证系统
  • 原文地址:https://www.cnblogs.com/chitanta/p/9675139.html
Copyright © 2020-2023  润新知