androd 內存泄露分析

內存泄露是在Android開發中尤其要重視的一個問題,對開發人員開說,這是一個很容易犯也很常見的錯誤。優化內存泄露的問題,主要從兩方面着手,一是開發人員避免寫出有內存泄露的代碼,二是通過一些諸如MAT的內存分析工具來找出潛在的內存泄露並解決它。


其實平時遇到的最多的情況,就是對Activity或Context保持一個長生命週期的引用。下面主要來分析一下造成內存泄露的各種原因。


一、靜態變量導致的內存泄露


要不怎麼說static關鍵字要慎用呢?來看看下面這段代碼,Context對象爲靜態的,那麼Activity就無法正常銷燬,會常駐內存。


  1. public class MemoryActivity extends Activity{  

  2.     public static Context mContext;  

  3.     @Override  

  4.     protected void onCreate(Bundle savedInstanceState) {  

  5.         super.onCreate(savedInstanceState);  

  6.         setContentView(R.layout.activity_main);  

  7.         mContext = this;  

  8.     }  

  9. }  


這是一個很隱晦的內存泄漏的情況,在開發過程中,Context能使用ApplicationContext得儘量使用ApplicationContext,因爲這個Context的生存週期和你的應用的生存週期一樣長,而不是取決於activity的生存週期。除此之外,在Activity裏面創建了靜態的View,這就意味着該View持有一個對當前這個Activity的引用,那麼Activity也是無法正常銷燬的。


二、引用沒釋放導致的內存泄露


1、註冊服務沒取消導致的內存泄露


假如我們在鎖屏界面(LockScreen)中,監聽系統中的電話服務以獲取一些信息(如信號強度等),則可以在LockScreen中定義一個PhoneStateListener的對象,同時將它註冊到TelephonyManager服務中。對於LockScreen對象,當需要顯示鎖屏界面的時候就會創建一個LockScreen對象,而當鎖屏界面消失的時候LockScreen對象就會被釋放掉。但是如果在釋放LockScreen對象的時候沒有取消之前註冊的PhoneStateListener對象,那麼則會導致LockScreen無法被垃圾回收。而鎖屏界面又不斷的創建和銷燬,則最終會由於大量的LockScreen對象沒有辦法被回收而引起OutOfMemory。類似的,BraodcastReceiver,ContentObserver,FileObserver在Activity onDeatory或者某類聲明週期結束之後一定要unregister掉,否則這個Activity類會被system強引用,不會被內存回收。


2、集合中的對象沒有及時清理導致的內存泄露


當該集合爲靜態的時候,那麼在集合裏面對象越來越多的時候,最好要及時清理不需要用到的對象。


三、單例模式導致的內存泄露


單例模式的特點就是它的生命週期和Application一樣,那麼如果某個Activity實例被一個單例所持有,也就是說在單例裏面引用了它,那麼就會造成Activity對象無法正常回收釋放。


四、資源對象未關閉導致的內存泄露


資源性對象(如Cursor,File文件等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收內存。它們的緩衝不僅存在於java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置爲null,而不關閉它們,往往會造成內存泄露。例如程序中經常會進行查詢數據庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果我們的查詢結果集比較小,對內存的消耗不容易被發現,只有在常時間大量操作的情況下才會復現內存問題,這樣就會給以後的測試和問題排查帶來困難和風險。類似的,Bitmap在不需要之後,應該調用recycle回收,再置爲null。


五、屬性動畫導致的內存泄露


例如下面的代碼,由於該屬性動畫爲循環動畫,如果在Activity銷燬時,沒有取消動畫,那麼雖然我們看不見動畫在執行,實際上動畫仍然一直播放下去,這個時候Button會被動畫所持有,而Button又持有對應的Activity對象,那麼就會造成Activity無法正常釋放。


  1. public class MemoryActivity extends Activity{  

  2.     @Override  

  3.     protected void onCreate(Bundle savedInstanceState) {  

  4.         super.onCreate(savedInstanceState);  

  5.         setContentView(R.layout.activity_main);  

  6.         Button button = (Button) findViewById(R.id.btn_end);  

  7.         ObjectAnimator animator = ObjectAnimator.ofFloat(button, "", 0,180);  

  8.         animator.setDuration(2000);  

  9.         animator.setRepeatCount(-1);  

  10.         animator.start(); // 沒有調用cancle()  

  11.     }  

  12. }  


六、Adapter未使用緩存的convertView導致的內存泄露


ListView提供每一個item所需要的view對象,初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化一定數量的View對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上面的Item的View對象會被回收,然後被用來構造新出現的最下面的Item。這個構造過程就是由getView()方法完成的,getView()的第二個形參View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。由此可以看出,如果我們不去使用 convertView,而是每次都在getView()中重新實例化一個View對象的話,即浪費資源也浪費時間,也會使得內存佔用越來越大。正確的寫法如下


  1. public View getView(int position, ViewconvertView, ViewGroup parent) {  

  2.         View view = null;  

  3.         if (convertView != null) { // 不應該直接new  

  4.                 view = convertView;   

  5.                 ...   

  6.         } else {   

  7.                 view = new Xxx(...);  

  8.                 ...   

  9.         }   

  10.         return view;   

  11. }  


七、Handler內部類內存泄露


當使用內部類(包括匿名類)來創建Handler的時候,Handler對象會隱式地持有一個外部類對象(通常是一個Activity)的引用,而Handler通常會伴隨着一個耗時的後臺線程(例如從網絡拉取圖片)一起出現,這個後臺線程在任務執行完畢(例如圖片下載完畢)之後,通過消息機制通知Handler,然後Handler把圖片更新到界面。然而,如果用戶在網絡請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時線程尚未執行完,而該線程持有Handler的引用,這個Handler又持有Activity的引用,就導致該Activity無法被回收(即內存泄露),直到網絡請求結束(例如圖片下載完畢)。另外,如果你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,並把這條Message推到MessageQueue中,那麼在你設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收。可以在Activity結束後,關閉線程,如果你的Handler是被delay的Message持有了引用,那麼調用removeCallbacks方法來移除消息隊列。


內存泄露檢測工具MAT的使用請參考:http://jingyan.baidu.com/article/ae97a646b4eea8bbfc461d5a.html


這裏需要強調一點,有一些內存泄露通過Mat是查不出來的,比如native的代碼,MAT對C/C++是無能爲力的。

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