Handlerは弱い引用(WeakReference)を使う必要がありますか?
15908 ワード
ネット上の多くの文章ではHanlderを書くにはstaticで静的と宣言する必要があり、構造関数から伝達されたActivityインスタンスを弱い参照で包む必要があると言われています.
例えばこの英語のブログ
http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
中のSampleはこう書いてあります
post,postDelayedというRunnableを伝える方法ではhandleMessageメソッドはトリガーされません.
だからsendEmptyMessageDelayedでテストしてみましょう
このActivityを開くと自動finishになり、Android Monitorの中にあるInitiate GCボタンをクリックしてGC操作をトリガーします.
この時Dump Java Heapはメモリの情況を見ます
SampleActivityはすでに回収されていて、何もありません.
中の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後
前の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キーワードを外すと
静的でない内部クラスは、外部クラスの暗黙的な参照を持つためです.したがって、MyHandlerは現在のSampleActivityオブジェクトインスタンス(このオブジェクトが白い)を持っています.
static内部クラスでは、弱い参照でSampleActivityが赤く表示されることがわかります.staticキーワードを使わずに白いです.
推測すると、赤は回収待ちメモリを表しており、次回GC後に回収される.ホワイト代表はまだ使用しており、GC後は回収されないメモリです.
コードを書き換え、postDelayedメソッドを使用するように変更します.この場合、以前の結論に従ってRunnableも静的でなければなりません.
Runnableに現在のActivityに関連するコードがある場合は、Activityを弱参照する必要があります.
Dump Java Heap見て
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でも上静的弱参照が必要であることに注意してください.
しかしもっと良い方法がある
onDestroyの中にいるだけだremoveCallbacksAndMessages(null);,runnbaleメッセージもmessageメッセージもすべてクリアしていても、Activityは関連付けられません.次回はGCで無事回収できます.
長時間の遅延メッセージ/タスクを送信することは、実際には珍しく、例えば、スレッドを開いてインターネットにアクセスしたり、スレッドを開いて時間のかかる計算をしたりして、終了してからHandlerを通じてメッセージを送信してUIを更新したりすることが多い.
Dump Java Heap
Activityが赤いリサイクルメモリであることがわかり、問題ありません.
しかしrunメソッドのコードを変更します.
だから弱いリファレンスのgetメソッドを使うと、メモリの弱いリファレンスを強いリファレンスに変換することに相当します.
このような場合は、このActivityを終了するときにこのタスクをキャンセルすることをお勧めします.Threadはタスクをキャンセルする方法を提供していません.AsyncTaskのcancelメソッド、ExecutorServiceのshutdownメソッドを使用できます.もちろん、一般的なネットワークフレームワークvolley、okhttpでは、対応するキャンセルリクエストメソッドも提供されます.
(正直、AsyncTaskを使用する場合も静的内部クラス+弱参照に変更するのはもちろんですが、非常に面倒です.AsyncTaskはUIスレッドのonPreExecute、onProgressUpdate、onPostExecuteでActivityに使用されるメンバー変数を実行する場合、弱参照Activityの判別方法が必要で、かなり面倒です.)
まとめると、私はhandlerを使ってHandlerしか使えないと思います.removeCallbacksAndMessages(null);この方法は便利で速い.
例えばこの英語のブログ
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操作をトリガーします.
この時Dump Java Heapはメモリの情況を見ます
SampleActivityはすでに回収されていて、何もありません.
中の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後
前の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キーワードを外すと
静的でない内部クラスは、外部クラスの暗黙的な参照を持つためです.したがって、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見て
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
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();
}
}
だから弱いリファレンスの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);この方法は便利で速い.