在Android
開發過程中常常會遇到OOM(java.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
在build.gradle
新增依賴:
1 2 3 4 5 |
dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' } |
使用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的程式碼:
1 2 3 4 5 6 7 |
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); } } |
這樣就可以檢測Activity的記憶體洩露了。內部實現使用了ActivityLifecycleCallbacks
方法監控所有Activity的生命週期。
檢測其他的
LeakCanary中提供了RefWatcher
,可以用來監控所有的物件。
首先需要例項化RefWatcher
:
1 |
public static RefWatcher sRefWatcher=LeakCanary.install(mContext); |
對於監控的物件使用:
1 |
sRefWatcher.watch(this) |
一般我們是對物件銷燬的時候對物件進行監控,比如內部實現的對於Activity的監控:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() { public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } public void onActivityStarted(Activity activity) { } public void onActivityResumed(Activity activity) { } public void onActivityPaused(Activity activity) { } public void onActivityStopped(Activity activity) { } public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } public void onActivityDestroyed(Activity activity) { ActivityRefWatcher.this.onActivityDestroyed(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成對出現