android handler的警告Handler Class Should be Static or Leaks Occur

在使用Handler更新UI的时候,我是这样写的:


    public class SampleActivity extends Activity {

      private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          // TODO
        }
      }
    }

看起来很正常的,但是 Android Lint 却给出了警告:
This Handler class should be static or leaks might occur
意思是说:这个Handler 必须是static的,否则就会引发内存泄露。
其实,对于这个问题,Android Framework 的工程师 Romain Guy 早已经在Google论坛上做出过解释,并且给出了他的建议写法

他的建议写法是:


    class OuterClass {

      class InnerClass {
        private final WeakReference mTarget;

        InnerClass(OuterClass target) {
               mTarget = new WeakReference(target);
        }

        void doSomething() {
               OuterClass target = mTarget.get();
               if (target != null) {
                    target.do();    
               }
         }
    }

下面,我们进一步解释一下:

  1. Android App启动的时候,Android FrameWork为主线程创建一个Looper对象,这个Looper对象将贯穿整个Application的生命周期,他实现了一个消息队列(Message Queue),并且开启一个循环来处理Message对象。而FrameWork的事件都包含着内部Message对象,当这些事件被触发的时候,Message对象会被加到消息队列中执行。
  2. 当一个Handler被实例化时,他将和主线程Looper对象的消息队列相关联,被推到消息队列中的Message对象将持有一个Handler的引用以便于当Looper处理到这个Message的时候,FrameWork执行handleMessage(Message)方法
  3. 在Java中,非静态内部类持有一个对外部类的隐式引用,而静态内部类不会。

到底内存泄露是在哪里发生的呢?以下面代码为例:


        public class SampleActivity extends Activity {

   private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          // ...
        }
      }

      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mLeakyHandler.postDelayed(new Runnable() {
          @Override
          public void run() { }
        }, 60 * 10 * 1000);

        // Go back to the previous Activity.
        finish();
      }
    }

当Activity被finish()掉,Messaage将存在于消息队列中长达10分钟的时间才会被执行到,这个Mesasge持有一个对handler的引用,Handler也会持有一个对外部类SampleActivity 的隐式引用,这些引用在Message被执行之前一直保持,这样会保证Activity的上下文不被垃圾回收机制回收,同时也会泄露应用程序的资源(Views and resources)。

为解决这个问题,下面这段代码中的Handler则是一个静态匿名内部类。静态匿名内部类不会持有一个对外部类的隐式引用,因此Activity将不会被泄露。如果你需要在Handler中调用外部Activity的方法,就让Handler持有一个对Activity的WeakReference,这样就不会泄露Activity的上下文了,如下所示:


    public class SampleActivity extends Activity {

      /**
       * Instances of static inner classes do not hold an implicit
       * reference to their outer class.
       */
      private static class MyHandler extends Handler {
        private final WeakReference mActivity;

        public MyHandler(SampleActivity activity) {
          mActivity = new WeakReference(activity);
        }

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

      private final MyHandler mHandler = new MyHandler(this);

      /**
       * Instances of anonymous classes do not hold an implicit
       * reference to their outer class when they are "static".
       */
      private static final Runnable sRunnable = new Runnable() {
          @Override
          public void run() { }
      };

      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

        // Go back to the previous Activity.
        finish();
      }
    }

总结
在实际开发中,如果内部类的生命周期和Activity的生命周期不一致(比如上面那种,Activity finish()之后要等10分钟,内部类的实例才会执行),则在Activity中要避免使用非静态的内部类,这种情况就使用一个静态内部类,同时持有一个对外部类的WeakReference.

你可能感兴趣的:(android handler的警告Handler Class Should be Static or Leaks Occur)