记一次Android内存优化

前言

MAT是闻名海内外的内存泄漏检测工具,虽然不能指望MAT告诉我们哪里泄漏了,但是,借用这个工具,可以分析、定位出很多内存占用和泄漏的问题。最近做了公司内某款应用内存占用和内漏问题的测试。偷得半日闲,总结下经验教训。因开发仍在优化问题解决中,暂时总结问题检测方法。
特别鸣谢:明哥、田姐姐,良师益友的存在,一直是我前进道路上的灯塔。

  • 直接下载独立版:http://www.eclipse.org/mat/downloads.php
  • 下载eclipseIDE插件:打开Eclipse - >help - > Install New Software,在work with输入:http://download.eclipse.org/mat/1.6/update-site/
  • 为防止内存镜像过大,造成堆溢出,设置 …\IDE\eclipse\eclipse.ini,修改-Xmx为:-Xmx2048m

2.ImageMagick(支持MAC、Linux、Windows):用于转化字节数组为图片
下载地址:http://www.imagemagick.org/script/download.php
3.MPA助手(公司自己开发的用于监控性能数据及内存分析工具),用于监控性能数据:
4.dump内存镜像和镜像转化脚本:https://github.com/stayhungryYu/memory

*另外附带用于某场景验证的脚本,Android内存dump文件到PC并转化

理论基础

内存:

记一次Android内存优化_第1张图片
进程总内存占用: 220.59M
1)JavaHeap: 90.08M
2)NativeHeap:native层的 so 中调用malloc或new创建的内存 —— 28.44M
3)Graphics:OpenGL和SurfaceFlinger相关内存 ——28.15M
4)Stack:线程栈——1.81M
5)Code:dex+so相关代码占用内存——64.16M
6)Other:7.96M
上述6中内存占用除了两种不需要考虑,其他5中通通需要优化。不需要考虑的是:
Other:暂时无从分析
Graphics:若应用没有直接调用OpenGL,则可以确定这部分内存是由Android Framework操控的,可以忽略。(当然对于游戏类应用,这里肯定是优化重点。)

测试前的小插曲

  1. MAT(或者说整个DDMS)检测并显示进程,需要APP打开debugable开关:AndroidManifest.xml文件里配置,android:debuggable=“true”
  2. 测试场景、用例可以详细,但无需过度详细,主要关注:
  • 应用内页面之间的跳转切换、视图窗口的开与关——看是否及时释放(有些应用为了快速恢复现场,选择不释放,视项目而定)及是否有泄漏泄漏
  • 应用之间的切换(跳转),应用前后台切换——看是否及时释放(有些应用为了快速恢复现场,选择不释放,视项目而定)及是否有泄漏泄漏
  • 前后、后台台静置——看是否有持续内存增加,
    (如果支持)设备横竖屏转屏——看是否有内存泄漏,主要是重复创建问题

MAT问题排查套路(其实并没有套路,只有经验)

网上几个不错的帖子:
  • Android 内存泄露实践分析 https://testerhome.com/topics/5822
  • Android 性能优化之使用MAT分析内存泄露问题 https://blog.csdn.net/xiaanming/article/details/42396507
  • 使用MAT (Memory Analyzer Tool)分析Andriod项目内存泄漏 https://www.jianshu.com/p/41e3aaf915e5
  • Android 应用内存泄露分析、改善经验总结 https://testerhome.com/topics/9637
自己琢磨:

另外,MAT自带帮助文档也不错,其中最开心的是,发现了这个根据地址查询
select * from OBJECTS 0xc6993000
记一次Android内存优化_第2张图片

应用明显问题优化

