内存泄漏案例分享3-view的内存泄漏

news/2024/7/22 0:29:16/文章来源:https://blog.csdn.net/chivalrousman/article/details/139243867

案例3——view内存泄漏
前文提到,profile#Leaks视图无法展示非Activity、非Fragment的内存泄漏,换言之,除了Activity、Fragment的内存泄漏外,其他类的内存问题我们只能自己检索hprof文件查询了。
下面有一个极佳的view内存泄漏例子,它的操作步骤为:

  1. 播放音乐,唤醒音乐悬浮窗
  2. 播放一段时间后,关闭音乐悬浮窗
  3. 重复步骤1和2

我们重复三次之后,得到一份hprof文件,下面我们来分析一下内存泄漏问题
在这里插入图片描述

①输入view的名称
②选择view
③可以看到分配了3个实例对象
④Instance List视图显示,view有3个实例对象及其引用
我们从上至下依次看3分实例的调用链

第一个泄漏点

view的第一个实例
先查看Fields区域,观察mLayoutmode值,判断view是否离开了窗口,如果已经离开了窗口,表明view未被回收,存在内存泄漏

在这里插入图片描述
可以看到mLayoutMode = -1 ,表明布局已经离开屏幕了,此实例存在内存泄漏的情况
在这里插入图片描述

接着我们查看References区域,逐级点开我们发现Handler发送的Message持有了当前view,导致view在离开窗口的时候,无法被垃圾回收器回收。
右键点击查看问题代码

在这里插入图片描述
问题代码:

   playHandler.post(new Runnable() {@Overridepublic void run() {tv_play.setText(playItem.getProgramTitle());tb_play.setSelected(true);initView();}});

看到new Runnbale,这是是匿名内部类,匿名内部类持有当前类的引用,匿名Runnbale未执行完毕,Runnbale内存未释放的时候,view就无法被释放,而匿名Runnbale的释放时机不可控,由Handler、Looper、Runnbale执行情况影响。
那么我们该怎么优化呢?

  1. 使用非匿名或静态的Handler+弱引用,处理此任务
  2. 在主线程处理此任务
  3. view退出的时候释放Message对view的引用
    笔者采用了方案3:
tv_play.setText(playItem.getProgramTitle());
tb_play.setSelected(true);
initView();

方案1代码与下面view的第三个实例写法一致,不重复写了;我们解释一下方案3:

view退出的时候释放Message对view的引用
根据上图所示,我们看到Message-Runnbale-View的引用关系可知,Looper中的Message持续的引用view,我们最高效释放内存的做法是view离开窗口的时候,斩断Message与view的引用关系,那么我们该怎么做呢?答案是:

  1. 结束子线程任务
  2. 清空Looper缓存的Message
  3. 释放Handler

第一步:结束子线程任务很简单
thread.interrupt()
本案例给Handler传入的是Runnbale,Handler未提供结束Runnbale的接口,此项优化搁置
第二步:清空Message
已知Looper提供了清空Message的接口

  1. Looper#quit
  2. Looper#quitSafely
  3. 主线程的Looper无法退出
    已知Handler提供了释放Message的接口
  4. Handler#removeCallbacksAndMessages
    那我们优化起来就很简单了,清空Handler持有的Message
