美文网首页
DialogFragment的内存泄漏问题

DialogFragment的内存泄漏问题

作者: Raven | 来源:发表于2020-07-27 15:33 被阅读0次

    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的内存泄漏问题

          本文链接:https://www.haomeiwen.com/subject/stbzlktx.html