【譯】什麼導致了Context泄露:Handler&內部類

思考下面代碼

複製代碼
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對象。一個Looper實現了一個簡單的消息隊列,在一個循環中處理Message對象。所有主要的應用程序框架事件(如活動生命週期方法調用,單擊按鈕,等等)都包含在Message對象,它被添加到Looper的消息隊列然後一個個被處理。主線程的Looper在應用程序的整個生命週期中存在。
2、當一個Handle在主線程被實例化,它就被關聯到Looper的消息隊列。被髮送到消息隊列的消息會持有一個Handler的引用,以便Android框架可以在Looper最終處理這個消息的時候,調用Handler#handleMessage(Message)
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弱引用的靜態內部類。

譯文鏈接


作者:kissazi2 
出處:http://www.cnblogs.com/kissazi2/ 
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

發佈了102 篇原創文章 · 獲贊 244 · 訪問量 74萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章