DialogFragment内存泄漏解决方案

在项目中有使用到BottomSheetDialogFragment,在测试中始终出现内存泄漏,在LeakCanary中看到有message的引用,本着百度一下就好了的心态上网查下就好了。结果发现并没有太好的解决方案。
先来分析下DialogFragment内存泄漏的原因:
在这里插入图片描述
此处可以看到是Handler持有的Message对象引起了内存泄漏,在Dialog源码中发现有一个mListenersHandler的变量,发现就是这个变量用来分发dimiss,show,cancel的事件

 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;
        }
    }
 private static final class ListenersHandler extends Handler {
        private final WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

在mListenersHandler的实现中,发现onDismiss()中持有了dialog的引用,大概可以猜出是这个引用没有被释放导致的内存泄漏。

 /**
     * Dismiss this dialog, removing it from the screen. This method can be
     * invoked safely from any thread.  Note that you should not override this
     * method to do cleanup when the dialog is dismissed, instead implement
     * that in {@link #onStop}.
     */
    @Override
    public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            mHandler.post(mDismissAction);
        }
    }

    @UnsupportedAppUsage
    void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }

在dismiss方法中调用了sendDismissMessage()

private void sendDismissMessage() {
        if (mDismissMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mDismissMessage).sendToTarget();
        }
    }

这个 mDismissMessage 持有了Dialog的引用,在dialogFragment中调用此方法传递的是dialogFragment中mDialog对象,即持有dialogFragment的引用导致的内存泄漏。

但是有点不明白,为什么会导致泄漏?
在网上看大神说是其他线程持有mDismissMessage,而mDismissMessage又持有dialog的引用导致的内存泄漏。

盗张图:
在这里插入图片描述
大体就是当Dialog关闭dismiss时,刚好取出的是已经回收的消息,并且这条消息被另一个线程所引用,此时的mDimissMessage重新引用了DialogFragment,因此不能被回收,造成内存泄露。
说的不太清楚,可以看下这篇文章分析的挺好:传送门

网上的解决方案大都是将setOnDismissListener() 置空,但是这样会有BUG;还有就是重写DialogFragment,在setOnDismissListener()传一个弱引用的DialogFragment,但比较麻烦

解决方案:
网上搜到了一个解决方案,亲测可用


/**
 * https://medium.com/square-corner-blog/a-small-leak-will-sink-a-great-ship-efbae00f9a0f
 * square 的解决方案。View detach 的时候就将引用置为 null 了,
 * 会导致 Dialog 重新显示的时候,原来设置的 Listener 收不到回调
 *
 * 在 show 之后,调用 clearOnDetach
 * */
class ClearOnDetachListener(private var delegate: DialogInterface.OnClickListener?) :
        DialogInterface.OnClickListener {
    override fun onClick(dialog: DialogInterface?, which: Int) {
        delegate?.onClick(dialog, which)
    }

    fun clearOnDetach(dialog: Dialog) {
        dialog.window?.decorView?.viewTreeObserver?.addOnWindowAttachListener(object :
                ViewTreeObserver.OnWindowAttachListener {
            override fun onWindowDetached() {
                delegate = null
            }

            override fun onWindowAttached() {
            }
        })
    }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
        val clearOnDetachListener = ClearOnDetachListener(DialogInterface.OnClickListener { dialog, which -> })
        this.dialog?.let {
            clearOnDetachListener.clearOnDetach(it)
        }
        super.onActivityCreated(savedInstanceState)
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DialogFragment内存泄漏通常是由于它与Activity的生命周期不一致而导致的。比如,在DialogFragment中使用了Activity的Context,而DialogFragment在Activity销毁之前没有被销毁,那么Activity所持有的DialogFragment引用就会一直存在,从而导致内存泄漏。 解决方法有以下几种: 1. 尽量避免在DialogFragment中持有Activity的引用,使用Application Context或者其他非Activity的Context。 2. 在DialogFragment中使用静态内部类或者弱引用来持有Activity的引用,从而避免内存泄漏。 3. 在DialogFragment的onDetach()方法中将Activity的引用置为null,从而解除对Activity的引用,让GC可以回收DialogFragment。 以下是一个使用静态内部类来避免DialogFragment内存泄漏的例子: ``` public class MyDialogFragment extends DialogFragment { private static WeakReference<Activity> sActivityRef; public static void show(Activity activity) { sActivityRef = new WeakReference<>(activity); MyDialogFragment dialogFragment = new MyDialogFragment(); dialogFragment.show(activity.getFragmentManager(), "MyDialogFragment"); } @Override public void onAttach(Context context) { super.onAttach(context); if (sActivityRef != null) { Activity activity = sActivityRef.get(); if (activity != null) { // do something with activity } } } @Override public void onDetach() { super.onDetach(); sActivityRef = null; } } ``` 使用时,可以调用MyDialogFragment.show()方法来显示DialogFragment,这样就可以避免在DialogFragment中持有Activity的引用,避免内存泄漏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值