context是如何泄漏的 - Handlers和內部類

本人翻譯, 略有改動, 原文地址如下:

http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

 

考慮如下代碼:

 

Java代碼  收藏代碼
  1. public class SampleActivity extends Activity {   
  2.     private final Handler mLeakyHandler = new Handler() {  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.           /* ... */  
  6.         }  
  7.     }  
  8. }  

 

你可能看不出來這段代碼會造成內存泄漏, 確實, 它不那麼容易被發現. 如果你運行Android的lint工具, 它會給你一個警告, 提示你把handler定義成靜態的(static), 否則可能造成內存泄漏. 但內存泄漏是怎麼發生的呢?

 

首先, 我們應該知道如下幾點:

1: 當一個android程序啓動時, 框架層(framework)會爲程序的主線程創建一個Looper對象. 該對象實現了一個簡單的消息隊列, 循環不斷的處理隊列上的消息對象(Message), 主線程上的Looper對象在程序的整個生命週期中一直存在.

2: 當一個Handler在主線程上被實例化時, 它就與Looper的消息隊列關聯到一起了. 隊列上的Message對象持有一個handler的引用, 這使得Looper處理到某一個Message時, 能夠調用handler.handleMessage()方法.

3: 在java裏, 非靜態內部類和匿名內部類持有一個隱式的外部類引用. 相反, 靜態的內部類就沒有該隱式引用.

 

那麼, 泄露在哪裏發生呢? 這個有點微妙, 考慮下面一個例子:

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.     // Post a message and delay its execution for 10 minutes.  
  15.     mLeakyHandler.postDelayed(new Runnable() {  
  16.       public void run() { }  
  17.     }, 600000);  
  18.        
  19.     // Go back to the previous Activity.  
  20.     finish();  
  21.   }  
  22. }  
  23.    

 

當Activityfinish後, 我們發出的那個"延遲處理消息"將在主線程的消息隊列中保持10分鐘, 直到該消息最終被處理. 由於消息持有一個handler的引用, 而handler又持有一個它的外部類-SampleActivity的引用, 這樣就阻止了activity的context被垃圾回收, 從而泄漏了Activty引用的所有的應用資源. 注意上述例子中的匿名的Runnable對象也一樣造成了context的泄露.

 

要避免這個問題, 就需要將Handler改爲靜態內部類. 如果你需要在Handler中調用Activity外部類的方法, 你可以在handler中使用一個WeakReference來持有activity對象.

(注意我們將Handler和Runnable都定義成了static的)

Java代碼  收藏代碼
  1. public class SampleActivity extends Activity {  
  2.   private static class MyHandler extends Handler {  
  3.     private final WeakReference<SampleActivity> mActivity;  
  4.    
  5.     public MyHandler(SampleActivity activity) {  
  6.       mActivity = new WeakReference<SampleActivity>(activity);  
  7.     }  
  8.    
  9.     @Override  
  10.     public void handleMessage(Message msg) {  
  11.       SampleActivity activity = mActivity.get();  
  12.       if (activity != null) {  
  13.         /* ... */  
  14.       }  
  15.     }  
  16.   }  
  17.    
  18.   private final MyHandler mHandler = new MyHandler(this);  
  19.    
  20.   // Instances of anonymous classes do not hold an implicit  
  21.   // reference to their outer class when they are "static".  
  22.   private final static Runnable sRunnable = new Runnable() {  
  23.       public void run() { }  
  24.   };  
  25.    
  26.   @Override  
  27.   protected void onCreate(Bundle savedInstanceState) {  
  28.     super.onCreate(savedInstanceState);  
  29.    
  30.     // Post a message and delay its execution for 10 minutes.  
  31.     mHandler.postDelayed(sRunnable, 600000);  
  32.        
  33.     // Go back to the previous Activity.  
  34.     finish();  
  35.   }  
  36. }  

 

 

結論: 在Activity中使用非靜態的內部類時, 儘量避免內部類生命週期超出了Activity之外. 類似的例子還有AsyncTask.

=================================

補充例子:

如果你看過PendingIntent的源代碼, 你會看到它有一些send(Handler...)的方法, 如果某個Activity調用了PendingIntent.send(...), 並且傳入一個非靜態的內部Handler類, 當activity被銷燬後, 內部類仍然持有它的引用, 導致它無法被垃圾收集.

當然, 如果你沒有像這樣不當的發佈一個Handler到其它的類, 你就不用擔心泄露發生.

=================================​

如果你不想每次都創建一個WeakReference, 可以先創建這樣一個通用類:

 

Java代碼  收藏代碼
  1. public abstract class WeakReferenceHandler<T> extends Handler {  
  2.     private WeakReference<T> mReference;  
  3.    
  4.     public WeakReferenceHandler(T reference) {  
  5.         mReference = new WeakReference<T>(reference);  
  6.     }  
  7.    
  8.     @Override  
  9.     public void handleMessage(Message msg) {  
  10.         if (mReference.get() == null)  
  11.             return;  
  12.         handleMessage(mReference.get(), msg);  
  13.     }  
  14.    
  15.     protected abstract void handleMessage(T reference, Message msg);  
  16. }  
  17.    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章