针对 DialogFragment 状态异常和内存泄漏的解决方案

作者:Jkwen2022

DialogFragment 是一种弹窗实现方式,其本质是 Fragment。

//它的类定义表明,它继承自Fragment,并且拥有Dialog的cancel和dismiss行为
public class DialogFragment extends Fragment
        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener 

因为在 DialogFragment 内部有一个 Dialog 类型的成员变量,所以可以简单理解 DialogFragment 是对 Dialog 进行了一层包装,使得我们可以通过 Fragment 的方式去管理 Dialog 弹窗。

实际场景使用上会存在两个问题,一个是内存泄漏,这个问题是内存泄漏检测时发现的。另一个是状态异常(Can not perform this action after onSaveInstanceState),这个问题是个老问题了。

就这两个问题,我整理了解决方案,方便项目实际处理。

如何解决 DialogFragment 的内存泄漏

这个问题在很多版本中都存在(例如 API 29,androidx 1.0.0),在 androidx 1.1.0 版本上已经做了处理。所以建议使用 androidx 1.1.0 库里的 DialogFragment。

内存泄漏的本质

那使用老版本的 DialogFragment,又是如何引起内存泄漏的呢?

我们需要了解下泄漏本质。

Android 的消息机制中是通过 Message 对象作为信息载体在消息队列中进行处理的,也就是说并不是发出消息后立马就执行该消息,有可能队列里有更优先的消息先处理,导致我们的消息一直处在队列中。

Message 类中有个 Object 类型的成员变量 obj,可以用来做引用指向。在 DialogFragment 里就用到了这点。

//9.0.0 的源码 DialogFragment
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    //省略部分源码。。。
    if (!mDialog.takeCancelAndDismissListeners("DialogFragment", this, this)) {
            throw new IllegalStateException(
                   "You can not set Dialog's OnCancelListener or OnDismissListener");
    }
    //省略部分源码。。。
}
//对应的 Dialog
/** @hide */
public boolean takeCancelAndDismissListeners(@Nullable String msg,
            @Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) {
    //注意这里的两个 Listener 入参,就是 DialogFragment 对象
    if (mCancelAndDismissTaken != null) {
        mCancelAndDismissTaken = null;
    } else if (mCancelMessage != null || mDismissMessage != null) {
        return false;
    }
    //这里把 DialogFragment 对象再传入
    setOnCancelListener(cancel);
    setOnDismissListener(dismiss);
    mCancelAndDismissTaken = msg;

    return true;
}
public void setOnDismissListener(@Nullable OnDismissListener listener) {
    if (mCancelAndDismissTaken != null) {
        throw new IllegalStateException(
                "OnDismissListener is already taken by "
                + mCancelAndDismissTaken + " and can not be replaced.");
    }
    //这里的 listener 就是 DialogFragment 对象
    if (listener != null) {
        //这样一来,DialogFragment 对象就作为创建 mDismissMessage 对象的入参
        //被赋值给了 Message 的 obj 变量。
        mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
    } else {
        mDismissMessage = null;
    }
}

这也正是造成内存泄漏的源头:Dialog 对象的 mDismissMessage 变量的 obj 变量持有着 DialogFragment 对象的引用。

如果在 DialogFragment 销毁时,这个 mDismissMessage 在消息队列里还没处理,那么就意味着 mDismissMessage 对象还在,也就造成 DialogFragment 对象无法被回收,引起内存泄漏。

androidx 里是如何解决内存泄漏

androidx 1.1.0 版本与上述版本相比,主要的差别在于 DialogFragment 的 dismissInternal(boolean,boolean) 方法。

void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
    //省略部分源码。。。
    if (mDialog != null) {
        // Instead of waiting for a posted onDismiss(),null out
        // the listener and call onDismiss() manually to ensure
        // that the callback happens before onDestroy()
        //将 dismissListener 置空的目的就是为了将 mDismissMessage 置空,
        //这样也就断开了对 DialogFragment 对象的引用
        //从而避免了在 DialogFragment 主动销毁(调用dismiss)时,引起的内存泄漏的可能
        mDialog.setOnDismissListener(null);
        mDialog.dismiss();
        //省略部分源码。。。
    }
    //省略部分源码。。。
}

老版本如何自己解决内存泄漏

解决的重点是不让 mDismissMessage 对象持有 DialogFragment 对象,我们可以自定义一个 Dialog,重写 setOnDismissListener(OnDismissListener) 方法,不执行现有的创建 mDismissMessage 逻辑,这样就不会通过 Message 对象持有 DialogFragment 对象了。

然后重写 Dialog 的 dismiss() 方法,调用 super 方法后,再手动回调 OnDismissListener 给到 DialogFragment。由于 mDismissMessage 对象没有被赋值,自然也就不会再发消息了。

最后再自定义 DialogFragment,重写 onCreateDialog(Bundle) 方法,返回上面我们自定义的 Dialog 对象,这样就不会有内存泄漏的可能了。

另外和 DismissListener 类似的 CancelListener,ShowListener 也需要处理。

如何解决 DialogFragment 的状态异常

这个问题更常见,平时在使用 Fragment 的时候就可能经常遇到这个问题。处理的方法 1.将 commit() 方法改为 commitAllowingStateLoss() 也就避免了异常的产生。

