DialogFragment内存泄露问题能不能一次性改好

简介: 跟内存泄漏说再见

孽缘

自DialogFragment在Android3.0之后作为一种特殊的Fragment引入,官方建议使用DialogFragment代替Dialog或者AllertDialog来实现弹框的功能,因为它可以更好的管理Dialog的生命周期以及可以更好复用。
然而建议虽好,实用须谨慎,在开发的过程中我们只要接入LeakCanary则经常会收到DialogFragment导致内存泄露的小鸟惊喜。

对于为什么DialogFragment会引起内存泄漏,网上资料一大堆,而且分析得也比较详尽,这里就不再多说了。总结起来就是内部的Dialog持有了DialogFragment的引用,导致DialogFragment在该回收的时候无法回收。

那么Dialog是如何持有了DialogFragment的引用的呢?主要就是DialogFragment中在onActivityCreated方法中调用了Dialog的setOnDismissListenersetOnCancelListener这两个方法,将DialogFragmen设置了进去导致的。

别人是怎么解决的

  • setOnDismissListenersetOnCancelListener设置为空

既然是DialogFragment中在onActivityCreated方法中调用了Dialog的setOnDismissListenersetOnCancelListener这两个方法导致的,那么解决方法不是很简单么?
我们继承DialogFragment,然后重写onActivityCreated方法,在super之后再次将setOnDismissListenersetOnCancelListener设置为空不就可以吗?
这么简单的一个小问题还要劳烦我这个高级划水师出马,真是不让人省心啊!!!

然而想法很美好,现实很残酷啊,在super.onActivityCreate()方法中默认已经调用了mDialog.setOnCancelListener(this)mDialog.setOnDismissListener(this)
此时获取的Message有可能是消息池中的某一条消息,而这条消息刚好被一个消息循环所持有不能释放的话,那么这个内存泄漏的问题依然无法解决,所以说这只是一个治标不治本的方法。

  • 建议第三方库一直发送空的消息,保持第三方库的消息循环消息队列一直不为空。

LeakCanary提供了一种解决方案:建议第三方库一直发送空的消息,保持第三方库的消息循环消息队列一直不为空。这种方式只能是提前知道哪个第三方库创建了自己的消息循环,
才能向这个消息循环中发送空消息,这并不能覆盖到所有的第三方创建的消息循环。而且,不断的向一个阻塞线程中发消息,线程时刻处于运行状态,占用线程空间资源。因此,此方案对于客户端开发来说并不可行。

  • 重写DialogFragment

直接拷贝DialogFragment代码至LeakDialogFragment类中,放弃实现DialogInterface.OnCancelListenerDialogInterface.OnDismissListener两个接口,
将这两个接口用静态内部类加弱引用的方式实现,然后将这个静态内部类设置到对应的内部Dialog当中去。

这的确是一个治标又治本的方案,但是工程量略大,而且本来DialogFragment是有谷歌官方维护的,现在变成了由你维护,如果未来官方发现了DialogFragment中产生了bug,默默修复了,那么你复制出来的这个类如何更新同步更新呢?

我不随流

其实一路过来无论是网上的资料还是LeakCanary都是告诉我们是说是DialogFragment发生了内存泄漏,但是罪魁祸首真的是DialogFragment吗?罪魁祸首是DialogFragment内部的Dialog啊,我们为什么一直揪着DialogFragment不放呢?
为什么一直想着给DialogFragment治病呢?能不能给DialogFragment它内部的Dialog治治啊?

通过查看DialogFragment的源码我们发现它内部的mDialog是通过onCreateDialog方法生成的,而且这个方法是开放的。那么我们能不能通过重写这个方法,返回一个不会对DialogFragment持有强引用的Dialog不就完事了吗?

那么我们就重写一个Dialog名为NoLeakDialog:

public class NoLeakDialog extends Dialog {

    public NoLeakDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
    }


    @Override
    public void setOnCancelListener(@Nullable OnCancelListener listener) {
        // 空实现,不持有外部的引用
    }

    @Override
    public void setOnShowListener(@Nullable OnShowListener listener) {
        // 空实现,不持有外部的引用
    }

    @Override
    public void setOnDismissListener(@Nullable OnDismissListener listener) {
        // 空实现,不持有外部的引用
    }

    @Override
    public void setCancelMessage(@Nullable Message msg) {
        // 空实现,不持有外部的引用
    }

    @Override
    public void setDismissMessage(@Nullable Message msg) {
        // 空实现,不持有外部的引用
    }

}

然后在我们的继承的DialogFramment的onCreateDialog方法中返回我们的NoLeakDialog即可。

至此我自己内心不得不为我这个高级划水师惊人的隐藏bug能力叹服,赶紧泡一杯枸杞喝起来准备下一轮的划水。

几杯枸杞水下肚,正准备倒计时下班时,测试带着奸笑跑过来说你这个弹窗不对啊,我点击了空白处隐藏了弹窗,跳转到别的页面后再返回,这个弹窗又自己弹出来了。。。

此时我怀着高级划水师应有的自信直接怼回去说肯定是你的操作方式有问题,一边私底下偷偷打开AS调试起来。。。。

