想自己做淘宝有什么网站吗,常州市网站建设设计,常州钟楼建设局网站,wordpress重新安装博客怎么搬家案例3——view内存泄漏 前文提到#xff0c;profile#Leaks视图无法展示非Activity、非Fragment的内存泄漏#xff0c;换言之#xff0c;除了Activity、Fragment的内存泄漏外#xff0c;其他类的内存问题我们只能自己检索hprof文件查询了。 下面有一个极佳的view内存泄漏例子…案例3——view内存泄漏 前文提到profile#Leaks视图无法展示非Activity、非Fragment的内存泄漏换言之除了Activity、Fragment的内存泄漏外其他类的内存问题我们只能自己检索hprof文件查询了。 下面有一个极佳的view内存泄漏例子它的操作步骤为
播放音乐唤醒音乐悬浮窗播放一段时间后关闭音乐悬浮窗重复步骤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执行情况影响。 那么我们该怎么优化呢
使用非匿名或静态的Handler弱引用处理此任务在主线程处理此任务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的引用关系那么我们该怎么做呢答案是
结束子线程任务清空Looper缓存的Message释放Handler
第一步结束子线程任务很简单 thread.interrupt() 本案例给Handler传入的是RunnbaleHandler未提供结束Runnbale的接口此项优化搁置 第二步清空Message 已知Looper提供了清空Message的接口
Looper#quitLooper#quitSafely主线程的Looper无法退出 已知Handler提供了释放Message的接口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缓存了MediaPlayListenerMediaPlayListener又是在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);}那为什么会未回收持续占用内存呢
抓拍hprof文件期间代码未执行到unregisterListener导致view内存未得到释放mMediaPlayListenerCacheList添加的listener与remove的listener不是同一个此处没有产生内存泄漏,判断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_handlerm_handler持有当前view 的引用m_handler如果长期存在那么view的内存也不会被释放 解决方法如下
定义外部类Handler定义静态内部类定义静态内部类弱引用 笔者采用了方案3 定义静态内部类 private static class UpdateHandler extends Handler {private final WeakReferenceMediaPlayerIml mediaPlayerImlWeakReference;private final WeakReferenceSeekBar seekBarWeakReference;private final WeakReferenceQQCircleProgressBar progressBarWeakReference;public UpdateHandler(MediaPlayerIml mediaPlayerIml, SeekBar seekBar, QQCircleProgressBar progressBar) {mediaPlayerImlWeakReference new WeakReferenceMediaPlayerIml(mediaPlayerIml);seekBarWeakReference new WeakReferenceSeekBar(seekBar);progressBarWeakReference new WeakReferenceQQCircleProgressBar(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;}}总结
总结我们针对此按理做的优化
静态Handler弱引用释放了对handler对view的引用让view及时销毁view占据的内存及时被垃圾回收器释放释放了Message对view的引用,在view及时退出界面的时候立即斩断message对view 回顾一下优化前的实例数量多次操作,隐藏展示悬浮窗之后内存中存在多份悬浮窗实例之前创建过的悬浮窗内存一直无法被回收: 优化后效果多次操作当屏幕上存在一个view时只存在一份view实例