DialogFragment的内存泄漏问题

DialogFragment的内存泄漏问题

前段时间,leakcanary报了一个有关dialogFragment的内存泄露,当时心里就犯嘀咕了,我这个DialogFragment业务很简单呀,也没用到handler这些东西。翻了翻源码,DialogFragment中的dialog,就是通过handler来执行dismiss,cancel,show这些操作。那难道是源码造成了泄漏?

源码分析

在DialogFragment的生命周期onActivityCreated中,mDialog被初始化,同时设置了onCancelListener和onDismissListener,这个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;
        }
    }

咋一看这也没啥问题。通过查看日志,发现每次造成泄漏的线程都不是同一个,可明明DialogFragment就是通过主线程去启动的,咋会跑到其他线程去。难道是message复用机制导致的?





Message的复用

obtainMessage会去执行Message.obtain(),这里就是message的复用机制了,通过链表结构的sPool获取message。这个message为啥可能会造成泄漏呢?

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();
    }

来看看sPool如何工作的,当消息队列中处理完事件后,会执行Message.recycleUnchecked,回收message,很明显各种参数都被初始化的,咋还会造成内存泄漏呢?虽然message里面没有保存什么信息,但message还是属于HanderThread的。等于说所有的HandlerThread对象,都会造成内存泄漏,只不过没被复用的时候,泄漏的只是一个空的message对象而已。

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++;
            }
        }
    }

情景再现

这里举个例子说明下DialogFragment泄漏的原因。

handlderThreadA的最后一个message在执行完后,被置为message.sPool,同时进入静默状态,造成空message的泄漏。DialogFragment在设置listener时,使用obtainMessage方法。Message.obtain会复用sPool,也就有可能获取到handlderThreadA的空message,给已经泄露的空message赋值obj,就会导致DialogFragment泄露。

找到问题的原因,问题也就好解决了,在DialogFragment销毁的时候,获取所有存活的HandlerThread,给他们每个人发个空消息,用来替换被泄漏的message。

你可能感兴趣的:(DialogFragment的内存泄漏问题)