一顿操作猛如虎之后我们发现按返回键和点击空白区域返回键只是调用了Dialog的dismiss放,并没有调用DialogFragment的dismiss方法,因为点击空白区域或者返回键需要Dialog
回调DialogFragment才会调用DialogFragment的dismiss方法,但是我们在NoLeakDialog类中将这些监听器都变成了空实现,所以也就没有了回调。

而在DialogFragment的onDismiss方法方法中我们看到了官方的注释:

  @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        if (!mViewDestroyed) {
            // Note: we need to use allowStateLoss, because the dialog
            // dispatches this asynchronously so we can receive the call
            // after the activity is paused.  Worst case, when the user comes
            // back to the activity they see the dialog again.
            dismissInternal(true, true);
        }
    }

   注释已经很清楚地说明了DialogFragmen会再次弹出

对于这个问题,我们只要在我们NoLeakDialog中重写dismiss方法,将相关事件回调给DialogFragment,然后调用DialogFragment的dismiss或者dismissAllowingStateLoss方法即可。

所以我们最终NoLeakDialog的代码应该这样:

public class NoLeakDialog extends Dialog {

    private WeakReference<DialogFragment> hostFragmentReference;

    public void setHostFragmentReference(DialogFragment hostFragment) {
        this.hostFragmentReference = new WeakReference<>(hostFragment);
    }

    public NoLeakDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
    }


    @Override
    public void setOnCancelListener(@Nullable OnCancelListener listener) {
        // 空实现,不持有外部的引用
    }

    @Override
    public void setOnShowListener(@Nullable OnShowListener listener) {
        // 空实现,不持有外部的引用
    }

    @Override
    public void setOnDismissListener(@Nullable OnDismissListener listener) {
         // 空实现,不持有外部的引用
    }

    @Override
    public void setCancelMessage(@Nullable Message msg) {
          // 空实现,不持有外部的引用
    }

    @Override
    public void setDismissMessage(@Nullable Message msg) {
         // 空实现,不持有外部的引用
    }

    @Override
    public void dismiss() {
        super.dismiss();
        if (null != hostFragmentReference && null != hostFragmentReference.get()) {
            hostFragmentReference.get().dismissAllowingStateLoss();
        }
    }
}

思考

由于我们将setOnDismissListener变成空实现,导致了点击空白区域或者返回键后再次返回界面又弹窗的问题,那么我们将其他的监听器设置为空,
会不会导致其他的问题呢?如果导致了,我们有补救措施不?

由此我们将众监听器都设置为空,那么如果我们真正的使用中需要用到这些键监听怎么办?

答案不是目的,无论何时何地,锻炼自己独立思考的能力助你上青云的推力!!!

关注我,一起进步,人生不止coding!!!

目录
相关文章
|
12月前
cocoscreator查内存泄露,绘制内存监视器
cocoscreator查内存泄露,绘制内存监视器
210 0
一次性讲清楚 Handler 使用不当导致的内存泄露?
一次性讲清楚 Handler 使用不当导致的内存泄露?
一次性讲清楚 Handler 使用不当导致的内存泄露?
|
存储 Web App开发 JSON
检查自己的代码是否存在内存泄露
造成内存泄露的根本原因就是我们写的代码中存在某些对象长期占用内存,得不到释放,且这个对象占用的内存会逐步增加,导致 v8 无法回收,从而造成的服务的异常和不稳定,甚至是服务的中断和崩溃。
282 0
检查自己的代码是否存在内存泄露
|
存储
刷新adapte要注意的地方,和adapter使用的流程
刷新adapte要注意的地方,和adapter使用的流程
|
消息中间件 Android开发
读源码长知识 | Android卡顿真的是因为”掉帧“?
掉帧是因为生产帧速度跟不上消费帧速度。Choreographer 用于同步生产和消费帧的速度。以读源码方式还原掉帧时软件层面发生的事情。
244 0
R代码忘记保存,系统崩溃了怎么办?
跑程序时电脑突然崩溃,程序被强制中断导致代码不见了怎么办? 这些糟心的情况想必每个打工人都不想经历,偏偏我就是那个倒霉蛋,今早打开电脑发现昨晚写的代码忘记保存,心态崩到想当场飙眼泪,冷静下来之后开始寻找解决方案
1312 0
R代码忘记保存,系统崩溃了怎么办?
|
缓存 Java 数据库
如何避免无意间创建多余对象
6 避免创建不必要的对象 从字面意思上来看,大家肯定都知道创建不必要的对象是错误的做法。但这一节其实主要是提醒我们避免无意识的创建不必要对象的代码写法。
如何避免无意间创建多余对象
|
Java
如何避免忘记清理 ThreadLocal ?
hreadLocal 可以解决“线程安全问题”。 也可以作为上下文暂存数据以备后续步骤获取。 但是 ThreadLocal 用不好的确容易产生故障,因而有些团队不允许使用 ThreadLocal。 最核心的一个原因是很容易忘记清理,在线程池环境下复用导致串环境。 那么,有什么优雅的解法没?本文给出自己的一个解法。
708 0
如何避免忘记清理 ThreadLocal ?
为什么有时候在子线程更新UI没报错?
为什么有时候在子线程更新UI没报错?
145 0
为什么有时候在子线程更新UI没报错?
|
Java
频繁 YGC的一段代码
假期重新把之前在新浪博客里面的文字梳理了下,搬到这里。
340 0