当前位置: 首页 > news >正文

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

案例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实例:
    [图片]

相关文章:

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

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

红外超声波雷达测距

文章目录 一HC-SR04介绍1HC-SR04简介及工作原理 二用HAL库实现HC-SR04测量距离1STM32CubeMX配置2keil53代码的添加 三效果 一HC-SR04介绍 1HC-SR04简介及工作原理 超声波是振动频率高于20kHz的机械波。它具有频率高、波长短、绕射现象小、方向性好、能够成为射线而定向传播等…...

AIGC 008-IP-Adapter文本兼容图像提示适配器用于文本到图像扩散模型

AIGC 008-IP-Adapter文本兼容图像提示适配器用于文本到图像扩散模型&#xff01; 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 这篇论文介绍了 IP-Adapter&#xff0c;一种 高效地将预训练的图像到图像转换模型适应到新领域 的方法。它通过在预训练模型的 输入端 添加一个…...

Java入门基础学习笔记50——ATM系统

1、项目演示&#xff1b; 2、项目技术实现&#xff1b; 1&#xff09;面向对象编程&#xff1a; 每个账户都是一个对象&#xff0c;所以要设计账户类Account&#xff0c;用于创建账户对象封装账户信息。ATM同样是一个对象&#xff0c;需要设计ATM类&#xff0c;代表ATM管理系…...

# linux 中使用 visudo 命令,怎么保存退出?

linux 中使用 visudo 命令&#xff0c;怎么保存退出&#xff1f; 在 visudo 中保存并退出的方法取决于您使用的文本编辑器。通常情况下&#xff0c;visudo 会使用 vim 或 vi 或 Nano 作为默认的文本编辑器。 1、使用 Vim 或 vi 编辑器&#xff1a; 按下 Esc 键退出编辑模式&…...

springboot项目,@Test写法 @Before @After

某文件示例 package cn.xxx.crm.boss;import cn.xxxx.crm.manager.mq.rabbit.AliyunCredentialsProvider; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; im…...

vue3的核心API功能:computed()API使用

常规使用方法: 这样是常规使用方法. 另一种,可写计算属性的使用方法: 这样分别定义computed的get回调函数和set回调函数, 上面例子定义了plusOne.value的值为1, 那么这时候就走了computed的set回调函数,而没有走get回调函数. 当我们打印plusOne.value的值的时候,走的是get的…...

Bootstrap5

Bootstrap5-容器 容器是Bootstrap—个基本的构建块&#xff0c;它包含、填充和对齐给定设备或视口中的內容。 Bootstrap 需要一个容器元素来包裏网站的内容 我们可以使用以下两个容器类&#xff1a; .container 类用于固定宽度并支持响应式布局的容器。.container-fluid 类用…...

宝塔部署纯Vue项目,无后端

1.打包项目 生成一个dist文件夹 2.创建云服务器根目录 3.创建站点 4.上传文件 5.访问...

spring boot3整合邮件服务实现邮件发送功能

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 目录 内容概要 开通服务 依赖引入 配置属性 创建邮件发送工具类 测试 最近发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家…...

算法刷题day54:搜索(一)

目录 引言一、池塘计数二、城堡问题三、山峰和山谷四、迷宫问题五、武士风度的牛六、抓住那头牛七、矩阵距离八、魔板 引言 针对于蓝桥杯&#xff0c;搜索问题还是非常之重要的&#xff0c;在省赛前深知暴搜的重要性&#xff0c;所以提前先把提高课的搜索一章给看了&#xff0…...

深入了解Redis的过期策略和内存淘汰机制

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…...

小白不知道怎么投稿?记住这个好方法

作为一名单位信息宣传员,我最初踏上这条道路时,满心憧憬着通过文字传递我们单位的精彩瞬间,让社会听见我们的声音。然而,理想与现实之间的距离,却在一次次邮箱投稿的石沉大海中渐渐清晰。那时的我,像所有“小白”一样,以为只要用心撰写稿件,通过电子邮件发给各大媒体,就能收获满…...

gRPC - Protocol Buffer 编译器安装

文章目录 Protocol Buffer 编译器安装如何安装 Protocol Buffer 编译器使用包管理器安装Linux 上&#xff0c;使用 apt 或 apt-get&#xff0c;例如&#xff1a;macOS 上&#xff0c;使用 Homebrew&#xff1a; 安装预编译的二进制文件&#xff08;任何操作系统&#xff09;其他…...

【Linux】centos7下载安装Python3.10,下载安装openssl1.1.1

目录 centos7下载安装Python&#xff08;版本3.10.14&#xff09; &#xff08;1&#xff09;网页下载python压缩包&#xff0c;并解压缩 &#xff08;2&#xff09;编译安装 Python在make altinstall时&#xff0c;报错及解决 &#xff08;3&#xff09;将安装目录和可执…...

通过 python 操作mongodb

库引入 Python 要连接 MongoDB 需要 MongoDB 驱动&#xff0c;这里我们使用 PyMongo 驱动来连接。 import pymongo 链接数据库 创建数据库需要使用 MongoClient 对象&#xff0c;并且指定连接的ip和端口号。 myclientpymongo.MongoClient("localhost",27017)#连接…...

若依框架对于后端返回异常后怎么处理?

1、后端返回自定义异常serviceException 2、触发该异常后返回json数据 因为若依对请求和响应都封装了&#xff0c;所以根据返回值response获取不到Code值但若依提供了一个catch方法用来捕获返回异常的数据 3、处理的方法...

vs code怎么补全路径,怎么快捷输入文件路径

安装插件&#xff1a; 链接&#xff1a;https://marketplace.visualstudio.com/items?itemNamejakob101.RelativePath 使用 按住 Ctrl Shift H&#xff0c;弹出窗口&#xff0c;输入文件补全&#xff0c;回车就可以了 排除文件 如果你的项目下文件太多&#xff0c;它会…...

git分支开发主干合并流程

文章目录 一、分支开发二、主干合并三、删除合并过的分支 一、分支开发 创建分支git branch <分支名> # git branch my_new_branch开发后提交代码git commit -m 本次开发内容 # git commit -m 增加登录保持功能同步远端仓库git push origin <分支名> # git push o…...

01Python相关基础学习

Python基础 模块相关导入模块sys模块 模块相关 导入模块 1. import 模块名 2. import 模块名 as 别名 3. from 模块名 import 成员名 as 别名sys模块 1. sys.argv 介绍: 实现从程序的外部想程序传递参数返回的是一个列表,第一个元素是程序文件名,第二个元素是程序外部传入的…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么&#xff1f;1.1.2 感知机的工作原理 1.2 感知机的简单应用&#xff1a;基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...