【訳】Context漏洩の原因:Handler&内部クラス

11673 ワード

次のコードを考えてみましょう
1 public class SampleActivity extends Activity {
2 
3   private final Handler mLeakyHandler = new Handler() {
4     @Override
5     public void handleMessage(Message msg) {
6       // ... 
7     }
8   }
9 }

よく観察しないと、上記のコードが深刻なメモリ漏洩を引き起こす可能性があります.Android Lintでは、次の警告が表示されます.
In Android, Handler classes should be static or leaks might occur.
しかし、いったい漏れなのか、どうやって起こったのか.問題の根源を特定し、まず私たちが知っている1、Androidアプリケーションが初めて起動したとき、AndroidフレームワークはアプリケーションのメインスレッドにLooperオブジェクトを作成します.1つのLooperは、Messageオブジェクトを1サイクルで処理する簡単なメッセージキューを実現する.アクティブライフサイクルメソッド呼び出し、ボタンクリックなどのすべての主要なアプリケーションフレームワークイベントは、Looperのメッセージキューに追加され、処理されます.メインスレッドのLooperは、アプリケーションのライフサイクル全体にわたって存在します.2.プライマリ・スレッドでMessageがインスタンス化されると、Looperのメッセージ・キューに関連付けられる.メッセージキューに送信されたメッセージは、AndroidフレームワークがHandleで最終的にこのメッセージを処理するときにLooperを呼び出すことができるように、Handlerの参照を保持する.3、Javaでは、非静的な内部クラスと匿名クラスが外部クラスの参照を暗黙的に保持します.静的内部クラスはできません.
では、いったいメモリ漏れですか?わかりにくいようですが、次のコードを例にしてみましょう.
 1 public class SampleActivity extends Activity {
 2  
 3   private final Handler mLeakyHandler = new Handler() {
 4     @Override
 5     public void handleMessage(Message msg) {
 6       // ...
 7     }
 8   }
 9  
10   @Override
11   protected void onCreate(Bundle savedInstanceState) {
12     super.onCreate(savedInstanceState);
13  
14     //  10 
15     mLeakyHandler.postDelayed(new Runnable() {
16       @Override
17       public void run() { }
18     }, 60 * 10 * 1000);
19  
20     //  Activity
21     finish();
22   }
23 }

 
このActivityがfinishedされると、遅延送信されたメッセージは、処理されるまでメインスレッドのメッセージキューで10分間生存し続ける.このメッセージはこのActivityのHandler参照を持ち、このHandlerは彼の外部クラス(この例ではSampleActivity)を暗黙的に持っている.この参照は、メッセージが処理されるまで解放されません.そのため、Activityはゴミ回収メカニズムによって回収されず、彼が持っているアプリケーションリソースを漏らすことはありません.なお,15行目の匿名Runnableクラスも同様である.匿名クラスの非静的インスタンスは、暗黙的な外部クラス参照を持つため、contextが漏洩します.
この問題を解決するために、Handlerのサブクラスは、新しいファイルに定義するか、静的内部クラスを使用する必要があります.静的内部クラスは、外部クラスの参照を暗黙的に保持しません.Activityの漏洩は起こりませんHandle内部で外部Activityを呼び出す方法が必要な場合は、HandlerにActivityの弱い参照(WeakReference)を持たせて、contextの漏洩を意外に起こさないようにします.匿名Runnableクラスのインスタンス化によるメモリ漏洩を解決するために、匿名クラスの静的インスタンスが外部クラスの参照を暗黙的に保持しないため、静的変数を使用して彼を参照します.
 1 public class SampleActivity extends Activity {
 2     /**
 3     *  
 4     */
 5     private static final Runnable sRunnable = new Runnable() {
 6             @Override
 7             public void run() {
 8             }
 9         };
10 
11     private final MyHandler mHandler = new MyHandler(this);
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16 
17         //  10 .
18         mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
19 
20         //  Activity
21         finish();
22     }
23 
24     /**
25     *  。
26     */
27     private static class MyHandler extends Handler {
28         private final WeakReference<SampleActivity> mActivity;
29 
30         public MyHandler(SampleActivity activity) {
31             mActivity = new WeakReference<SampleActivity>(activity);
32         }
33 
34         @Override
35         public void handleMessage(Message msg) {
36             SampleActivity activity = mActivity.get();
37 
38             if (activity != null) {
39                 // ...
40             }
41         }
42     }
43 }

静的と非静的の内部クラスの違いは分かりにくいが、Android開発者一人一人が理解しなければならない.開発中に触ってはいけない雷区は何ですか?ActivityのライフサイクルがActivityより長くならないように、Activityでは非静的な内部クラスは使用されません.逆に,Activity弱参照を持つ静的内部クラスをできるだけ使用する.
Handler#handleMessage(Message)