@Override protected void onDetachedFromWindow() { 
super.onDetachedFromWindow(); ... // 释放message,断开message-Runnbale-view的引用链 if (playHandler != null) { playHandler.removeCallbacksAndMessages(null); playHandler = null; }}

第二个泄漏点

我们继续看view的第二个实例
先查看Fields区域,观察mLayoutmode值,判断view是否离开了窗口,如果已经离开了窗口,表明view未被回收,存在内存泄漏

在这里插入图片描述
可以看到mLayoutMode = -1 ,表明布局已经离开屏幕了,此实例存在内存泄漏的情况
接着我们看References区域,观察调用链
在这里插入图片描述
可以看到MediaPlayerIml有一个成员变量mMediaPlayListenerCacheList,缓存了MediaPlayListener,MediaPlayListener又是在view实例里面创建的,并且作为内部类,它持有view的实例。现在我们得到了清晰的调用链,MediaPlayerIml->mMediaPlayListenerCacheList->MediaPlayListener->view,MediaPlayerIml引用view导致view实例无法被释放
查看问题代码:
笔者发现view#onDetachedFromWindow已经触发了移除list#listener操作

@Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mediaPlayerIml.unregisterListener(playListener); 

可以看到内部实现是remove调引用的

 /*** 取消注册listener** @param listener*/public synchronized void unregisterListener(MediaPlayListener listener) {mMediaPlayListenerCacheList.remove(listener);}

那为什么会未回收持续占用内存呢?

  1. 抓拍hprof文件期间,代码未执行到unregisterListener,导致view内存未得到释放
  2. mMediaPlayListenerCacheList添加的listener与remove的listener不是同一个
  3. 此处没有产生内存泄漏,判断view是否应该被回收的依据有问题

第三个泄漏点

搁置疑问,接着我们来看view的第三个实例,节省时间,笔者直接调到代码索引出,展示问题代码:

  /*** 播放进度条刷新控*/private Handler m_handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case MSG_FLUSH_SEEKBAR:boolean isPlaying = mediaPlayerIml != null && mediaPlayerIml.getPlayStatus() == QingtingConfig.PLAY;if (isPlaying) {int currentTime = mediaPlayerIml.getCurrentTime();int totalTime = mediaPlayerIml.getTotalTime();mSeekBar.setMax(totalTime);mSeekBar.setProgress(currentTime);mPrograssBar.setMaxProgress(totalTime);mPrograssBar.setCurrentProgress(currentTime);LoggerUtils.instance().logE("mediaPlayJindu", "mediaPlayJindu" + totalTime + "/" + currentTime);}m_handler.sendEmptyMessageDelayed(MSG_FLUSH_SEEKBAR, MSG_FLUSH_TIME);break;}}};

可以看到此处还是使用了非静态内部类m_handler,m_handler持有当前view 的引用,m_handler如果长期存在,那么view的内存也不会被释放
解决方法如下:

  1. 定义外部类Handler
  2. 定义静态内部类
  3. 定义静态内部类+弱引用
    笔者采用了方案3:
    定义静态内部类
 private  static  class UpdateHandler extends Handler {private final WeakReference<MediaPlayerIml> mediaPlayerImlWeakReference;private final WeakReference<SeekBar> seekBarWeakReference;private final WeakReference<QQCircleProgressBar> progressBarWeakReference;public UpdateHandler(MediaPlayerIml mediaPlayerIml, SeekBar seekBar, QQCircleProgressBar progressBar) {mediaPlayerImlWeakReference = new WeakReference<MediaPlayerIml>(mediaPlayerIml);seekBarWeakReference = new WeakReference<SeekBar>(seekBar);progressBarWeakReference = new WeakReference<QQCircleProgressBar>(progressBar);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == MSG_FLUSH_SEEKBAR) {MediaPlayerIml mediaPlayerIml = mediaPlayerImlWeakReference.get();SeekBar seekBar = seekBarWeakReference.get();QQCircleProgressBar qqCircleProgressBar =progressBarWeakReference.get();boolean isPlaying = mediaPlayerIml != null && mediaPlayerIml.getPlayStatus() == QingtingConfig.PLAY;if (isPlaying && seekBar!=null && qqCircleProgressBar != null) {int currentTime = mediaPlayerIml.getCurrentTime();int totalTime = mediaPlayerIml.getTotalTime();seekBar.setMax(totalTime);seekBar.setProgress(currentTime);qqCircleProgressBar.setMaxProgress(totalTime);qqCircleProgressBar.setCurrentProgress(currentTime);LoggerUtils.instance().logE("mediaPlayJindu", "mediaPlayJindu" + totalTime + "/" + currentTime);}sendEmptyMessageDelayed(MSG_FLUSH_SEEKBAR, MSG_FLUSH_TIME);}}}

在view使用时,初始化handler,构造参数传入组件id;

m_handler = new UpdateHandler(MediaPlayerIml.getInstance(),mSeekBar,mPrograssBar);
m_handler.sendEmptyMessage(MSG_FLUSH_SEEKBAR);

在view离开窗口时候,销毁handler数据;

  @Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();...if(m_handler!=null){m_handler.removeCallbacksAndMessages(null);m_handler = null;}}

总结

