Handlerは弱い引用(WeakReference)を使う必要がありますか?

15908 ワード

ネット上の多くの文章ではHanlderを書くにはstaticで静的と宣言する必要があり、構造関数から伝達されたActivityインスタンスを弱い参照で包む必要があると言われています.
例えばこの英語のブログ
http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
 
中のSampleはこう書いてあります
 
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, 1000 * 60 * 10);
    
    // Go back to the previous Activity.
    finish();
  }
}

 
post,postDelayedというRunnableを伝える方法ではhandleMessageメソッドはトリガーされません.
だからsendEmptyMessageDelayedでテストしてみましょう
 
public class SampleActivity extends Activity {

    private ImageView iv;

    /**
     * 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();
            Log.e("YAO", "MyHandler - handleMessage ------        activity!=null:" + (activity != null));
            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);

        //         ,    Activity     ,       Memory   
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

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

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

 
このActivityを開くと自動finishになり、Android Monitorの中にあるInitiate GCボタンをクリックしてGC操作をトリガーします.
Handler还需要用到弱引用(WeakReference)吗?_第1张图片
この時Dump Java Heapはメモリの情況を見ます
Handler还需要用到弱引用(WeakReference)吗?_第2张图片
SampleActivityはすでに回収されていて、何もありません.
Handler还需要用到弱引用(WeakReference)吗?_第3张图片
中のMyHandlerはまだ存在し、ここではHandlerがMessageオブジェクトを1つしか持っていないことも見ることができます.
30秒待ってメッセージが到着したら、ログが印刷されます.
02-11 15:22:15.033 7153-7153/com.yao.memorytest E/YAO:MyHandler-handleMessage------activityに到着!=null:false
SampleActivityが回収されたことを示します.
 
 
 
もう一度、今度Initiate GCボタンをクリックしないで、Dump Java Heap後
Handler还需要用到弱引用(WeakReference)吗?_第4张图片
 
Handler还需要用到弱引用(WeakReference)吗?_第5张图片
前の2枚の図を比較すると、SampleActivityがまだ存在することがわかります.MyHandlerから見ると、このインスタンスにはMessageオブジェクトのほかにSampleActivityオブジェクトがあります(このオブジェクトが赤いことに注意してください).
30秒待って結果印刷
02-11 15:24:15.033 7153-7153/com.yao.memorytest E/YAO:MyHandler-handleMessage------activityに到着!=null:true
説明ページを閉じる30秒後、弱参照Activityはまだあります.
 
 
 
staticキーワードを外すと
Handler还需要用到弱引用(WeakReference)吗?_第6张图片
静的でない内部クラスは、外部クラスの暗黙的な参照を持つためです.したがって、MyHandlerは現在のSampleActivityオブジェクトインスタンス(このオブジェクトが白い)を持っています.
 
static内部クラスでは、弱い参照でSampleActivityが赤く表示されることがわかります.staticキーワードを使わずに白いです.
推測すると、赤は回収待ちメモリを表しており、次回GC後に回収される.ホワイト代表はまだ使用しており、GC後は回収されないメモリです.
 
 
コードを書き換え、postDelayedメソッドを使用するように変更します.この場合、以前の結論に従ってRunnableも静的でなければなりません.
Runnableに現在のActivityに関連するコードがある場合は、Activityを弱参照する必要があります.
 
public class SampleTwoActivity extends Activity {

    private ImageView iv;

    /**
     * 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(SampleTwoActivity activity) {
//            mActivity = new WeakReference(activity);
//        }
//
//        @Override
//        public void handleMessage(Message msg) {
//            SampleTwoActivity activity = mActivity.get();
//            Log.e("YAO", "MyHandler - handleMessage ------        activity!=null:" + (activity != null));
//            if (activity != null) {
//                // ...
//            }
//        }
    }

    private final MyHandler mHandler = new MyHandler();

    /**
     * Instances of anonymous classes do not hold an implicit
     * reference to their outer class when they are "static".
     */
    private static class MyRunnable implements Runnable {

        private final WeakReference mActivity;

        public MyRunnable(SampleTwoActivity activity) {
            mActivity = new WeakReference(activity);
        }

        @Override
        public void run() {
            SampleTwoActivity activity = mActivity.get();
            Log.e("YAO", "MyRunnable - run ------     Runnable  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.iv.setVisibility(View.VISIBLE);
            }
        }
    }

    private final MyRunnable mMyRunnable = new MyRunnable(this);

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

        //         ,    Activity     ,       Memory   
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(mMyRunnable, 1000L * 30);
        //mHandler.sendEmptyMessageDelayed(0, 1000L * 30);

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

 
Dump Java Heap見て
Handler还需要用到弱引用(WeakReference)吗?_第7张图片
MyRunnableもActivityの弱い引用を持っていて、赤いです.GCを実行すると、Activityも予想外に回収された.
 
30秒待って結果印刷
02-12 03:31:18.215 7501-7501/com.yao.memorytest E/YAO:MyRunnable-run------実行遅延Runnable activity!=null:false
 
 
以上は,送信用Handlerが遅延メッセージ,遅延タスクを送信する場合である.
上静的内部クラス+弱参照Handlerでメモリリークの問題を解決できることがわかりますが、Runnableでも上静的弱参照が必要であることに注意してください.
 
 
しかしもっと良い方法がある
 
public class SampleFourActivity extends Activity {

