DialogFragment的內(nèi)存泄漏問(wèn)題

DialogFragment的內(nèi)存泄漏問(wèn)題

前段時(shí)間,leakcanary報(bào)了一個(gè)有關(guān)dialogFragment的內(nèi)存泄露,當(dāng)時(shí)心里就犯嘀咕了,我這個(gè)DialogFragment業(yè)務(wù)很簡(jiǎn)單呀,也沒(méi)用到handler這些東西。翻了翻源碼,DialogFragment中的dialog,就是通過(guò)handler來(lái)執(zhí)行dismiss,cancel,show這些操作。那難道是源碼造成了泄漏?

源碼分析

在DialogFragment的生命周期onActivityCreated中,mDialog被初始化,同時(shí)設(shè)置了onCancelListener和onDismissListener,這個(gè)listener就是DialogFragment本身。

public void setOnCancelListener(@Nullable OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }

public void setOnDismissListener(@Nullable OnDismissListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnDismissListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
        } else {
            mDismissMessage = null;
        }
    }

咋一看這也沒(méi)啥問(wèn)題。通過(guò)查看日志,發(fā)現(xiàn)每次造成泄漏的線(xiàn)程都不是同一個(gè),可明明DialogFragment就是通過(guò)主線(xiàn)程去啟動(dòng)的,咋會(huì)跑到其他線(xiàn)程去。難道是message復(fù)用機(jī)制導(dǎo)致的?





Message的復(fù)用

obtainMessage會(huì)去執(zhí)行Message.obtain(),這里就是message的復(fù)用機(jī)制了,通過(guò)鏈表結(jié)構(gòu)的sPool獲取message。這個(gè)message為啥可能會(huì)造成泄漏呢?

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

來(lái)看看sPool如何工作的,當(dāng)消息隊(duì)列中處理完事件后,會(huì)執(zhí)行Message.recycleUnchecked,回收message,很明顯各種參數(shù)都被初始化的,咋還會(huì)造成內(nèi)存泄漏呢?雖然message里面沒(méi)有保存什么信息,但message還是屬于HanderThread的。等于說(shuō)所有的HandlerThread對(duì)象,都會(huì)造成內(nèi)存泄漏,只不過(guò)沒(méi)被復(fù)用的時(shí)候,泄漏的只是一個(gè)空的message對(duì)象而已。

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

情景再現(xiàn)

這里舉個(gè)例子說(shuō)明下DialogFragment泄漏的原因。

handlderThreadA的最后一個(gè)message在執(zhí)行完后,被置為message.sPool,同時(shí)進(jìn)入靜默狀態(tài),造成空message的泄漏。DialogFragment在設(shè)置listener時(shí),使用obtainMessage方法。Message.obtain會(huì)復(fù)用sPool,也就有可能獲取到handlderThreadA的空message,給已經(jīng)泄露的空message賦值obj,就會(huì)導(dǎo)致DialogFragment泄露。

找到問(wèn)題的原因,問(wèn)題也就好解決了,在DialogFragment銷(xiāo)毀的時(shí)候,獲取所有存活的HandlerThread,給他們每個(gè)人發(fā)個(gè)空消息,用來(lái)替換被泄漏的message。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。