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

背景

笔者优化音乐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.mzph.cn/pingmian/17123.shtml

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

相关文章

“不是我兄弟”!刘强东内部“狼性训话”流出!

今天&#xff0c;京东创始人刘强东5月24日的线上讲话流出。 在这次线上讲话中&#xff0c;刘强东首先宣布为全体采销员工涨薪20%—100%&#xff0c;随后进行了一番“狼性训话”。往期报道可戳&#xff1a;刘强东怒了&#xff1a;“不是我兄弟”&#xff01; 刘强东在讲话中指…

【Day8:JAVA字符串的学习】

目录 1、常用API2、String类2.1 String类的特点2.2 String类的常见构造方法2.3 String类的常见面试题&#xff1a;2.3.1 面试题一&#xff1a;2.3.2 面试题二&#xff1a;2.3.3 面试题三&#xff1a;2.3.4 面试题四&#xff1a; 2.4 String类字符串用于比较的方法2.5 String类字…

如何使用DevEco Studio创建Native C++应用

简介 本篇主要介绍如何使用DevEco Studio for OpenAtom OpenHarmony &#xff08;以下简称“OpenHarmony”&#xff09;创建一个Native C应用。应用采用“Native C”模板&#xff0c;实现了通过Node-API调用C标准库的功能。本示例通过调用C标准库接口来演示调用过程&#xff0…

windows-386、windows-amd64、windows-arm64这三者有什么区别?

选择文件的版本出现下面问题&#xff1a; Architectures windows-386 &#xff1a;这些是针对 32 位 Windows 系统编译的。windows-amd64 &#xff1a;这些是针对具有 AMD 或 Intel x86-64 架构的 64 位 Windows 系统编译的。windows-arm64 &#xff1a;这些是针对具有 ARM 架…

Golang的内存关系

1.Page Golang的Page,在操作系统对虚拟内存管理的MMU定义的物理页有相似的定义,默认的Page为8KB 2.mSpan 多个连续的Page称之为是一个Span&#xff0c;其定义含义有操作系统的管理的页表相似 3.Size Class Size Class: 相当于 一个等级和刻度, 比如 第二等级 就代表 一个Pag…

从零开始打造教育APP:在线教育系统源码与开发流程

很多人疑问&#xff0c;应该如何从零开始打造一个在线教育APP&#xff1f;今天&#xff0c;小编将详细为大家讲解在线教育系统的源码与开发流程。 一、需求分析 对于在线教育APP&#xff0c;需要要明确以下几点&#xff1a; 1.目标用户&#xff1a;明确APP的用户群体&#xf…

四川景源畅信:抖音小店新手如何做?

随着短视频平台的兴起&#xff0c;抖音小店成为了许多创业者的新选择。但是&#xff0c;对于新手来说&#xff0c;如何在抖音上开设并经营好自己的小店呢?本文将围绕这一问题展开讨论。 一、明确目标和定位作为抖音小店的新手&#xff0c;首先要明确自己的经营目标和定位。是想…

挑战你的数据结构技能:复习题来袭【3】

chap3 练习1 一. 单选题 1. (单选题)栈和队列具有相同的&#xff08;&#xff09; A. 抽象数据类型B. 逻辑结构C. 存储结构D. 运算 答案: B:逻辑结构 答案分析&#xff1a;逻辑结构都属于线性结构,只是它们对数据的运算不同。 2. (单选题)栈是() A. 顺序存储的线性结构B…

如何处理逻辑设计中的时钟域

1.什么是时钟域 2.PLL对时钟域管理 不管是否需要变频变相&#xff0c;在FPGA内部将外部输入时钟从专用时钟引脚扇入后先做PLL处理。如何调用pll&#xff0c;见另一篇文章。 约束输入时钟 creat_clock -period 10 -waveform {0 5} [get_ports {sys_clk}] 3.单bit信号跨时钟…

黏土风格绘画神器Image to Clay Style Online,可免费生成50000张

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃斜杠君&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之笔记&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &#…

[pdf,epub]《软件方法》2024版电子书共290页(202405更新)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 已上传本账号CSDN资源。 或者到以下链接下载&#xff1a; http://www.umlchina.com/url/softmeth2024.html&#xff0c;或点击“阅读原文”。 如果需要提取码&#xff1a;umlc 已排…

mysql高级篇学习(数据表的设计方法,索引优化)

使用docker 安装 mysql 安装 docker # yum 包更新到最新 yum update# 卸载旧的 docker yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine # 安装 gcc 环境 yum -y install…

库卡机器人保养服务包,教你保养技术

为了确保机器人的持续稳定运行和延长使用寿命&#xff0c;正确的库卡机器人保养和KUKA机械手维修至关重要。 KUKA机器人保养工作对于保障其稳定运行和延长使用寿命具有重要意义。通过了解子锐机器人提供的库卡机械手保养服务包&#xff0c;可以确保机器人在良好的状态下为生产线…

英语新概念2-回译法-lesson16

第一次回译 if you ___ your car on a wrong place, the traffic police man will find you quickly. If he do not give you the ticket,you are lucky.However,the ___ not all like this,The police man is __ sometimes.I had a holiday in Sweden, I found a ___ in my c…

深入探索:中文字符的编码与转移字符的奥秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;探索字符编码的世界 二、字符编码基础&#xff1a;理解ASCII与Unicode…

2023idea没有VCS首次提交代码到Git

1、setting 2、vcs------>create git repository 3、右键项目----->Git------>add 4、右键项目------>git------>commit Directory 之后就会显示这个页面(下面写你提交的信息&#xff0c;就是你修改了什么) 点击commit,提交 5、Git--------->push 6、选择…

IDEA出现javax.servlet.http包错误解决方法

问题原因&#xff1a;缺少对应的jar包&#xff0c;其实tomcat服务器自带与HTTP一些相关的Jar包&#xff0c;没有导入进去。 解决方案1&#xff1a; 导入对应jar包 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</…

图片如何去水印?ps去除图片上的水印应该怎么操作?教会你!

图片去水印是一个常见的需求&#xff0c;尤其在使用他人图片时&#xff0c;为避免版权问题&#xff0c;去除水印是很有必要的。Photoshop&#xff08;PS&#xff09;作为一款专业的图片处理软件&#xff0c;提供了多种去除水印的方法。以下是使用PS去除图片水印的几种详细步骤&…

SNAT和DNAT策略

1.SNAT策略及应用 SNAT应用环境&#xff1a;局域网主机共享单个公网IP地址接入Internet&#xff08;私有不能在Internet中被正常路由&#xff09; SNAT原理&#xff1a; 修改数据包的源地址。 SNAT转换前提条件&#xff1a; 局域网各主机已正确设置IP地址、子网掩码、默认网…

记录使用 Vue3 过程中的一些技术点

1、自定义组件&#xff0c;并使用 v-model 进行数据双向绑定。 简述&#xff1a; 自定义组件使用 v-model 进行传参时&#xff0c;遵循 Vue 3 的 v-model 机制。在 Vue 3 中&#xff0c;v-model 默认使用了 modelValue 作为 prop 名称&#xff0c;以及 update:modelValue 作为…