DialogFragment内存泄露问题能不能一次性改好
孽缘
自DialogFragment在Android3.0之后作为一种特殊的Fragment引入,官方建议使用DialogFragment代替Dialog或者AllertDialog来实现弹框的功能,因为它可以更好的管理Dialog的生命周期以及可以更好复用。
然而建议虽好,实用须谨慎,在开发的过程中我们只要接入LeakCanary则经常会收到DialogFragment导致内存泄露的小鸟惊喜。
对于为什么DialogFragment会引起内存泄漏,网上资料一大堆,而且分析得也比较详尽,这里就不再多说了。总结起来就是内部的Dialog持有了DialogFragment的引用,导致DialogFragment在该回收的时候无法回收。
那么Dialog是如何持有了DialogFragment的引用的呢?主要就是DialogFragment中在onActivityCreated
方法中调用了Dialog的setOnDismissListener
和setOnCancelListener
这两个方法,将DialogFragmen设置了进去导致的。
别人是怎么解决的
- 将
setOnDismissListener
和setOnCancelListener
设置为空
既然是DialogFragment中在onActivityCreated
方法中调用了Dialog的setOnDismissListener
和setOnCancelListener
这两个方法导致的,那么解决方法不是很简单么?
我们继承DialogFragment,然后重写onActivityCreated
方法,在super之后再次将setOnDismissListener
和setOnCancelListener
设置为空不就可以吗?
这么简单的一个小问题还要劳烦我这个高级划水师出马,真是不让人省心啊!!!
然而想法很美好,现实很残酷啊,在super.onActivityCreate()方法中默认已经调用了mDialog.setOnCancelListener(this)
和mDialog.setOnDismissListener(this)
。
此时获取的Message有可能是消息池中的某一条消息,而这条消息刚好被一个消息循环所持有不能释放的话,那么这个内存泄漏的问题依然无法解决,所以说这只是一个治标不治本的方法。
- 建议第三方库一直发送空的消息,保持第三方库的消息循环消息队列一直不为空。
LeakCanary提供了一种解决方案:建议第三方库一直发送空的消息,保持第三方库的消息循环消息队列一直不为空。这种方式只能是提前知道哪个第三方库创建了自己的消息循环,
才能向这个消息循环中发送空消息,这并不能覆盖到所有的第三方创建的消息循环。而且,不断的向一个阻塞线程中发消息,线程时刻处于运行状态,占用线程空间资源。因此,此方案对于客户端开发来说并不可行。
- 重写DialogFragment
直接拷贝DialogFragment代码至LeakDialogFragment类中,放弃实现DialogInterface.OnCancelListener
和DialogInterface.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);}@Overridepublic void setOnCancelListener(@Nullable OnCancelListener listener) {// 空实现,不持有外部的引用}@Overridepublic void setOnShowListener(@Nullable OnShowListener listener) {// 空实现,不持有外部的引用}@Overridepublic void setOnDismissListener(@Nullable OnDismissListener listener) {// 空实现,不持有外部的引用}@Overridepublic void setCancelMessage(@Nullable Message msg) {// 空实现,不持有外部的引用}@Overridepublic void setDismissMessage(@Nullable Message msg) {// 空实现,不持有外部的引用}}
然后在我们的继承的DialogFramment的onCreateDialog
方法中返回我们的NoLeakDialog即可。
至此我自己内心不得不为我这个高级划水师惊人的隐藏bug能力叹服,赶紧泡一杯枸杞喝起来准备下一轮的划水。
几杯枸杞水下肚,正准备倒计时下班时,测试带着奸笑跑过来说你这个弹窗不对啊,我点击了空白处隐藏了弹窗,跳转到别的页面后再返回,这个弹窗又自己弹出来了。。。
此时我怀着高级划水师应有的自信直接怼回去说肯定是你的操作方式有问题,一边私底下偷偷打开AS调试起来。。。。
一顿操作猛如虎之后我们发现按返回键和点击空白区域返回键只是调用了Dialog的dismiss放,并没有调用DialogFragment的dismiss方法,因为点击空白区域或者返回键需要Dialog
回调DialogFragment才会调用DialogFragment的dismiss方法,但是我们在NoLeakDialog类中将这些监听器都变成了空实现,所以也就没有了回调。
而在DialogFragment的onDismiss方法方法中我们看到了官方的注释:
@Overridepublic 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);}@Overridepublic void setOnCancelListener(@Nullable OnCancelListener listener) {// 空实现,不持有外部的引用}@Overridepublic void setOnShowListener(@Nullable OnShowListener listener) {// 空实现,不持有外部的引用}@Overridepublic void setOnDismissListener(@Nullable OnDismissListener listener) {// 空实现,不持有外部的引用}@Overridepublic void setCancelMessage(@Nullable Message msg) {// 空实现,不持有外部的引用}@Overridepublic void setDismissMessage(@Nullable Message msg) {// 空实现,不持有外部的引用}@Overridepublic void dismiss() {super.dismiss();if (null != hostFragmentReference && null != hostFragmentReference.get()) {hostFragmentReference.get().dismissAllowingStateLoss();}}
}
思考
由于我们将setOnDismissListener变成空实现,导致了点击空白区域或者返回键后再次返回界面又弹窗的问题,那么我们将其他的监听器设置为空,
会不会导致其他的问题呢?如果导致了,我们有补救措施不?
由此我们将众监听器都设置为空,那么如果我们真正的使用中需要用到这些键监听怎么办?
答案不是目的,无论何时何地,锻炼自己独立思考的能力助你上青云的推力!!!
关注我,一起进步,人生不止coding!!!
相关文章:
DialogFragment内存泄露问题能不能一次性改好
孽缘 自DialogFragment在Android3.0之后作为一种特殊的Fragment引入,官方建议使用DialogFragment代替Dialog或者AllertDialog来实现弹框的功能,因为它可以更好的管理Dialog的生命周期以及可以更好复用。 然而建议虽好,实用须谨慎,…...

