Android Context導致的內存泄漏分析(示例代碼+分析工具使用)

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引用很容易就發生了內存泄漏,開發者需要特別警惕!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章