总结我们针对此按理做的优化

  1. 静态Handler+弱引用,释放了对handler对view的引用,让view及时销毁,view占据的内存及时被垃圾回收器释放
  2. 释放了Message对view的引用,在view及时退出界面的时候,立即斩断message对view
    回顾一下优化前的实例数量,多次操作,隐藏展示悬浮窗之后,内存中存在多份悬浮窗实例,之前创建过的悬浮窗内存一直无法被回收:
    在这里插入图片描述
    优化后效果,多次操作,当屏幕上存在一个view时,只存在一份view实例:
    [图片]

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

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

相关文章

ubuntu20.04 10分钟搭建无延迟大疆无人机多线程流媒体服务器

1.使用效果 无人机画面 2.服务器视频端口 3.使用教程 3.1.下载ubuntu对应软件包&#xff1a;系统要求ubuntu16以上 3.2修改端口&#xff08;config.xml文件&#xff09; 3.3启动服务 目录下输入&#xff1a;终端启动&#xff1a;./smart_rtmpd 后台启动&#xff1a;nohup ./…

如何选择序列化协议:关键因素与场景分析

如何选择序列化协议&#xff1a;关键因素与场景分析 序列化协议的选择直接影响着系统的性能、可维护性及跨平台兼容性。以下是针对不同场景下&#xff0c;几种常见序列化协议的选择建议&#xff1a; 1. 公司间系统调用&#xff08;性能要求宽松&#xff09; SOAP (基于XML)&a…

LeetCode 279 —— 完全平方数

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此图利用动态规划进行求解&#xff0c;首先&#xff0c;我们求出小于 n n n 的所有完全平方数&#xff0c;存放在数组 squareNums 中。 定义 dp[n] 为和为 n n n 的完全平方数的最小数量&#xff0c;那么有状态…

操作教程|通过DataEase开源BI工具对接金山多维表格

前言 金山多维表格是企业数据处理分析经常会用到的一款数据表格工具&#xff0c;它能够将企业数据以统一的列格式整齐地汇总至其中。DataEase开源数据可视化分析工具可以与金山多维表格对接&#xff0c;方便企业更加快捷地以金山多维表格为数据源&#xff0c;制作出可以实时更…

k8s pv 一直是release状态

如下图所示&#xff0c;pv 一直是release状态 这个时候大家可能就会想到现在我的 PVC 被删除了&#xff0c;PV 也变成了 Released 状态&#xff0c;那么我重建之前的 PVC 他们不就可以重新绑定了&#xff0c;事实并不会&#xff0c;PVC 只能和 Available 状态的 PV 进行绑定。…

ssm137基于SSM框架的微博系统+vue

微博系统网站的设计与实现 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就…

保安维稳,四信以科技构筑高速公路安全智慧防线

近日&#xff0c;广东梅大高速发生严重塌方事故&#xff0c;造成了严重的人员伤亡和财产损失。这一事件在公众心中敲响了安全的警钟&#xff0c;再次引起了公众对于交通设施运营安全性的重点关注。 国务院安委会办公室和国家防灾减灾救灾委员会办公室等主管机构先后印发紧急通知…

make disclean V=1 分析

文章目录 make distclean步骤1&#xff1a;2090-2114行&#xff0c;执行依赖 clean步骤2&#xff1a;2120-2124行&#xff0c;执行依赖 $(mrproper-dirs)步骤3&#xff1a;2118-2129行&#xff0c;执行依赖 mrproper步骤4&#xff1a;2135-2142行&#xff0c;实现 distclean 编…

flutter 的webview中touchstart和touchend 执行异常问题解决

效果 背景 使用flutter 调用webview内网页&#xff0c;网页内容是监听touchstart和 touchend&#xff0c;触发不同是事件&#xff0c;但是发现每次长按都 手指抬起后 才会执行 touchstart和touchend&#xff0c;满足不了我的需求&#xff0c;我的需求是当手指按下 立即执行touc…

大模型时代的具身智能系列专题(三)

清华高阳团队 高阳为清华叉院助理教授&#xff0c;本科毕业于清华大学计算机系&#xff0c;博士毕业于UC Berkeley。博士导师是Vision领域的大牛Trevor Darrell&#xff0c;读博期间和Sergey Levine合作开始强化学习方面的探索&#xff0c;博后跟随Pieter Abbeel做强化学习&am…

