内存泄漏案例分享1—Activity或Fragment的内存泄漏

news2024/11/10 18:44:31

背景

笔者优化音乐App内存泄漏时候,遇到了3个典型内存泄漏,泄漏的内存为39kb,一次39KB看上去不多,积少成多很有可能导致OOM,值得重视。
PS:文末有优化方案,优化后内存减少至原先的150分之一。
操作步骤为进入歌单列表页面,点击播放按钮,我们按照此步骤抓取一份内存日志,来分析下:
在这里插入图片描述
①点+导入hprof文件
②选择app 堆存储
③根据类名排序
④仅展示Activity、Fragment内存泄漏
⑤过滤关键字,如app进程的包名
⑥点击Leaks,下方Class Name区域会展示出内存泄漏的类名
⑦在Class Name区域点击选择要观察的类名
⑧在Instance List区域点击选择该类的实例,这一步我们选择了第三个实例
⑨在Instace Details区域点击References 选项卡
⑩选中 GC root only,一层一层展开引用链
也可以在第9步时候观察Activity的生命周期来判断内存泄漏

PlayUtil导致内存泄漏

首先点击Fields,查看Activity的生命周期,可以看到Instacen Details - Fields -Instance视图中Activity#mDestroyed = true ,表明此页面已经处于销毁状态,但Activity的内存空间仍然未释放
在这里插入图片描述

其次点击References,点一层层展开引用链,可以看到Activity使用了PlayUtil,PlayUtil初始化的时候传入了此Activity,而PlayUtil是一个单例类,该单例类全局持有了此Activity的实例,导致Activity一直被PlayUtil持有着,在Activity生命周期结束后,Activity占据的堆内存,无法被垃圾回收器回收,出现了泄漏的情况。
单例类持有Activity,导致Activity占据的内存无法被释放,遇到这种问题,解决起来也很容易:

  1. 替换context,使用ApplicationContext
  2. 使用一个更轻量无任何业务的Activity来初始化PlayUtil
  3. 如无必要不要使用context

在这里插入图片描述

PlayUtil问题,笔者采用了方法3:

修改前

    private PlayUtil(Context context) {
        m_MediaPlayerIml = MediaPlayerIml.getInstance();
       this.m_context = context;
   }

修改后

    private PlayUtil(Context context) {
        m_MediaPlayerIml = MediaPlayerIml.getInstance();
       // 兼容老代码,保留入参context,但不使用,避免内存泄漏
//        this.m_context = context;
   }

这样可以达到PlayUtil不持有Activity的目的,在Activity#onDestroyed时候,Activity占据的内存将被垃圾回收器回收。

LoadProgress导致内存泄漏

接着我们继续看该Activity的第二个实例,查看该实例的生命周期可知该Activity已经处于Destroyed状态,但内存未被回收,这是为什么呢?

在这里插入图片描述
我们继续查看References,可以看到Activity被LoadProgress持有了,且无法被释放,笔者分析可能是Activity#showLoading期间,Activity转入后台或其他因素,并未来得及调用dissLoading导致LoadProgress工具类持续持有该Activity的引用,产生了内存泄漏。
笔者分析可能是Activity#showLoading期间,Activity转入后台或其他因素,并未来得及调用dissLoading导致LoadProgress工具类持续持有该Activity的引用,产生了内存泄漏。我们来看看问题代码
问题代码
Activity#initLoading,传入了当前Activity

  private void initLoading() {
        if (this.viewModel != null) {
            this.viewModel.showLoading.observe(this, new Observer<Boolean>() {
                public void onChanged(Boolean aBoolean) {
                    if (aBoolean) {
                        if (!LoadProgress.get().getDialog(BaseActivity.this).isShowing()) {
                            LoadProgress.get().getDialog(BaseActivity.this).show();
                        }
                    } else {
                        LoadProgress.get().dismissDialog(BaseActivity.this);
                    }

                }
            });
        }
    }

LoadProgress内使用HashMap缓存了传入的Activity

 this.dialogs.put(context, lp);

解决方法
遇到这种问题也很好处理——在适当的时机清除HashMap缓存的Activity引用。
按照这种思路,我们看到:LoadProgress工具类里提供了HashMap的清空方法LoadProgress#cleanUpTrash,那么我们在合适的地方,清空`Activity缓存即可
在Activity#onDestroyed调用

 @Override protected void onDestroy() {
  super.onDestroy(); // 清空缓存
   LoadProgress.get().cleanUpTrash(); 
  }

这样,当Activity生命周期处于onDestroyed的时候,HashMap会清空持有的Activity,避免Activity内存泄漏
在这里插入图片描述

MediaplayerListener导致内存泄漏

