Android handlerのContextメモリ漏洩


まず次のコードを見てください.
public class LeakActivity extends Activity {

    public Handler leakHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
        }
    };

ここで簡単にhandlerを宣言します.すべて正常に見えますが、この書き方はメモリの漏洩を引き起こす可能性があります.理由はコンパイラが教えてくれたからです.この書き方には警告があります.
AndroidでHandlerはstaticと宣言されるべきです.そうしないとメモリの漏洩の問題が発生する可能性があります.
なぜメモリが漏れる可能性があるのでしょうか?既存の情報を分析します
  • Androidアプリケーションが最初に起動すると、システムはアプリケーションのメインスレッドにLoopオブジェクトを作成し、Loopはメッセージキュー(Message queue)であり、次々と情報を処理する.アプリケーションの主なイベント、たとえばActivityのライフサイクル、クリックイベントなどは、メッセージオブジェクトに含まれます.メッセージはLoopのメッセージキューに並んで処理されるのを待つので、このプライマリスレッドのLoopはアプリケーションのライフサイクル全体にわたって常に
  • 存在する.
  • 現在、私たちのhandlerはメインスレッドで宣言されており、自然にメインスレッドLoopのメッセージキューに関連付けられています.このとき、メッセージキューに伝達するメッセージにはhandlerの参照がある.なぜなら、メッセージが処理されると、システムはHandlerのhandleMessage(Message msg)メソッド
  • を自分で呼び出す必要があるからである.
  • javaでは静的でない内部匿名クラス(つまりHandleを宣言する方法)は、静的な内部クラスではできないが、彼の外部クラスの暗黙的なアプリケーションを持っている.

  • 以上の点に基づいてこの例を修正する
    public class LeakActivity extends Activity {
    
        public Handler leakHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                ...
            }
        };
    
        protected void onCreate(android.os.Bundle savedInstanceState) {
            setContentView(R.layout.activity_leak);
            //10         
            leakHandler.postDelayed(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
    
                }
            }, 1000 * 60 * 10);
            //      Activity
            finish();
        };

    宣言されたhandlerは10分遅延して情報を送信し、finish()は現在のActivityを削除し、上記の2点目に従って10分遅延して送信された情報は、メインスレッドのメッセージキューに残り、10分待ってから送信されます.問題は、メッセージがhandlerの参照を持っているため、handlerは匿名の内部クラスであり、匿名の内部クラスは彼の外部クラス(つまりLeakActivity)の暗黙的な参照を持っているが、LeakActivityはfinish()によって削除されているため、Contextのメモリ漏洩を引き起こす可能性がある.上のrunnableも匿名の内部クラスであることに注意してください.
    この隠れた危険をどのように解決するかは、静的な内部クラスを使用することを知っていると信じています.これにより、外部の暗黙的な参照は保持されません.
    public class LeakActivity extends Activity {
    
      /** *                     */
      private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;
    
        public MyHandler(SampleActivity activity) {
          mActivity = new WeakReference<SampleActivity>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
          SampleActivity activity = mActivity.get();
          if (activity != null) {
            // ...
          }
        }
      }
    
      private final MyHandler mHandler = new MyHandler(this);
    
      /** *    Runnable   *                     */
      private static final Runnable sRunnable = new Runnable() {
          @Override
          public void run() { /* ... */ }
      };
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        //10         
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    
        //       Activity
        finish();
      }
    }

    handlerとrunnableの両方を静的内部クラスとして宣言し、handlerが所有するContextをWeakReferenceとして宣言します.
    FBI Warning
    内部クラスがactivityのライフサイクル以外に存在する場合は、外部クラス(activity)の暗黙的な参照を持っているため、非静的な内部クラスは使用しないでください.それを静的に宣言する