Windows下mingw32编译ffmpeg5.1.4实现rtsp拉流

由于客户要求&#xff0c;要在Windows下使用mingw32编译&#xff0c;去ffmpeg.org下载需要编译的版本&#xff0c;使用msys2方法进行编译&#xff0c;使用QT5.10的编译器&#xff0c;基本上把网上的方法试了个遍&#xff0c;编译全部库总是报错出问题 查看了ffbuild文件夹中con…

【全开源】JAVA情侣扭蛋机情侣游戏系统源码支持微信小程序+微信公众号+H5

一、引言&#xff1a;增添恋爱甜蜜&#xff0c;扭出惊喜时刻 在恋爱生活中&#xff0c;总是需要一些小小的惊喜和乐趣来增添甜蜜。为此&#xff0c;我们精心打造了“情侣扭蛋机情侣游戏系统小程序源码”&#xff0c;为情侣们提供一个充满趣味和创意的互动平台。 二、独特扭蛋…

C++的第一道门坎:类与对象(一)

1.面向过程与面向对象 1.1面向过程 我们之前学习的C语言就是一种面向过程的语言&#xff0c;面向过程的语言强调的是具体实现的过程&#xff0c;一般用函数来具体实现。我们用面向过程的思想&#xff0c;就可以把炒菜分为以下几个步骤: 1.2面向对象 而对于面向对象的语言而言…

【ARMv8/v9 异常模型入门及渐进 10 -- WFI 与 WFE 使用详细介绍 1】

请阅读【ARMv8/v9 ARM64 System Exception】 文章目录 WFI 与 WFE等待事件&#xff08;WFE&#xff09;发送事件&#xff08;SEV&#xff09;本地发送事件&#xff08;SEVL&#xff09;WFE 唤醒事件 WFE 使用场景举例与代码实现wfe睡眠函数sev 事件唤醒函数全局监视器和自旋锁 …

如何异地组网添加摄像机?

本文将介绍如何使用天联技术实现异地组网添加摄像机&#xff0c;并保障数据的安全性。 安防摄像机的应用愈发广泛&#xff0c;无论是家庭安防还是企业监控&#xff0c;摄像机都扮演着重要角色。在一些特殊场合或者特殊需求下&#xff0c;我们需要将摄像机添加到异地网络中进行监…

uniapp android使用uni.chooseLocation,app云打包后,定位地址列表一直在加载中

复现BUG 1、自己生成一个证书 参考生成证书流程 2、使用刚生成证书的SHA1 &#xff0c;重新创建一个高德key 高德开放平台地址 3、打包&#xff08;打包的包名要与高德申请key所填的包名一致&#xff09;

Java面试八股之Synchronized锁升级的原理

Synchronized锁升级的原理 Synchronized锁升级是Java为了提高并发性能而引入的一项优化措施&#xff0c;这一机制主要发生在JDK 1.6及之后的版本中。Synchronized锁升级旨在减少锁带来的性能开销&#xff0c;通过从低开销的锁逐步升级到高开销的锁&#xff0c;以适应不同的竞争…

从零入门激光SLAM(二十一)——FAST-LIO2论文解析

FAST-LIO2: Fast Direct LiDAR-Inertial Odometry 论文地址&#xff1a;https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber9697912 代码&#xff1a;https://github.com/hku-mars/FAST_LIO 一、文章概述 1.问题导向 基于视觉传感器的高分辨率和高精度的实时密…

Docker笔记-搭建达梦Python环境(dmPython + SQLAlchemy)

背景 达梦提供的C接口&#xff0c;dpi和java的jar包已经很好用了&#xff0c;想不到&#xff0c;来了一个用python的同事&#xff0c;这里就只能适应下他了&#xff0c;在不影响其他环境下搭建一个python的达梦环境。最后发现&#xff0c;python对进行达梦增删改查&#xff0c…

【设计模式】JAVA Design Patterns——Bytecode(字节码模式)

&#x1f50d;目的 允许编码行为作为虚拟机的指令 &#x1f50d;解释 真实世界例子 一个团队正在开发一款新的巫师对战游戏。巫师的行为需要经过精心的调整和上百次的游玩测试。每次当游戏设计师想改变巫师行为时都让程序员去修改代码这是不妥的&#xff0c;所以巫师行为以数据…