接着我们看Activity的第三个实例,有前两个泄漏点的分析经验,可得知Activity此时已经处于onDestroyed状态,被某个类引用了,导致Activity的内存无法回收,熟能生巧,我们按图索骥可看到是MediaplayerIml通过mMediaPlayListenerCacheList持有了该类,该类是用于存储播放回调的,用于AIDL服务端通知Client的回调,更新播放界面。
问题代码:
Activity#onCreate时候调用了注册回调

   mediaPlayerIml.registerListener(playListener);
    private List<MediaPlayListener> mMediaPlayListenerCacheList = new ArrayList<>();

 public synchronized void registerListener(MediaPlayListener listener) {
        if (!mMediaPlayListenerCacheList.contains(listener)) {
            mMediaPlayListenerCacheList.add(listener);
        }
    }

解决办法也很简单,页面销毁时,调用解绑注册

@Override
protected void onDestroy() {
    super.onDestroy();
    if(mediaPlayerIml!=null){
        mediaPlayerIml.unregisterListener(playListener);
    }
    // 清空缓存
    LoadProgress.get().cleanUpTrash();
}

在这里插入图片描述

总结

总结上述3个内存泄漏点

  1. PlayUtil构造函数持有Activity未释放
  2. LoadProgress成员变量HashMap持有Activity未释放
  3. AIDL远程服务端持有Listener,Listener持有Activity未释放

优化效果

优化前,多次进入该页面,点击播放按钮,产生4个实例,有3个实例无法被垃圾回收,出现了内存泄漏的情况
在这里插入图片描述

优化后,多次进入该页面,点击播放按钮,只产生一个实例

在这里插入图片描述

根据Retained Szie来看,优化前后节省内存,效果达到了 49937/332 = 150 倍。由此可见,Activity及时回收,极大的节省了内存占用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1702739.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电子画册制作技巧,从零基础到专业人士

电子画册作为一种新兴的视觉传达形式&#xff0c;正越来越受到大众的喜爱。从设计新手到专业人士&#xff0c;如何快速掌握电子画册的制作技巧&#xff0c;提升自己的创作水平呢&#xff1f; 一、明确设计目的和定位 制作电子画册前&#xff0c;首先要明确其设计目的和定位。画…

【编译原理】LR(0)分析

一、实验目的 LR(0)分析法是一种移进归约过程&#xff0c;能根据当前分析栈中的符号串&#xff0c;同时也不用向右查看输入串的符号就可唯一确定分析器的动作。通过对给定的文法构造LR(0)分析表和实现某个符号串的分析掌握LR(0)分析法的基本思想。 二、实验要求 实现LR(0)分…

推荐3款好用的AI智能写作工具

AI智能写作如今已经很成熟了&#xff0c;不仅有很多AI综合大模型可以实现AI写作&#xff0c;还有很多专门针对AI写作场景专门研发的垂直领域工具。 如果你在工作学习中也想提高写作效率&#xff0c;不妨试试下面3个国内可直接登录使用的AI写作工具&#xff0c;其中不乏有简单易…

【Java】IdentityHashMap 的使用场景

文章目录 前言1. Druid 应用场景2. IdentityHashMap 特性3. IdentityHashMap 同步化4. IdentityHashMap 处理key为空值后记 前言 最近有兴趣看一下 Druid 连接池怎么做连接管理的&#xff0c;看到一个类 IdentityHashMap &#xff0c;这里记录一下使用场景。 1. Druid 应用场…

民国漫画杂志《时代漫画》第26期.PDF

时代漫画26.PDF: https://url03.ctfile.com/f/1779803-1248635183-9832d2?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(十二)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 19节&#xff09; P19《18.ArkUI组件-页面路由》 以访问京东页面为例&#xff0c;访问过的页面并没有消失&#xff0c;而是进入了…

[图解]企业应用架构模式2024新译本讲解01-事务脚本

1 00:00:00,220 --> 00:00:03,010 接下来&#xff0c;我们就要进入模式的讲解了 2 00:00:04,030 --> 00:00:05,940 这个是书里面的目录 3 00:00:06,230 --> 00:00:08,140 按照 4 00:00:08,150 --> 00:00:09,220 领域逻辑模式 5 00:00:09,230 --> 00:00:10,5…

一点点 cv 经验 1:cv方向、模型评估、输入尺寸、目标检测器设计

一点点 cv 经验 1&#xff1a;cv方向、模型评估、输入尺寸、目标检测器设计 cv 方向Pytorch数据集划分 模型评估误差偏差方差噪声 输入尺寸方法一&#xff1a;让数据适应模型方法二&#xff1a;修改模型适应数据方法三&#xff1a;划分Patch&#xff0c;分别处理 目标检测器结构…

探索演进:了解IPv4和IPv6之间的区别

