Android開發中因爲有限的內存,以及防止OOM問題出現,解決內存泄漏問題將是開發者一直持續下去的工作。本文就分析了不當使用(持有)context導致的內存泄漏。
1. 爲什麼使用Context有可能會導致內存泄漏?
首先從context的本質談起,context名稱上代表了上下文,實質上是Application、Activity或Service的一個引用。因此如果有生命週期較長的對象,比如線程持有了一個context引用,那麼在線程結束前,這個context是無法得到釋放的,這也意味着context代表的activity、service無法被GC回收,這就發生了內存泄漏。
2. 來個例子
還記得屏幕旋轉,會銷燬當前Activity,重新創建一個新的Activity的事嗎?我們就以這個操作來做泄漏內存的示例,原理是讓老的Activity無法被銷燬。
private static Drawable mBackgroudImage;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);//持有context
label.setText("演示旋轉屏幕導致內存泄漏");
if (mBackgroudImage == null) {
mBackgroudImage = getDrawable(R.drawable.beauty); //這個是隨便在網上找的一張圖片
}
label.setBackgroundDrawable(mBackgroudImage);
setContentView(label);
}
好,運行起來,將屏幕旋轉幾次,這個代碼已經泄漏了老的Activity。
是不是內心有點小懷疑?
我們往下看截圖可以證明。
3. 使用Android Monitor驗證內存泄漏情況
怎麼驗證,是否發生了內存泄漏呢?
我們可以通過Android Studio提供的Memory Monitor工具來觀察Java Heap情況。
打開Android Monitor,如圖所示,我們旋轉幾次屏幕後,點擊“Dump Java Heap”按鈕
接下來會,彈出一個hprof編輯框,在右邊的紅圈部分點開“分析界面”
好,在新彈出的分析界面,點擊綠色箭頭開始分析,下半部分就是分析結果。
注意我紅圈標註出來的地方,發現SplashActivity竟然有2個實例!!!!這就證明確實發生了內存泄漏,有一個SplashActivity泄漏了。
4. 分析原因。
-
首先我們發現,這段代碼定義了一個沒有初始化的靜態Drawable變量。衆所周知,靜態變量是屬於一種跟“類”而非“實例”綁定在一起的對象。是一個被所有實例共享的成員變量,當給它賦值的時候,實際上是賦值給了整個“類對象”。
-
出於省事的考慮,代碼中並沒有在每次onCreate中去加載賦值mBackgroudImage,而是檢測它如果不爲空,就不再賦值。
-
根據官網的說法,將一個Drawable對象賦給某個View的時候,這個View同時也作爲一個callBalk被Drawable對象給引用了。即,Drawable對象持有對應View的引用。這個可以看setBackgroundDrawable()源碼,確實是沒錯的。
@Deprecated
public void setBackgroundDrawable(Drawable background) {
//省略其他內容,可以看到確實View
//作爲一個callBalk被Drawable對象給引用了
// Set callback last, since the view may still be initializing.
background.setCallback(this);
}
-
事實上,當我們第一次運行起來SplashActivity時,會給mBackgroudImage賦值,當屏幕旋轉的時候則不會第二次賦值。因此,mBackgroudImage仍舊持有第一次的TextView的引用。
-
而TextView的新建需要傳入一個context,因此它持有了SplashActivity的一個引用。
-
所以,相當於靜態變量mBackgroudImage間接的、始終持有第一個創建出來SplashActivity沒有釋放。
-
所以就發生了內存泄漏!!!
總結
通過本文的分析,讓一個生命週期長於Activity的對象持有context引用很容易就發生了內存泄漏,開發者需要特別警惕!