经典问题1:图片超尺寸加载

  • 问题现象:在应用内存分析时,发现一张图片占用56M内存。
  • 问题分析:
    • (1)首先怀疑资源问题,可能是图片自身过大,导致占用内存过大,通过查看,图片为1080x1920的图片,理论上,这张图片占用内存大小为1080x1920x4≈7.9M。图片本身大小应该不是导致内存过大的原因。
      记一次Android内存优化_第3张图片
    • (2)查看内存中图片大小为2880*5120,长宽均为原来的2.7倍,想到极有可能是Android自身适配造成的。经分析,测试手机屏幕密度为640,图片放在“\res\drawable-hdpi-v4”下,hdpi对应的手机屏幕密度为240,640/240≈2.7,这数值与放大倍数相符,正好解释了图片长宽均被放大2.7倍,内存放大7倍。
  • 问题原因:
    开发将图片放错资源目录。
  • 解决方案:
    • (1)优先高密度目录:对于不确定的图片,尽量放到高密度目录下。
    • (2)更换API:Drawable.createFromSream替换getresource().getDrawable。

经典问题2:关于图片解码配色设置

  • 问题现象:
    6张欢迎页图片过大,每张高达7.9M(1080x1920x4≈7.9M)
  • 问题分析:
    6张欢迎页图片过大,每张高达7.9M,因为测试机的屏幕分辨力为1080x1920,1080x1920x4≈8M,推断开发使用的是RGB888格式使用的图片,建议开发在不影响用户体验的基础上降低内存,可以使用RGB565,这样一张图片可以节省1080x1920x((24-16)/8)≈2M空间。
    这6张图是背景图,尺寸巨大,色泽相对单一,改为RGB565,不会影响效果,又能降低内存
  • 问题原因:
    不恰当的使用了对图片质量要求高的GRB888
  • 解决方案:
    使用RGB_565解码图片

经典问题4:图片加载无用资源

问题现象:加载了未使用到的图片,占用1M
记一次Android内存优化_第4张图片
解决方案:
删除未使用的图片

经典问题5:统一缓存,避免资源重复

  • 问题现象:
    不用业务、组件,使用同一张图片,却重复申请了内存,多占用内存约1.2M
    记一次Android内存优化_第5张图片
  • 问题分析
    对“看起来一样的图片”进行分析,发现他们被不同的业务组件引用,重复申请了内存
  • 问题原因:
    没有使用统一缓存
  • 解决方案:
    使用公共图片缓存库

经典问题6:错误使用PDF播放器

  • 问题现象:
    377.6K的PDF,打开后,需要占用40M的内存(PDF为图片拼接)

  • 问题分析
    记一次Android内存优化_第6张图片
    记一次Android内存优化_第7张图片

  • 问题原因:
    使用集成com.artifex.mupdfdemo的PDF阅读器,将PDF转成图片,然后呈现消耗内存较大

  • 解决方案:
    改使用第三方PDF阅读器,降低风险

经典问题7:重复创建线程导致内存泄漏

  • 问题现象:相机从后台切换到前台,出现一连串的GC日志且内存使用和内存堆都在逐渐增加。
    记一次Android内存优化_第8张图片
  • 问题分析
    使用DDMS获取操作后的内存镜像文件HPROF,并用MAT工具打开进行分析,MAT问题报告显示可能泄露的是Byte和Bitmap占用大内存,未发现异常。
    接着在重复一次前后台切换操作后,获取HPROF文件并用MAT打开,这时问题报告中显示出com.xx.xxx.xxxxxcamera.gif.GifView D r a w T h r e a d 占 用 了 很 大 内 存 。 对 比 前 后 2 次 的 内 存 信 息 发 现 c o m . x x . x x x . x x x x x c a m e r a . g i f . G i f V i e w DrawThread占用了很大内存。对比前后2次的内存信息发现com.xx.xxx.xxxxxcamera.gif.GifView DrawThread2com.xx.xxx.xxxxxcamera.gif.GifViewDrawThread被重复创建了。
    重复操作前:
    记一次Android内存优化_第9张图片
    重复操作后:记一次Android内存优化_第10张图片
  • 问题原因:
    在拍照界面的右下角有一个用来显示最近拍的一个照片,以GIF模式生成的照片,在Activity的onResume方法中使用new GifView来显示Gif照片,并且创建DrawThread线程来处理,重复前后台切换导致onResume方法重复执行最后导致线程多次创建。
    线程作为GC root在没有自己结束前对应的资源是无法被GC回收的