探索演进&#xff1a;了解IPv4和IPv6之间的区别 在广阔的互联网领域中&#xff0c;设备之间的通信依赖于一组独特的协议来促进连接。前景协议中&#xff0c;IPv4&#xff08;Internet 协议版本 4&#xff09;和 IPv6&#xff08;Internet 协议版本 6&#xff09;是数字基础设施…

二、OpenWebUI 使用(.Net8+SemanticKernel+Ollama)

OpenWebUI的github上安装部署已经很详细&#xff0c;直接照着敲命令即可 GitHub&#xff1a;https://github.com/open-webui/open-webui 一、使用配置 1、访问&#xff1a;http://Ip:3000&#xff0c;打开如下OpenWebUI界面。 2、先点击“注册”&#xff0c;注册一个管理员帐号…

从普通神经网络到transformer

1.单隐藏层的多层感知机 2. 循环神经网络 3.现代循环神经网络。 GRU 门控循环单元 LSTM 长短期记忆网络 候选记忆元&#xff1a; ˜C t ∈ R &#xff08;nh&#xff09; 记忆元

重生之 SpringBoot3 入门保姆级学习(04、 包扫描)

重生之 SpringBoot3 入门保姆级学习&#xff08;04、 包扫描&#xff09; 2.1 包扫描 2.1 包扫描 默认包扫描规则&#xff1a; SpringBootApplication 标注的就是主程序 SpringBoot 只会扫描主程序下面的包 自动的 component-scan 功能 在 SpringBootApplication 添加参数可以…

一文搞透常见的Python编码陷阱(上)(分析+案例)

一个认为一切根源都是“自己不够强”的INTJ 个人主页:用哲学编程-CSDN博客专栏:每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 一、别忘了冒号 1. if 语句 2. while 语句 3. for 语句 4. 函数定义 5. 类定义 6. try/except 语句 …

把maven本地库(windows)导入Nexus3(ubuntu)

1、在nexus中创建导入仓库 点“Create repository” 选择maven2(hosted) 填上对应的仓库name&#xff0c;Version policy选“Mixed” Hosted中的Deployment policy选择“Allow redeploy” 点“Create repository”创建仓库 创建好的仓库如下 记下仓库的url&#xff0c;下…

HTML-JavaWeb

目录 1.标题排版 2.标题样式 ​编辑 ​编辑 小结 3.超链接 4.正文排版 ​编辑​编辑​编辑5.正文布局 6.表格标签 7.表单标签 8.表单项标签 1.标题排版 ● 图片标签 :< img> src:指定图像的ur1(绝对路径/相对路径) width:图像的宽度(像素/相对于父元素的百…

OrangePi AIpro 开箱初体验及语音识别样例

OrangePi AIpro 开箱初体验及语音识别样例 一、 前言 首先非常感谢官方大大给予这次机会&#xff0c;让我有幸参加此次活动。 OrangePi AIpro联合华为精心打造&#xff0c;采用昇腾AI技术路线&#xff0c;具体为4核64位处理器AI处理器&#xff0c;集成图形处理器&#xff0c;…

【Linux环境搭建实战手册】:打造高效开发空间的秘籍

文章目录 &#x1f680;Linux环境搭建&#x1f4a5;1. 设备要求❤️2. 了解虚拟机&#x1f680;3. 安装VMware&#x1f308;4. 终端基础信息解读 &#x1f680;Linux环境搭建 &#x1f4a5;1. 设备要求 处理器&#xff08;CPU&#xff09;&#xff1a;至少具有1 GHz的处理能力&…

《Ai企业知识库》-模型实践-rasa开源学习框架-搭建简易机器人-环境准备(针对windows)-02

rasa框架 Conversational AI Platform | Superior Customer Experiences Start Here 阿丹: 其实现在可以使用的ai的开发框架有很多很多&#xff0c;就需要根据各个模型的能力边界等来讨论和设计。 rasa整体流程以及每一步的作用 NLU(自然语言理解): 自然语言理解&#xff…

07、SpringBoot 源码分析 - SpringApplication启动流程七

SpringBoot 源码分析 - SpringApplication启动流程七 初始化基本流程SpringApplication的prepareContext准备上下文postProcessApplicationContext处理applyInitializers初始化器初始化load SpringApplication的refreshContext刷新上下文refreshServletWebServerApplicationCon…

谷歌开发者账号身份验证不通过?该怎么办?

我们都清楚&#xff0c;随着谷歌上架行业的快速发展&#xff0c;谷歌政策也在不断更新变化&#xff0c;对开发者账号的审核标准也在不断提升。其中一项要求就是&#xff0c;开发者账号需要进行身份验证才能发布应用。 Your identity couldnt be verified&#xff01;“我们无法…