版权声明:本文章原创于 RamboPan ,未经允许,请勿转载。
记录一次使用 Android Profiler 分析内存
最近手里开发了一个小应用,功能也不复杂,大致功能是:
-
加载本地的视频及生成缩略图,用户选择一个点击按钮进行观看,再点击一次结束观看。
-
记录时间差,然后跳转一个新界面,把时间差动态加载出来。
-
点击按钮主动 或者 倒数30秒 后返回主菜单。
也不难,贴一个做完了的效果。
虽然这个应用不大,我们也需要严谨是吧,也分析下代码中有没有导致内存泄露的情况。
这次做个笔记。
这里使用 Android Profiler 进行分析,分析内存中的对象是否因为不合理的写法,在页面关闭后未能被回收。
先说说这个小应用,主要分为 3 个页面:配置页面、播放页面、结果页面。
主要是把 assets 目录中的本地文件复制到需要使用的设备上。完成第一次使用配置。
做这种给他人使用的应用,多加一些操作说明方便他人,也给自己节省时间(别人询问的时间)。
加载视频以及生成缩略图,这里用了 LruCache 做了一层内存缓存(用户切换视频时加载更快),也生成了对应图片文件做了一层硬盘缓存(下一次进入时直接读取本地文件)。
这个界面就一个倒计时与动态显示秒数,因为并没有要求特别复杂的动态显示,直接采用间隔时间修改 TextView ,做了一个简单的任务定时修改 TextView 。返回可以通过点击按钮返回,也可以是倒计时到时间返回。
我们开始通过 Android Profiler 启动这个应用。
然后点击 Memory 这部分。
先看第一个界面的内存分析。
因为我点击复制文件之后,复制了几个较大的视频文件。从波浪的图形中能够看出内存不断使用。
每复制完成一个文件后,占用内存变小再进行下一次增大,结束最后一次复制后和刚开始时内存占用差不多。
这里看着没有问题,我们直接进入第二个界面分析。
才进入的时候,因为需要生成缩略图文件以及内存缓存,所以内存占用迅速增加。
这里使用 Dunp Java Heap 按钮,获取堆信息。
能看到有 PlayActivity 与 PlayActivity$ ,这里 PlayActivity $ 表明 PlayActivity 的某个内部对象正在引用外部的 PlayActivity 。
从右边也能看出,有些 view 正在加载图片,这次是才进入播放界面采集的堆信息,有图片正在生成与加载肯定是正常的。
先说明下我们需要观察的几个参数,见下图:
- Allocations : 代表总的实例数量。
- Shallow Size : 代表这些实例自身所占的大小。
- Retained Size : 代表这些实例及其引用所占的所有大小。
现在对比下上面的图,我们只进入了一次 ResultActivity ,所以对应 PlayActivity 与 PlayActivity$ 的 Allocations 都是 1 。
后面我们会测试几次进入,可以再观察这个值。
我们接着刚才的说,耐心等上一小会,我们手动 GC 一次进行清理,再获取堆信息。
一般应用在 GC 之后能发现明显的内存减少。
这次能看到因为图片已经加载完成,所以只留下了一个 PlayActivity 实例。这里看也是没有什么问题的。
(6 个 FrameImageView 是因为主界面有 6 个场景选择的 View。)
现在我们点击观看视频,等待一段时间后,结束观看,此处开始跳转结果页面。
第一个上升趋势是点击按钮后视频开始播放,第二个上升趋势是跳转了结果页面。
然后我们点击结果页面的返回按钮。再过一小段时间,我们点击 GC 按钮,再取一次堆信息。
第一个下降趋势是点击返回时,部分内存直接收回。第二个下降趋势是点击 GC 后再次释放的内存,
从图里能看见,除了 ResultActivity 没有被回收,还有一个 ResultActivity $ 1,我们点击之后能看到有一个 mOnClickListener in View$Listener ,那么这个代码对应在哪呢 ?
对应在屏幕中央的返回按钮,我使用了一个匿名的 OnClickListener 。
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
看了 ResultActivity$1 ,我们再来看 ResultActivity 这个实例,点开之后发现都是资源有关的引用。
从这来看,也没有什么大的问题,回收肯定会回收,只是时间会等得更久一点。
稍等一会再回来,再次点击 GC 按钮,只剩 PlayActivity,也符合之前的判断。
这次测试是从结果页面返回播放界面,等待了一会之后点击 GC 再进行堆信息获取。
那么如果点击返回之后立刻获取堆信息会怎么样 ? 现在来试一试。
怎么突然多了这么多 ResultActivity$ !!!
既然出现了这么多,肯定是有原因的。点开 ResultActivity$2 发现是倒数计时的 View 以及不断倒计时的 String 。
因为需要不断更新 TextView 的文字,肯定需要一个定时的任务或者线程来执行。我这是把 TextView 和线程封装了一下。
因为之前已经想到了可能会提前结束,所以在 onDestroy() 方法中加入了对任务的停止,
当然放在点击按钮时处理也是可以的。
@Override
protected void onDestroy() {
if (mCountDownTask != null) mCountDownTask.stopTask();
super.onDestroy();
}
所以此处产生了很多 String 对象,也算正常。再点开 ResultActivity$3。
能看到有个 val$alpha ,还有刚刚的 View ,因为这个 View 结束时会闪动一下,我这是通过修改透明度实现的,所以此处存在很多透明度值也是正常的。
还有个 ResultActivity$1 和之前是一样的,那个匿名 OnClickListener。
此时我们再点击 GC 发现因为倒计时 View 产生的多个对 ResultActivity 的引用已经不在了。
剩下两个 ResultActivity$ 仍然是之前分析的匿名 OnClickListener 与 Resultactivity 中的一些资源引用。
再次等待一会后 GC ,能看到 ResultActivity 相关的都已经回收了。
我们测试了单次(观看视频后跳转结果再退回主界面) ,现在我们来多测试几次,再获取一次堆信息。
能看到 ResultAcitivity 相关的都多了很多,至少都是 4+,点开其中一个发现都是重复的,说明确实是因为几次使用增加了相关的对象,然后我们耐心等待后再 GC 一次,发现 ResultActivity 还剩一个,后面就没再截图了。
从这些分析暂时看出代码中没有内存泄露的迹象。
如果有不妥之处或不同意见,烦请留言,互相提高。
Android Profiler 参考资料:
https://developer.android.com/studio/profile/android-profiler?hl=zh-cn