一般的な Android プログラミングでは、非同期操作を実行し、返された結果を処理するときにハンドラーがよく使用されます。通常、コードは次のように実装されます。
次のようにコードをコピーします。
パブリック クラス SampleActivity extends Activity {
プライベート最終ハンドラー mLeakyHandler = new Handler() {
@オーバーライド
public void handleMessage(メッセージメッセージ) {
// ...
}
}
}
ただし、実際には、上記のコードは Android lint ツールを使用するとメモリ リークを引き起こす可能性があります。
次のようにコードをコピーします。
Android では、ハンドラー クラスは静的でなければならず、リークが発生する可能性があります。アプリケーション スレッドの MessageQueue にエンキューされたメッセージも、ターゲット ハンドラーを保持します。ハンドラーが内部クラスの場合、外部クラスのリークを避けるために、その外部クラスも保持されます。外部クラスへの WeakReference を持つ静的なネストされたクラスとしてハンドラーを宣言します。
これを見ても、コードのどこでメモリ リークが発生するのか、またどのようにメモリ リークが発生するのか、混乱するかもしれません。それではゆっくり分析していきましょう。
1. Android アプリケーションが起動すると、アプリケーションのメイン スレッドで使用する Looper インスタンスが自動的に作成されます。 Looper の主な仕事は、メッセージ キュー内のメッセージ オブジェクトを 1 つずつ処理することです。 Android では、Android フレームワークのすべてのイベント (アクティビティ ライフ サイクル メソッドの呼び出しやボタンのクリックなど) がメッセージに入れられ、Looper によって処理されるメッセージ キューに追加されます。Looper はそれらのイベントを 1 つずつ処理する責任があります。 1つずつ。メインスレッドでの Looper のライフサイクルは、現在のアプリケーションと同じ長さです。
2. ハンドラーがメインスレッドで初期化され、このハンドラーを対象とするメッセージを Looper によって処理されるメッセージキューに送信すると、実際に送信されるメッセージにはすでに Handler インスタンスへの参照が含まれています。この方法でのみ Looper が処理されます。メッセージを受信した場合は、Handler#handleMessage(Message) を呼び出してメッセージの正しい処理を完了できます。
3. Java では、非静的内部クラスと匿名内部クラスは、その外部クラスへの参照を暗黙的に保持します。静的内部クラスは外部クラスへの参照を保持しません。詳細については、「Chat Java: "無効な" private 修飾子」を参照してください。
確かに、上記のコード例ではメモリ リークを検出するのが少し難しいですが、次の例は非常に明白です。
次のようにコードをコピーします。
パブリック クラス SampleActivity extends Activity {
プライベート最終ハンドラー mLeakyHandler = new Handler() {
@オーバーライド
public void handleMessage(メッセージメッセージ) {
// ...
}
}
@オーバーライド
protected void onCreate(バンドル保存インスタンス状態) {
super.onCreate(savedInstanceState);
// メッセージを投稿し、その実行を 10 分間遅らせます。
mLeakyHandler.postDelayed(new Runnable() {
@オーバーライド
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// 前のアクティビティに戻ります。
仕上げる();
}
}
上記のコードを分析すると、アクティビティの終了メソッドを実行すると、遅延メッセージは処理されるまで 10 分間メイン スレッドのメッセージ キューに存在し、このメッセージにはハンドラーへの参照が含まれており、ハンドラーは匿名のインスタンスです。内部クラスは外部の SampleActivity への参照を保持しているため、SampleActivity がリサイクルできなくなり、その結果、SampleActivity が保持する多くのリソースがリサイクルできなくなります。これは、よくメモリ リークと呼ばれるものです。
上記の新しい Runnable も匿名の内部クラスによって実装されており、SampleActivity への参照も保持しているため、SampleActivity がリサイクルされないことに注意してください。
この問題を解決するには、Handler を継承するときに非静的内部クラスを使用しないようにし、別のクラス ファイルに配置するか、静的内部クラスを使用します。静的内部クラスは外部クラスへの参照を保持しないため、外部クラスのインスタンスのメモリ リークは発生しません。静的内部クラスで外部アクティビティを呼び出す必要がある場合、弱い参照を使用してそれを処理できます。さらに、Runnable も静的メンバー属性として設定する必要があります。注: 静的匿名内部クラス インスタンスは、外部クラスへの参照を保持しません。 変更後にメモリリークが発生しないコードは次のとおりです。
次のようにコードをコピーします。
パブリック クラス SampleActivity extends Activity {
/**
* 静的内部クラスのインスタンスは暗黙的なクラスを保持しません。
* 外部クラスへの参照。
*/
プライベート静的クラス MyHandler extends Handler {
private Final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity アクティビティ) {
mActivity = new WeakReference<SampleActivity>(アクティビティ);
}
@オーバーライド
public void handleMessage(メッセージメッセージ) {
SampleActivity アクティビティ = mActivity.get();
if (アクティビティ != null) {
// ...
}
}
}
private Final MyHandler mHandler = new MyHandler(this);
/**
* 匿名クラスのインスタンスは暗黙的なクラスを保持しません。
* 「静的」である場合の外部クラスへの参照。
*/
プライベート静的最終ランナブル sRunnable = new Runnable() {
@オーバーライド
public void run() { /* ... */ }
};
@オーバーライド
protected void onCreate(バンドル保存インスタンス状態) {
super.onCreate(savedInstanceState);
// メッセージを投稿し、その実行を 10 分間遅らせます。
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// 前のアクティビティに戻ります。
仕上げる();
}
}
実際、この記事で説明したように、Android でのメモリ リークの多くは、Activity での非静的内部クラスの使用によって引き起こされるため、非静的内部クラスを使用する場合は、そのインスタンスのライフ サイクルに特別な注意を払う必要があります。保持されているオブジェクトがその外部クラスのオブジェクトより大きい場合、メモリ リークが発生する可能性があります。個人的には、この問題を解決するために、この記事の静的クラスと弱い参照メソッドを使用する傾向があります。