LeakCanary傻瓜式的記憶體洩露檢測工具

姜家志發表於2016-05-30

Android開發過程中常常會遇到OOMjava.lang.OutOfMemoryError),一般出現最多的是在建立Bitmap上,也有可能是在記憶體中處理了大量的資料造成的。

一般會針對Bitamp做下面幾種的優化:

  • 1.增加程式的記憶體大小
  • 2.使用Bitmap.Config.ALPHA_8(圖片失真)
  • 3.顯示的呼叫System.gc()
  • 4.catch Exception
  • 5.呼叫bitmap.recycle()
  • 6.縮小bitmap的大小(如果是讀取大圖這樣做是應該的,Bitmap如果是剛好適配螢幕的就不應該縮小)
  • 7.使用弱引用和軟引用

一般我們會忽略掉一個問題就是造成OOM的原因一般都是不是發生OOM崩潰的地方,可能Activity造成的記憶體洩露,也可能是運算元據庫造成的記憶體洩露,當記憶體已經非常接近峰值的時候,這個時候恰巧要建立一個Bitmap物件就會發生OOM,一般來說Bitmap物件佔用的記憶體空間比較大。

記憶體洩露

每個物件都有自己的生命週期,Activity會呼叫onDestroy做銷燬處理,但是如果使用Activity的Context呼叫Toast,就會把這個Activity的引用傳給了Toast,Toast的生命週期不會隨著Activity的銷燬而銷燬,這樣Activity會發生記憶體洩露,因為被Toast引用。

常見的記憶體洩露形成的原因:

  • 1.Toast持有Activity的引用
    2.資料庫遊標Cursor沒有關閉
    3.Adapter沒有複用convertView
    4.物件被生命週期更長的物件引用,Activity被靜態集合引用
    ….

監控記憶體的方式

Heap Dump是一種Java比較常用的檢測記憶體的方式,簡單來說就是我們在一個初始狀態A, Dump一次記憶體,在做了一些操作之後回到狀態A,再Dump一次記憶體。然後使用分析工具做分析(MAT),根據分析的結果就能知道是否存在記憶體洩露,這種方式比較複雜和繁瑣並不是特別易用。

Moitors:Android SDK 自帶的記憶體監控工具,Monitors能看到記憶體的變化,記憶體是增加還是減少.開啟一個Activity會導致記憶體增加,關閉一個Activity會導致記憶體減少,反覆的實驗如果每次開啟一個Activity再關閉之後增加的記憶體不會減少就說明這個Activity有記憶體洩露的問題,可以使用log輔助進行檢測,這種方式的缺點是並不是特別的準確,因為記憶體的釋放是內生命週期有關也和GC的呼叫有關。

而LeakCanary就是一個簡單的,方便的記憶體檢測工具,可以輕易的發現記憶體問題,還有更加簡單清晰的報告。

LeakCanary

LeakCanary是一個開源的檢測記憶體洩露的java庫。專案地址:https://github.com/square/leakcanary

LeakCanary實際上就是在本機上自動做了Heap dump,對生成的hprof檔案分析,展示結果。和手工分析Heap Dump的方式一樣。

下面是一個LeakCanary的結果截圖:

LeakCanary傻瓜式的記憶體洩露檢測工具

整合LeakCanary

build.gradle新增依賴:

使用LeakCanary會影響程式的效能,尤其是在Heap dump和分析操作時,不過我們可以在依賴裡面指定對應的版本,debug的時候才進行分析,release的時候不進行分析。

debugCompile可以使用檢測版本:com.squareup.leakcanary:leakcanary-android

releaseCompile使用no-op模式,即No Operation Performed就是不會把對應的類庫編譯,指定類庫為無用的指令:com.squareup.leakcanary:leakcanary-android-no-op
然後在Application中加入分析Activity的程式碼:

這樣就可以檢測Activity的記憶體洩露了。內部實現使用了ActivityLifecycleCallbacks方法監控所有Activity的生命週期。

檢測其他的

LeakCanary中提供了RefWatcher,可以用來監控所有的物件。

首先需要例項化RefWatcher:

對於監控的物件使用:

一般我們是對物件銷燬的時候對物件進行監控,比如內部實現的對於Activity的監控:

只是在onActivityDestroyed的時候才對於activity進行監控。

如何解決記憶體洩露

一般情況記憶體洩露的原因都是由於引用的使用不當造成的,而且Android的GC能夠保證回收迴圈引用,如果一個迴圈引用沒有外部引用時就會被回收,而且Android的GC效率很高,(當然GC的演算法本身也在不停的改進)。

一般情況下我們儘量避免錯誤的引用方式帶來的記憶體洩露問題:

1.生命週期長的物件引用生命週期短的物件,比如static的物件群引用Activity
2.使用Application的Context物件,而不是Activity的Context
3.避免非靜態類的內部類對於類的隱式引用,使用靜態的內部類
4.使用Android的快取機制,比如ListView的複用機制
5.手動關閉資源,比如Curous的關閉
6.registerReceiver和unRegisterReceiver成對出現

相關文章