java.lang.OutOfMemoryError
at android.graphics.Bitmap.nativeCreate(Bitmap.java:-2)
at android.graphics.Bitmap.createBitmap(Bitmap.java:689)
at com.squareup.ui.SignView.createSignatureBitmap(SignView.java:121)
誰也不會喜歡 OutOfMemoryError
在 Square Register 中, 在簽名頁面,我們把客戶的簽名畫在 bitmap
cache 上。 這個 bitmap 的尺寸幾乎和屏幕的尺寸一樣大,在創建這個 bitmap 對象時,經常會引發 OutOfMemoryError
,簡稱OOM
。
當時,我們嘗試過一些解決方案,但都沒解決問題
-
使用 Bitmap.Config.ALPHA_8 因爲,簽名僅有黑色。
-
捕捉
OutOfMemoryError
, 嘗試 GC 並重試(受 GCUtils 啓發)。 -
我們沒想過在 Java heap 內存之外創建 bitmap 。苦逼的我們,那會 Fresco 還不存在。
路子走錯了
其實 bitmap 的尺寸不是真正的問題,當內存吃緊的時候,到處都有可能引發 OO。在創建大對象,比如 bitmap 的時候,更有可能發生。OOM 只是一個表象,更深層次的問題可能是: 內存泄露。
什麼是內存泄露
一些對象有着有限的生命週期。當這些對象所要做的事情完成了,我們希望他們會被回收掉。但是如果有一系列對這個對象的引用,那麼在我們期待這個對象生命週期結束的時候被收回的時候,它是不會被回收的。它還會佔用內存,這就造成了內存泄露。持續累加,內存很快被耗盡。
比如,當 Activity.onDestroy
被調用之後,activity
以及它涉及到的 view 和相關的 bitmap 都應該被回收。但是,如果有一個後臺線程持有這個 activity 的引用,那麼 activity 對應的內存就不能被回收。這最終將會導致內存耗盡,然後因爲 OOM 而 crash。
對戰內存泄露
排查內存泄露是一個全手工的過程,這在 Raizlabs 的 Wrangling Dalvik 系列文章中有詳細描述。
以下幾個關鍵步驟:
-
通過 Bugsnag, Crashlytics 或者 Developer Console 等統計平臺,瞭解
OutOfMemoryError
情況。 -
重現問題。爲了重現問題,機型非常重要,因爲一些問題只在特定的設備上會出現。爲了找到特定的機型,你需要想盡一切辦法,你可能需要去買,去借,甚至去偷。 當然,爲了確定復現步驟,你需要一遍一遍地去嘗試。一切都是非常原始和粗暴的。
-
在發生內存泄露的時候,把內存 Dump 出來。具體看這裏。
-
計算這個對象到 GC roots 的最短強引用路徑。
-
確定引用路徑中的哪個引用是不該有的,然後修復問題。
很複雜對吧?
如果有一個類庫能在發生 OOM 之前把這些事情全部都搞定,然後你只要修復這些問題就好了,豈不妙哉!
LeakCanary
LeakCanary 是一個檢測內存泄露的開源類庫。你可以在 debug 包種輕鬆檢測內存泄露。
先看一個例子:
class Cat {
}
class Box {
Cat hiddenCat;
}
class Docker {
// 靜態變量,將不會被回收,除非加載 Docker 類的 ClassLoader 被回收。
static Box container;
}
// ...
Box box = new Box();
// 薛定諤之貓
Cat schrodingerCat = new Cat();
box.hiddenCat = schrodingerCat;
Docker.container = box;
創建一個RefWatcher
,監控對象引用情況。
// 我們期待薛定諤之貓很快就會消失(或者不消失),我們監控一下
refWatcher.watch(schrodingerCat);
當發現有內存泄露的時候,你會看到一個很漂亮的 leak trace 報告:
- GC ROOT static Docker.container
- references Box.hiddenCat
- leaks Cat instance
我們知道,你很忙,每天都有一大堆需求。所以我們把這個事情弄得很簡單,你只需要添加一行代碼就行了。然後 LeakCanary 就會自動偵測 activity 的內存泄露了。
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
然後你會在通知欄看到這樣很漂亮的一個界面:
結論
使用 LeakCanary 之後,我們修復了我們 APP 中相當多的內存泄露。我們甚至發現了 Android SDK 中的一些內存泄露問題。
結果是驚豔的,我們減少了 94% 的由 OOM 導致的 crash。
如果你也想消滅 OOM crash,那還猶豫什麼,趕快使用 LeakCanary
相關鏈接:
-
一個非常簡單的 LeakCanary demo: https://github.com/liaohuqiu/leakcanary-demo
-