java学习--多线程
多线程 了解多线程 多线程是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。 并发和并行 并行:在同一时刻,有多个指令在CPU上同时执行并发࿱…...

90后阿里P7技术专家晒出工资单:狠补了这个,真香...
最近一哥们跟我聊天装逼,说他最近从阿里跳槽了,我问他跳出来拿了多少?哥们表示很得意,说跳槽到新公司一个月后发了工资,月入5万多,表示很满足!这样的高薪资着实让人羡慕,我猜这是税后…...

2023美赛C题:Wordle筛选算法
Wordle 规则介绍 Wordle 每天会更新一个5个字母的单词,在6次尝试中猜出单词就算成功。每个猜测必须是一个有效的单词(不能是不能组成单词的字母排列)。 每次猜测后,字母块的颜色会改变,颜色含义如下: 程…...

SpringBoot 集成 Kafka
SpringBoot 集成 Kafka1 安装 Kafka2 创建 Topic3 Java 创建 Topic4 SpringBoot 项目4.1 pom.xml4.2 application.yml4.3 KafkaApplication.java4.4 CustomizePartitioner.java4.5 KafkaInitialConfig.java4.6 SendMessageController.java5 测试1 安装 Kafka Docker 安装 Kafk…...

OpenCV 图像金字塔算子
本文是OpenCV图像视觉入门之路的第14篇文章,本文详细的介绍了图像金字塔算子的各种操作,例如:高斯金字塔算子 、拉普拉斯金字塔算子等操作。 高斯金字塔中的较高级别(低分辨率)是通过先用高斯核对图像进行卷积再删除偶…...

【自学Linux】Linux一切皆文件
Linux一切皆文件 Linux一切皆文件教程 Linux 中所有内容都是以文件的形式保存和管理的,即一切皆文件,普通文件是文件,目录是文件,硬件设备(键盘、监视器、硬盘、打印机)是文件,就连套接字&…...

CUDA C++扩展的详细描述
CUDA C扩展的详细描述 文章目录CUDA C扩展的详细描述CUDA函数执行空间说明符B.1.1 \_\_global\_\_B.1.2 \_\_device\_\_B.1.3 \_\_host\_\_B.1.4 Undefined behaviorB.1.5 __noinline__ and __forceinline__B.2 Variable Memory Space SpecifiersB.2.1 \_\_device\_\_B.2.2. \_…...