经典问题8:Activity泄露

  • 问题现象:
    在测试某自研浏览器,使用了一段时间后,点击打开下载管理,响应明显变慢了,监控CPU发现CPU使用率没有特别高,再从Logcat中过滤GC日志出来,发现内存堆在持续增加,并且每次GC导致的线程暂停时间已经到了94ms,正常1秒24帧即41.6ms人眼不会感受明显卡顿,而线程暂停时94ms,超过最低限制的2倍所以会感受到卡顿了。
    在这里插入图片描述
  • 问题分析:
    使用DDMS获取hprof文件,再使用MAT打开分析,很快发现DownLoadManActivity存在多个,在属性面板查看到这是CustomPopupWindow的子类,PopupWindow是基于Activity上的一种窗口,
    在这边已经基本可以确定是下载窗口泄漏了,
    记一次Android内存优化_第11张图片
    从另一方面,使用dumpsys meminfo 命令查看每次打开下载管理,Views都在增加,并且关闭了下载管理数量也没有减少。
    记一次Android内存优化_第12张图片
  • 问题原因:
    要释放PopupWindow资源需要调用dissmiss()方法,绿色上网中点击关闭下载管理只是让弹窗不可见,再打开的时候却又重复new了DownLoadManActivity,这样对象没有释放掉,又重复创建新对象导致了这里的内存泄露。

经典问题9:图片泄露

  1. 图片是占用的内存应用内存最主要的一部分,当发现应用内存使用比较高时,可以分析是否存在图片使用不合理,甚至是图片泄露等,以个人中心为例
    先使用DDMS获取HPROF文件,再打开MAT进行分析,查找出Bitmap的所有实例
    记一次Android内存优化_第13张图片
  2. 把图片的buffer保存成rgba文件,利用ImageMagick工具还原图片:
  • 选中一个Bitmap实例,在左边属性栏选择mBuffer导出格式为rgba的文件
    记一次Android内存优化_第14张图片
  • 生成rgba文件后,在cmd中进入ImageMagick工具的根目录,运行命令:
    convert.exe -size mWidthxmHeight -depth 8 <生成的.png>
    注:mWidth、mHeight对应Bitmap属性中的宽与高,一般情况下默认depth都是8
    在这里插入图片描述
  • 查看生成后的图片,发现有4张注册才用到的953K的图片,这4张图片就浪费了3.8MB的内存空间了
    记一次Android内存优化_第15张图片
  • 分析图片的GC 路径,发现还是因为LoginActivity泄露导致的
    记一次Android内存优化_第16张图片

优化成果

记一次Android内存优化_第17张图片

套路工具化

dump内存镜像和镜像转化脚本
大图片导出
图片超尺寸加载
检查重复activity、线程
检查重复图片

未来

缓存监控与优化
线程创建分析
smaps 文件分析

参考资料

  1. 手Q Android缓存监控与优化实践:https://mp.weixin.qq.com/s/_6xXb9WckYKAHPMCYJ5VGA
  2. Wetest-我这样减少了26.5M Java内存!:https://mp.weixin.qq.com/s/ZGpGXM8wGiSr-jtrxU3ALA
  3. Wetest-Android 内存暴减的秘密?!:https://mp.weixin.qq.com/s/4YS4QW1lo0LiyuApFznMVA
  4. Android高效内存1:一张图片占用多少内存:https://www.cnblogs.com/popfisher/p/6959106.html
  5. Android高效内存:让图片占用尽可能少的内存:http://www.cnblogs.com/popfisher/p/6770018.html
  6. 经典面试题1:图片占多少内存:https://www.jianshu.com/p/1af904e9a6e4

你可能感兴趣的:(Android性能测试)