1. 程式人生 > >【Android】小白進階之WeakReference弱引用基礎淺析

【Android】小白進階之WeakReference弱引用基礎淺析

作為一枚 android 應用開發小白,工作中凡是遇到不懂的點都要做一番總結,希望對你有益。

1、弱引用定義

弱引用,與強引用相對,GC 在回收時會忽略掉弱引用物件(忽略掉這種引用關係)。

即,就算弱引用指向了某個物件,但只要該物件沒有被強引用指向,該物件也會被GC檢查時回收掉

2、示例

2.1、以 Handler 記憶體洩漏為例

Java使用有向圖機制,通過 GC 自動檢查記憶體中的物件,如果 GC 發現一個或一組物件為不可達的狀態,則將該物件從記憶體中回收。也就是說:一個物件不被任何引用所指向,則該物件會在被GC發現的時候回收。

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    };
};

當使用內部類(或者匿名類)來建立 Handler 的時候,Handler 物件會隱式地持有一個外部類的物件(通常是Activity)的引用(否則怎麼可能通過Handler來操作Activity的View?)。

而 Handler 通常會伴隨著一個耗時的後臺執行緒(比如:拉取網路圖片),該後臺執行緒在任務執行完畢後,通過訊息機制通知 Handler,然後 Handler 把圖片更新到介面上。

假設使用者在網路請求過程中關閉了 Activity,正常情況下這個 Activity 不再被使用,就有可能被 GC 回收,但此時執行緒尚未執行完畢,而該執行緒持有 Handler 的引用(不然怎麼傳送訊息給Handler?),Handler 又持有 Activity 的引用,就導致該 Activity無法被回收(記憶體洩漏),直到網路請求結束(如:圖片下載完畢)。

另外如果執行了 Handler 的 postDelayed(),該方法會將 Handler 裝入一個 Message,並把該 Message 推到 MessageQueue 中,由此產生了一條鏈式結構:MessageQueue->Message->Handler->Activity,導致 Activity 被持有引用而無法被回收。

總結:例項物件被其他物件持有引用,而無法被回收。

而弱引用便可以解決該問題:

被弱引用關聯的物件只能生存到下一次 GC 發生之前。當GC工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。

public class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        // bitmapWorkerTaskReference 例項關聯 BitmapWorkerTask
        bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
                bitmapWorkerTask);
    }
    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

此時 WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference 關聯了 bitmapWorkerTask 例項(可認為兩者是“好朋友”關係),在虛擬機器看來 bitmapWorkerTask 例項是垃圾時,但 WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference 不是垃圾。但垃圾並不會立即被回收,也就是仍然可以使用物件 bitmapWorkerTask。

若該物件已被清理,則必須重新構建物件,並再一次關聯。

優點:

可以申請任意多個 bitmapWorkerTask 例項物件,並與弱引用物件關聯,使用前先判斷是否已被釋放;如果已被釋放,則重新申請,若未被釋放,則直接使用。

使用了上述程式碼後,使用者在關閉 Activity 之後,就算後臺執行緒還沒有結束,但由於僅有一個來自 Handler 的弱引用指向 Activity,所有 GC 仍然會在檢查的時候把 Activity 回收掉。

2.2、詳細示例

對於 handler 當一個 android 主執行緒被建立的時候,同時會有一個 Looper 物件被建立,而這個 Looper 物件會實現一個 MessageQueue (訊息佇列),當我們建立一個 handler 物件時,而 handler 的作用就是放入和取出訊息從這個訊息佇列中,每當我們通過 handler 將一個 msg 放入訊息佇列時,這個 msg 就會持有一個 handler 物件的引用。因此當 Activity 被結束後,這個 msg 在被取出來之前,這 msg 會繼續存活,但是這個 msg 持有 handler 的引用,而 handler 在 Activity 中建立,會持有 Activity 的引用,因而當 Activity結束後,Activity 物件並不能夠被 gc 回收,因而出現記憶體洩漏。

根本原因:

Activity 在被結束之後,MessageQueue 並不會隨之被結束,如果這個訊息佇列中存在 msg,則導致持有 handler 的引用,但是又由於 Activity 被結束了,msg 無法被處理,從而導致永久持有 handler 物件,handler 永久持有 Activity 物件,於是發生記憶體洩漏。

解決方式:

a、將 hanlder 物件宣告為靜態的物件

b、使用靜態內部類,通過 WeakReference 實現對 Activity 的弱引用

static 型別可以解決這個問題,在java中所有非靜態的物件都會持有當前類的強引用,而靜態物件則只會持有當前類的弱引用。宣告為靜態後,handler 將會持有一個 Activity 的弱引用,而弱引用會很容易被 gc 回收,這樣就能解決 Activity 結束後,gc 卻無法回收的情況。

public class TestActivity extends Activity {

    public static class MyHandler extends Handler
    {
        protected WeakReference<Activity> m_oReference;

        public MyHandler(Activity activity)
        {
            // 弱引用 m_oReference 關聯到 activity
            m_oReference = new WeakReference<Activity>(activity);
        }
    };

    private MyHandler mReceiveMsgFromThread = new MyHandler(this)
    {
        @Override
        public void handleMessage(Message msg)
        {
            try
            {
                super.handleMessage(msg);

                // 判斷是否被釋放
                if(null == m_oReference.get())
                {
                    return;
                }

                switch (msg.what)
                {
                  // null
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    };
}

refer:

https://www.cnblogs.com/CVstyle/p/6395745.html