为什么重写equals必须重写hashCode
关于这个问题,看了网上很多答案,感觉都参差不齐,没有答到要点,这次就记录一下! 首先我们为什么要重写equals?这个方法是用来干嘛的? public boolean equals (Object object&#x…...

< 每日小技巧:N个很棒的 Vue 开发技巧, 持续记录ing >
每日小技巧:6 个很棒的 Vue 开发技巧👉 ① Watch 妙用> watch的高级使用> 一个监听器触发多个方法> watch 监听多个变量👉 ② 自定义事件 $emit() 和 事件参数 $event👉 ③ 监听组件生命周期常规写法hook写法ὄ…...

数据结构与算法之二分查找分而治之思想
决定我们成为什么样人的,不是我们的能力,而是我们的选择。——《哈利波特与密室》二分查找是查找算法里面是很优秀的一个算法,特别是在有序的数组中,这种算法思想体现的淋漓尽致。一.题目描述及其要求请实现无重复数字的升序数组的…...

训练自己的中文word2vec(词向量)--skip-gram方法
训练自己的中文word2vec(词向量)–skip-gram方法 什么是词向量 将单词映射/嵌入(Embedding)到一个新的空间,形成词向量,以此来表示词的语义信息,在这个新的空间中,语义相同的单…...
ubuntu系统环境配置和常用软件安装
系统环境 修改文件夹名称为英文 参考链接 export LANGen_US xdg-user-dirs-gtk-update 常用软件安装 常用工具 ping 和ifconfig工具 sudo apt install -y net-tools inetutils-ping 截图软件 sudo apt install -y net-tools inetutils-ping flameshot 录屏 sudo apt-get i…...

【1139. 最大的以 1 为边界的正方形】
来源:力扣(LeetCode) 描述: 给你一个由若干 0 和 1 组成的二维网格 grid,请你找出边界全部由 1 组成的最大 正方形 子网格,并返回该子网格中的元素数量。如果不存在,则返回 0。 示例 1&#…...

windows11安装sqlserver2022报错
window11安装SQL Server 2022 报错 糟糕… 无法安装SQL Server (setup.exe)。此 SQL Server安装程序介质不支持此OS的语言,或没有SQL Server英语版本的安装文件。请使用匹配的特定语言SQL Server介质;或安装两个特定语言MUI,然后通过控制面板的区域设置…...

Python快速上手系列--日志模块--详解篇
前言本篇主要说说日志模块,在写自动化测试框架的时候我们就需要用到这个模块了,方便我们快速的定位错误,了解软件的运行情况,更加顺畅的调试程序。为什么要用到日志模块,直接print不就好了!那得写多少print…...

【THREE.JS学习(1)】绘制一个可以旋转、放缩的立方体
学习新技能,做一下笔记。在使用ThreeJS的时候,首先创建一个场景const scene new THREE.Scene();接着,创建一个相机其中,THREE.PerspectiveCamera()四个参数分别为:1.fov 相机视锥体竖直方向视野…...

数仓实战 - 滴滴出行
项目大致流程: 1、项目业务背景 1.1 目的 本案例将某出行打车的日志数据来进行数据分析,例如:我们需要统计某一天订单量是多少、预约订单与非预约订单的占比是多少、不同时段订单占比等 数据海量 – 大数据 hive比MySQL慢很多 1.2 项目架…...

python虚拟环境与环境变量
一、环境变量 1.环境变量 在命令行下,使用可执行文件,需要来到可执行文件的路径下执行 如果在任意路径下执行可执行文件,能够有响应,就需要在环境变量配置 2.设置环境变量 用户变量:当前用户登录到系统,…...
BeautifulSoup文档4-详细方法 | 用什么方法对文档树进行搜索?
4-详细方法 | 用什么方法对文档树进行搜索?1 过滤器1.1 字符串1.2 正则表达式1.3 列表1.4 True1.5 可以自定义方法2 find_all()2.1 参数原型2.2 name参数2.3 keyword 参数2.4 string 参数2.5 limit 参数2.6 recursive 参数3 find()4 find_parents()和find_parent()5…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
字符串哈希+KMP
P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...
ffmpeg(三):处理原始数据命令
FFmpeg 可以直接处理原始音频和视频数据(Raw PCM、YUV 等),常见场景包括: 将原始 YUV 图像编码为 H.264 视频将 PCM 音频编码为 AAC 或 MP3对原始音视频数据进行封装(如封装为 MP4、TS) 处理原始 YUV 视频…...