Android 中 Handler 引發的內存泄露

在Android經常使用編程中,Handler在進行異步操做並處理返回結果時常常被使用。一般咱們的代碼會這樣實現。java

1.  public class SampleActivity extends Activity { 編程

2.   安全

3.    private final Handler mLeakyHandler = new Handler() { app

4.      @Override 框架

5.      public void handleMessage(Message msg) { 異步

6.        // ...  ide

7.      } 工具

8.    } oop

9.  post

可是,其實上面的代碼可能致使內存泄露,當你使用Android lint工具的話,會獲得這樣的警告

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

看到這裏,可能仍是有一些搞不清楚,代碼中哪裏可能致使內存泄露,又是如何致使內存泄露的呢?由移動應用安全團隊-愛內測(www.ineice.com)的技術工程師爲咱們分析下:

1.當一個Android應用啓動的時候,會自動建立一個供應用主線程使用的Looper實例。Looper的主要工做就是一個一個處理消息隊列中 的消息對象。在Android中,全部Android框架的事件(好比Activity的生命週期方法調用和按鈕點擊等)都是放入到消息中,而後加入到 Looper要處理的消息隊列中,由Looper負責一條一條地進行處理。主線程中的Looper生命週期和當前應用同樣長。

2.當一個Handler在主線程進行了初始化以後,咱們發送一個target爲這個Handler的消息到Looper處理的消息隊列時,實際上 已經發送的消息已經包含了一個Handler實例的引用,只有這樣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.     // Post a message and delay its execution for 10 minutes. 

15.     mLeakyHandler.postDelayed(new Runnable() { 

16.       @Override 

17.       public void run() { /* ... */ } 

18.     }, 1000 * 60 * 10); 

19.  

20.     // Go back to the previous Activity. 

21.     finish(); 

22.   } 

23.

分析一下上面的代碼,當咱們執行了Activity的finish方法,被延遲的消息會在被處理以前存在於主線程消息隊列中10分鐘,而這個消息中 又包含了Handler的引用,而Handler是一個匿名內部類的實例,其持有外面的SampleActivity的引用,因此這致使了 SampleActivity沒法回收,進行致使SampleActivity持有的不少資源都沒法回收,這就是咱們常說的內存泄露。

注意上面的new Runnable這裏也是匿名內部類實現的,一樣也會持有SampleActivity的引用,也會阻止SampleActivity被回收。

要解決這種問題,思路就是不適用非靜態內部類,繼承Handler時,要麼是放在單獨的類文件中,要麼就是使用靜態內部類。由於靜態的內部類不會持有外部類的引用,因此不會致使外部類實例的內存泄露。當你須要在靜態內部類中調用外部的Activity時,咱們可使用弱引用來處理。另外關於一樣也須要將Runnable設置爲靜態的成員屬性。注意:一個靜態的匿名內部類實例不會持有外部類的引用。 修改後不會致使內存泄露的代碼以下

1.  public class SampleActivity extends Activity { 

2.   

3.    /** 

4.     * Instances of static inner classes do not hold an implicit 

5.     * reference to their outer class. 

6.     */ 

7.    private static class MyHandler extends Handler { 

8.      private final WeakReference<SampleActivity> mActivity; 

9.   

10.     public MyHandler(SampleActivity activity) { 

11.       mActivity = new WeakReference<SampleActivity>(activity); 

12.     } 

13.  

14.     @Override 

15.     public void handleMessage(Message msg) { 

16.       SampleActivity activity = mActivity.get(); 

17.       if (activity != null) { 

18.         // ... 

19.       } 

20.     } 

21.   } 

22.  

23.   private final MyHandler mHandler = new MyHandler(this); 

24.  

25.   /** 

26.    * Instances of anonymous classes do not hold an implicit 

27.    * reference to their outer class when they are "static". 

28.    */ 

29.   private static final Runnable sRunnable = new Runnable() { 

30.       @Override 

31.       public void run() { /* ... */ } 

32.   }; 

33.  

34.   @Override 

35.   protected void onCreate(Bundle savedInstanceState) { 

36.     super.onCreate(savedInstanceState); 

37.  

38.     // Post a message and delay its execution for 10 minutes. 

39.     mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 

40.  

41.     // Go back to the previous Activity. 

42.     finish(); 

43.   } 

44.

其實在Android中不少的內存泄露都是因爲在Activity中使用了非靜態內部類致使的,就像本文提到的同樣,因此當咱們使用時要非靜態內部 類時要格外注意,若是其實例的持有對象的生命週期大於其外部類對象,那麼就有可能致使內存泄露。

相關文章
相關標籤/搜索