    private ImageView iv;

    private class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            Log.e("YAO", "MyHandler - handleMessage ------      ");
            iv.setVisibility(View.VISIBLE);
        }
    }

    private final MyHandler mHandler = new MyHandler();

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

        //         ,    Activity     ,       Memory   
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

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

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

onDestroyの中にいるだけだremoveCallbacksAndMessages(null);,runnbaleメッセージもmessageメッセージもすべてクリアしていても、Activityは関連付けられません.次回はGCで無事回収できます. 
 
 
 
長時間の遅延メッセージ/タスクを送信することは、実際には珍しく、例えば、スレッドを開いてインターネットにアクセスしたり、スレッドを開いて時間のかかる計算をしたりして、終了してからHandlerを通じてメッセージを送信してUIを更新したりすることが多い.
 
public class SampleThreeActivity extends Activity {

    private ImageView iv;

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

    }

    private final MyHandler mHandler = new MyHandler();

    /**
     * Instances of anonymous classes do not hold an implicit
     * reference to their outer class when they are "static".
     */
    private static class MyRunnable implements Runnable {

        private final WeakReference mActivity;

        public MyRunnable(SampleThreeActivity activity) {
            mActivity = new WeakReference(activity);
        }

        @Override
        public void run() {
            Log.e("YAO", "TestMemoryActivity.java - run() ----------             ....");
            SystemClock.sleep(1000L * 30);
            SampleThreeActivity activity = mActivity.get();
            Log.e("YAO", "MyRunnable - run ------     Runnable  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.mHandler.obtainMessage().sendToTarget();
            }
        }
    }

    private final MyRunnable mMyRunnable = new MyRunnable(this);

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

        //         ,    Activity     ,       Memory   
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

        // Post a message and delay its execution for 10 minutes.
        //mHandler.postDelayed(mMyRunnable, 1000 * 30);
        //mHandler.sendEmptyMessageDelayed(0, 1000L * 30);
        new Thread(mMyRunnable).start();

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

 
Dump Java Heap
 
Handler还需要用到弱引用(WeakReference)吗?_第8张图片
Activityが赤いリサイクルメモリであることがわかり、問題ありません.
しかしrunメソッドのコードを変更します.
 
@Override
public void run() {
	SampleThreeActivity activity = mActivity.get();
	if (activity != null) {
		//        Activity       (    url),      。
		String url = activity.url;
		Log.e("YAO", "TestMemoryActivity.java - run() ----------             ....");
		SystemClock.sleep(1000L * 30);
		activity.mHandler.obtainMessage().sendToTarget();
	}
}

 
 
Handler还需要用到弱引用(WeakReference)吗?_第9张图片だから弱いリファレンスのgetメソッドを使うと、メモリの弱いリファレンスを強いリファレンスに変換することに相当します.
 
 
このような場合は、このActivityを終了するときにこのタスクをキャンセルすることをお勧めします.Threadはタスクをキャンセルする方法を提供していません.AsyncTaskのcancelメソッド、ExecutorServiceのshutdownメソッドを使用できます.もちろん、一般的なネットワークフレームワークvolley、okhttpでは、対応するキャンセルリクエストメソッドも提供されます.
(正直、AsyncTaskを使用する場合も静的内部クラス+弱参照に変更するのはもちろんですが、非常に面倒です.AsyncTaskはUIスレッドのonPreExecute、onProgressUpdate、onPostExecuteでActivityに使用されるメンバー変数を実行する場合、弱参照Activityの判別方法が必要で、かなり面倒です.)
 
    static class MyAsyncTask extends AsyncTask {

        private final WeakReference mActivity;

        public MyAsyncTask(SampleFiveActivity activity) {
            mActivity = new WeakReference(activity);
        }

        @Override
        protected void onPreExecute() {
            SampleFiveActivity activity = mActivity.get();
            Log.e("YAO", "MyAsyncTask - onPreExecute ------  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.iv.setVisibility(View.VISIBLE);
            }
        }

        @Override
        protected Void doInBackground(Void... params) {
            Log.e("YAO", "TestMemoryActivity.java - run() ----------             ....");
            SystemClock.sleep(1000L * 30);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            SampleFiveActivity activity = mActivity.get();
            Log.e("YAO", "MyAsyncTask - onPostExecute ------  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.iv.setVisibility(View.VISIBLE);
            }
        }
    }

 
 
 
 
 
 
まとめると、私はhandlerを使ってHandlerしか使えないと思います.removeCallbacksAndMessages(null);この方法は便利で速い.