但可惜的是,DialogFragment 所有的 show 方法都是用的 commit() 进行 Fragment 展示,反而 dismiss 方法却是有 dismissAllowingStateLoss(),这显然不太行。

通过查阅资料,我们可以这样处理,

//1.在 show 的调用前,判断状态,异常就不展示
@Override
public void show(FragmentManager manager, String tag) {
    if (manager.isStateSaved() || manager.isDestroyed()) {
        //这里可以做下日志记录
        return;
    }
    super.show(manager, tag);
}
//2.通过反射调用,将 commitAllowingStateLoss() 暴露出来调用
public void showAllowingStateLoss(FragmentManager manager, String tag) {
    setBooleanField("mDismissed", false);
    setBooleanField("mShownByMe", true);
    FragmentTransaction ft = manager.beginTransaction();
    ft.add(this, tag);
    ft.commitAllowingStateLoss();
}

private void setBooleanField(String fieldName, boolean value) {
    try {
        Field field = DialogFragment.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.setBoolean(this, value);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

怎么理解 Fragment 的状态异常

粗浅点理解,我觉得就是 Fragment 也是有合理生命周期的,通过状态校验确保数据展示及交互处理的正常,特别是一些对页面状态要求比较高的数据交互,但如果是一些提示性交互,对状态的控制也不需要那么高要求。

所以,我们可以从业务角度出发,对状态要求高的,可以提前做状态判断,避免交给系统报错,不然可能就会造成有感知的崩溃(当然大概率可能是无感崩溃)。对状态没什么要求的,只要满足条件就可以展示或者消失的,那就允许 stateLoss。

原文地址:https://blog.csdn.net/weixin_61845324

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml<?xml version="1.0" encoding="utf-8"?><RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具
文章浏览阅读2.1k次。初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情。事实上,这种想法完全是没有必_有素材的游戏引擎
文章浏览阅读3.2k次,点赞2次,收藏2次。2014年12月从csdn专家福利获得的一本书《Android游戏开发技术实战详解》,尘封了一年多的时间,今天才翻开来看。我认识中的Android,提到Android最先浮现在我脑海中的是那可爱的机器人图标:这个Logo是由Ascender公司设计的,诞生于2010年,其设计灵感源于男女厕所门上的图形符号(真的是灵感无处不在),于是布洛克绘制了一个简单的机器人,它的躯干就像锡罐的形状,头上还有两根_智能手机的特点有哪些?
文章浏览阅读8.1k次,点赞9次,收藏11次。首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。2016-08-26近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya_android 开发和asp.net哪个好 site:blog.csdn.net
文章浏览阅读6.1k次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;而如果没有手机或没有对应型号的手机,又想做对应型号(屏幕尺寸、Android系统版本)的适配,应该怎么办呢?这时Android模拟器就派上用场了。Android模拟器Android SDK自带一个移动模拟器。它是一个可以运行在你电脑上的_安卓移动开发软件怎样预览
文章浏览阅读8.9k次。Google IO 2017 上宣布,将Kotlin语言作为安卓开发的官方语言。Kotlin由JetBrains公司开发,与Java 100%互通,并具备诸多Java尚不支持的新特性。谷歌称还将与JetBrains公司合作,为Kotlin设立一个非盈利基金会。Kotlin 是一个基于 JVM 的静态类型编程语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JV_kotlin为什么被嫌弃
文章浏览阅读9.6w次,点赞17次,收藏35次。有些情况下,不方便使用断点的方式来调试,而是希望在控制台打印输出日志,使用过Eclipse的同学都知道Java可以使用 System.out.println(""); 来在控制台打印输出日志,但是在android studio中却是不行的,还是有差别的,那应该用什么呢?android.util.Log在调试代码的时候我们需要查看调试信息,那我们就需要用Android Log类。android.ut_andirod.studio 为什么不在控制台打印输出
文章浏览阅读8.2k次,点赞2次,收藏8次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;这里讲一下如何为应用添加一个按钮,并为按钮添加Click单击事件处理程序,显示/隐藏另一个按钮。添加按钮在HelloWorld项目的基础上,打开界面布局文件:activity_main.xml切换到Design(设计)模式;在组件But_activity_main.xml按钮隐藏
文章浏览阅读2.9k次,点赞3次,收藏9次。android 开发工具主流的还是Android Studio,当然也有很多人喜欢用Eclipse,也有人喜欢用IntelliJ IDEA ;还有Xamarin这种只需要编写一次代码,可以编译多种平台可运行的强大工具。但是它又真的强大吗?就我看来没有,身边很多人还是在用Android Studio、XCode开发应用,没见谁在用Xamarin之类的工具。系统要求WindowsMicrosoft®_android开发下载安装
文章浏览阅读4.2k次,点赞7次,收藏26次。你知道Hello World程序的由来吗?对于大多数编程语言的学习来说,真正入门的一课就是 Hello World!会而不难,难而不会。虽然很多人写过关于Android开发Hello World的文章,但随着时间的推移,开发工具、技术的进步,可能有些已经过时了。我就记录一下当下我所经历的第一个Android APP HelloWorld。一、准备1、开发环境参考:Android Studio 下载_android helloworld textview 句柄获取
这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android...
这篇文章主要介绍“Android岛屿数量算法怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android岛屿数量算...
本篇内容主要讲解“Android如何开发MQTT协议的模型及通信”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Andro...