当前位置: 首页 > 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 介绍: 实现从程序的外部想程序传递参数返回的是一个列表,第一个元素是程序文件名,第二个元素是程序外部传入的…...

效率提升利器:用快马ai生成jdk多版本一键切换与配置管理工具

作为一名经常需要切换不同JDK版本的开发者&#xff0c;我深知手动配置环境变量的痛苦。每次切换项目时&#xff0c;都要反复修改JAVA_HOME和PATH&#xff0c;还要担心配置出错影响其他项目。最近发现InsCode(快马)平台可以快速生成JDK管理工具&#xff0c;彻底解决了这个痛点。…...

Claude Code 进行“从头重写”的项目 Claw Code全面介绍(claw-code)

一个对泄露的 Claude Code 进行“从头重写”的项目&#xff0c;已成为 GitHub 上增长最快的仓库。一、项目是什么 claw-code&#xff08;仓库全名写作 Rewriting Project Claw Code&#xff09;是一个围绕「智能体 Harness&#xff08; harness&#xff1a;把模型、工具、会话与…...

【人生底稿】08:2018 北京创业 180 天(上):第一次进京,从高铁上的微信到鸟巢旁的工位

2018年6月11日&#xff0c;天津到北京的高铁上&#xff0c;我第一次踏上去北京的路。 89年生的我&#xff0c;天津人&#xff0c;家离北京不过半小时高铁&#xff0c;活了快30年&#xff0c;居然从来没正经去过北京。 这不是什么逆袭爽文&#xff0c;是一个30岁程序员&#xff…...

为什么选择Clef Handbook:15个创新政策彻底改变传统雇佣关系

为什么选择Clef Handbook&#xff1a;15个创新政策彻底改变传统雇佣关系 【免费下载链接】handbook An employee handbook built for inclusion 项目地址: https://gitcode.com/gh_mirrors/handbook6/handbook Clef Handbook是一份为包容性而构建的员工手册&#xff08;…...

从Python入门到AI项目实战:以NLP-StructBERT部署为第一个完整案例

从Python入门到AI项目实战&#xff1a;以NLP-StructBERT部署为第一个完整案例 你是不是刚学Python&#xff0c;对着书本上的print(“Hello World”)感觉有点无聊&#xff1f;或者学了一堆语法&#xff0c;却不知道这些for循环、if判断到底能用来做什么实际的东西&#xff1f; …...

AI驯服“人造太阳”:等离子体物理的智能革命

AI驯服“人造太阳”&#xff1a;等离子体物理的智能革命 引言 可控核聚变&#xff0c;被誉为人类能源的“终极梦想”。然而&#xff0c;驾驭上亿度的等离子体——这团“人造太阳”&#xff0c;其复杂性与不稳定性让科学家们挑战了数十年。如今&#xff0c;人工智能&#xff08…...

ai辅助开发:为openclawskills网站打造智能个性化教程推荐引擎

最近在帮朋友优化他的技能学习网站openclawskills&#xff0c;想给用户增加个性化推荐功能。作为一个独立开发者&#xff0c;借助InsCode(快马)平台的AI辅助开发能力&#xff0c;整个过程比想象中顺利很多。记录下这个智能推荐系统的实现思路&#xff0c;或许对其他想做类似功能…...

Windows输入自定义的现代化实践:QKeyMapper实现免重启的键盘映射与虚拟手柄方案

Windows输入自定义的现代化实践&#xff1a;QKeyMapper实现免重启的键盘映射与虚拟手柄方案 【免费下载链接】QKeyMapper [按键映射工具] QKeyMapper&#xff0c;Qt开发Win10&Win11可用&#xff0c;不修改注册表、不需重新启动系统&#xff0c;可立即生效和停止。支持游戏手…...

HunterPie终极指南:免费提升怪物猎人世界游戏体验的完整教程

HunterPie终极指南&#xff1a;免费提升怪物猎人世界游戏体验的完整教程 【免费下载链接】HunterPie-legacy A complete, modern and clean overlay with Discord Rich Presence integration for Monster Hunter: World. 项目地址: https://gitcode.com/gh_mirrors/hu/Hunter…...

网站 SEO 优化培训的优势和局限性是什么

网站 SEO 优化培训的优势和局限性是什么 在当今数字化时代&#xff0c;网站 SEO 优化培训已经成为许多企业和个人提升网站流量、吸引更多潜在客户的重要手段。对于这种培训的优势和局限性&#xff0c;有许多人仍不够了解。本文将从多个角度详细探讨网站 SEO 优化培训的优势和局…...