使用Android studio分析記憶體洩露
This post is a permitted translation of badoo Tech Blog and I add some text and screenshots for android studio users.
Origin Author: Dmytro Voronkevych
follow badoo on Tweet
Translator: Miao1007
截至androidstudio1.5,內部已經支援分析洩漏,故本文不再更新。
Android使用java作為平臺開發,幫助了我們解決了很多底層問題,比如記憶體管理,平臺依賴等等。然而,我們也經常遇到OutOfMemoey
接下來是一個Handler Leak
的例子,它一般會在編譯器中被警告提示。
所需要的工具
- Android Studio 1.1 or higher
- Eclipse MemoryAnalyzer
示例程式碼
public class NonStaticNestedClassLeakActivity extends ActionBarActivity {
TextView textView;
public static final String TAG = NonStaticNestedClassLeakActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_non_static_nested_class_leak);
textView = (TextView)findViewById(R.id.textview);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override public void
textView.setText("Done");
}//a mock for long time work
}, 800000L);
}
}
這是一個非常基礎的Activity.注意這個匿名的Runnable
被送到了Handler中,而且延遲非常的長。現在我們執行這個Activity,反覆旋轉螢幕,然後匯出記憶體並分析。
匯入 Memory 到Eclipse MemoryAnalyzer
使用Androidstudio匯出 heap dump
Android Studio dump Memory Analyze
- 點選左下角的Android
- 選中你的程式的包名
- 點選 initiates garbage collection on selected vm
- 點選 dump java heap for selected client
開啟MAT,進行分析
MAT是對java heap中變數分析的一個工具,它可以用於分析記憶體洩露。
- 點選
OQL
圖示 - 在視窗輸入
select * from instanceof android.app.Activity
並按Ctrl + F5
或者!
按鈕 - 奇蹟出現了,現在你發現洩露了許多的activity
- 這個真是相當的不容樂觀,我們來分析一下為什麼GC沒有回收它
EMA
在OQL(Object Query Language)視窗下輸入的查詢命令可以獲得所有在記憶體中的Activities,這段查詢程式碼是不是非常簡單高效呢?
點選一個activity物件,右鍵選中Path to GC roots
GC root
Message in looper hold a reference to Activity
在開啟的新視窗中,你可以發現,你的Activity是被this$0
所引用的,它實際上是匿名類對當前類的引用。this$0
又被callback
所引用,接著它又被Message
中一串的next
所引用,最後到主執行緒才結束。
任何情況下你在class中建立非靜態內部類,內部類會(自動)擁有對當前類的一個強引用。
一旦你把Runnable
或者Message
傳送到Handler
中,它就會被放入LooperThread
的訊息佇列,並且被保持引用,直到Message
被處理。傳送postDelayed這樣的訊息,你輸入延遲多少秒,它就會洩露至少多少秒。而傳送沒有延遲的訊息的話,當佇列中的訊息過多時,也會照成一個臨時的洩露。
嘗試使用static inner class來解決
現在把Runnable
變成靜態的class
StaticClass
現在,搖一搖手機,匯出記憶體
StaticClass_memory_analyze
為什麼又出現了洩露呢?我們看一看Activities
的引用.
StaticClass_memory_analyze_explained
看到下面的mContext
的引用了嗎,它被mTextView
引用,這樣說明,使用靜態內部類還遠遠不夠,我們仍然需要修改。
使用弱引用 + static Runnable
現在我們把剛剛記憶體洩露的罪魁禍首 - TextView改成弱引用。
StaticClassWithWeakRef_code
再次注意我們對TextView保持的是弱引用,現在讓它執行,搖晃手機
小心地操作WeakReferences,它們隨時可以為空,在使用前要判斷是否為空.
StaticClassWithWeakRef_memory_analyze
哇!現在只有一個Activity的例項了,這回終於解決了我們的問題。
所以,我們應該記住:
- 使用靜態內部類
- Handler/Runnable的依賴要使用弱引用。
如果你把現在的程式碼與開始的程式碼相比,你會發現它們大不相同,開始的程式碼易懂簡介,你甚至可以腦補出執行結果。
而現在的程式碼更加複雜,有很多的模板程式碼,當把postDelayed
設定為一個短時間,比如50ms
的情況下,寫這麼多程式碼就有點虧了。其實,還有一個更簡單的方法。
onDestroy中手動控制宣告週期
Handler可以使用removeCallbacksAndMessages(null)
,它將移除這個Handler所擁有的Runnable
與Message
。
//Fixed by manually control lifecycle
@Override protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
現在執行,旋轉手機,匯出記憶體
removeCallbacks_memory_analyze
Good!只有一個例項。
這樣寫可以讓你的程式碼更加簡潔與可讀。唯一要記住的就是就是要記得在生命週期onDestory的時候手動移除所有的訊息。
使用WeakHander
(這個是第三方庫,我就不翻譯了,大家去Github上去學習吧)
結論
在Handler中使用postDelayed
需要額外的注意,為了解決問題,我們有三種方法
- 使用靜態內部Handler/Runnable + 弱引用
- 在onDestory的時候,手動清除Message
這三種你可以任意選用,第二種看起來更加合理,但是需要額外的工作。第三種方法是我最喜歡的,當然你也要注意WeakHandler不能與外部的強引用共同使用。
文/BlackSwift(簡書作者)
原文連結:http://www.jianshu.com/p/c49f778e7acf
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。