——关于对图片进行处理以减小应用运行内存的心得
一、存在问题
自己的APP大体完成后,该实现的基本功能都实现了,通过网络获取歌单信息、收藏歌单歌曲MV、听歌看MV、评论下载、修改个人信息等等功能也实现了。但是运行起来的效果不是很好,这个应用主界面的导航栏用了ViewPager嵌套ViewPager,同时加载了许多图片和从服务端获取的信息。因此每次返回到主界面都会崩溃,并报Out Of Merory(OOM)的错误。之前一直在完善各种功能,没怎么处理这个问题。等到现在,才发现,运行内存过大的话,APP的界面再好看,细节处理得再好也没用,因为用户在体验到这些亮点之前应用就已经崩溃了。所以解决运行内存过大的问题势在必行了。
二、问题分析
根据网上获取的经验(《Android APP内存泄露之调试工具》这篇文章提供的方法去查看应用的内存使用情况),我查看到我的应用的内存是这样的:
右上角DDMS—选Device—待查项目—Heap选项查看内存情况其中“1-byte array(byte[],Boolean[])”这一行就是应用里图片占的运行内存,我的项目居然达到了58M多。如果我跳转到了其他页面,继续加载图片,使用的内存会继续增加,然后项目很快就崩溃了。Logcat里面报Out Of Merory(OOM)的错误。这是因为虚拟机对项目的运行内存有限制,最多96MB。换真机进行调试,同样用DDMS查看运行内存可以发现,项目运行占用的内存最高居然达到了120多MB,而图片资源更是占了80多MB,随着运行内存的增加,在手机上运行的项目也不出意料地崩溃了。点击1-byte array(byte[],Boolean[])一看,吓了一跳。最大一张图片居然占了8.789MB!即使把网络断了,让APP不加载网络图片,只加载项目本身的图片资源,内存情况也有36MB,最大一张图片也有4.932MB(如下图)。
不连接服务端的项目运行内存情况百思不得其解啊!我自己在PPT里设计的一些简单的矢量图只有10KB左右,就算有一些用PS处理过的用来做背景的图片,以及服务端传过来的歌单封面图片,也基本不超过100KB。为什么在内存里一张图片却占了4.932MB乃至8.789MB的内存呢?
继续查资料,发现自己有一个认识误区,想当然地以为加载一张100KB的图片到ImageView里,占用的运行内存也只是100KB,其实不是这样的,事实上加载一张图片有可能要占用其大小许多倍的运行内存。其中具体的原理先不去了解,为了效率,先解决减小项目中图片资源占用内存过大的问题。
三、实验思路:
为提高项目的运行效率,避免项目OOM崩溃影响用户体验。我新建了一个实验用的项目,通过以下实验去验证影响图片资源占用运行内存大小的因素。
实验1、分别在同一页面加载两张内容相同、尺寸大小为100X100、格式分别为JPG和PNG的图片,对比其占用内存的大小;
实验2、分别在同一页面加载两张内容相同、格式相同、尺寸分别为100X100,50X50的图片,对比其占用内存的大小;
实验3、分别在同一页面加载两张内容相同、尺寸大小100X100、格式相同,第一次设置为visible,第二次设置为gone,对比其占用内存大小;
实验4、分别在同一页面设置1个ImageView和10个ImageView,均加载同一张图片,对比其占用内存大小;
实验5、分别在两个页面设置同一个ImageView,加载同一张图片,从第一个页面跳转到第二个页面,对比其占用内存大小;
实验6、分别在同一个页面用xml布局和Java代码去加载同一张图片,对比其占用内存大小;
实验7、分别在同一页面用Java代码写加载同一张图片的方法,第一次,调用该方法,第二次不调用该方法;对比其占用内存的大小。
实验图片的属性(宽高、格式)可以从图片名看出,如图所示:
图片的内容 图片的大小(非尺寸大小)四、实验结果:
实验1、第一次:test100jpg.Jpg 占用内存156.258KB
第二次:test100png.png 占用内存156.258KB
实验2、第一次:test100jpg.Jpg 占用内存156.258KB
第二次:test50jpg.jpg 占用内存39.070KB
实验3、第一次:test100jpg.Jpg 占用内存156.258KB
第二次:test100jpg.jpg(gone) 占用内存156.258KB
实验4:第一次:test100jpg.Jpg 占用内存156.258KB
第二次:test100jpg.jpg(10次) 占用内存156.258KB
实验5:第一次:test100jpg.Jpg (MainActivity) 占用内存156.258KB
第二次:test100jpg.jpg(SecondActivity) 占用内存156.258KB
实验6:第一次:test100jpg.Jpg(在XML布局添加) 占用内存156.258KB
第二次:test100jpg.jpg(在Java代码里用setImageResource()方法添加)占用内存156.258KB
实验7:第一次:test100jpg.Jpg(Java代码调用加载图片的方法) 占用内存156.258KB
第二次:test100jpg.jpg(Java代码不调用加载图片的方法) 不占用内存
五、实验结论:
1、图片占用的运行内存与图片格式无关。其实想想也知道,Android的图片资源对格式不敏感,加载图片只需要获取文件名,而不需要文件的格式后缀。
2、图片占用的运行内存与图片尺寸大体成正比。内容相同,100X100的图片占用内存为156.258KB,100X100=10000,50X50的图片占用内存为39.070KB,50X50=2500,刚好都是4倍。
3、图片的Visibility为gone,也需要占用相同内存。我们可以理解为先为ImageView这个图片控件加载了图片,然后再将这个控件设置为gone,因此,虽然ImageView控件被隐藏了,但是同样执行了图片的加载,因此需要占用内存。
4、相同的图片,加载一次和加载多次均只占用相同的内存。
5、相同的图片,只要加载过一次,即使在不同Activity继续加载,也只占用一份内存。
6、在xml里加载图片与在Java代码里用setImageResource()方法添加图片,所占用内存大小相同。
7、如果Java代码里加载图片的方法不被调用,则不需要占用内存空间。
六、对策方法:
根据以上的结论,再检查自己项目里的图片资源文件,发现了许多问题。比如我为求自己做的APP界面好看一点,用PPT画一些简单的矢量图,毕竟在网上直接找的图标,底色是白色的,而自己的APP的背景色有可能是别的颜色,唯有用PPT画一些矢量图,把填充色改为透明。然而这样做出来一些图标之后,虽然只有10KB不到,但是尺寸达到了400X400这个尺度,刚开始通过DDMS的heap up查询到的占用了4.932MB内存的图片,后来发现只是一张9KB,尺寸为433X389的矢量图。这些图片的尺寸并没有根据所要加载的控件大小去进行合理地设置,比如“播放”这一个ImageView控件的大小仅为60dpX60dp,那么“播放”这个图标的尺寸为400X400就没有必要了,完全可以将其的尺寸缩小。
根据发现的问题,做出了以下几点修改:
1、按照要加载的控件大小去重新设置图标的尺寸。
2、服务端的图片比如歌单封面、用户头像等也根据加载的控件大小去设计图标尺寸。
3、有些控件刚开始是不显示或者不被用户使用的,可以不在XML布局加载图片,而是在Java代码里面去加载图片。
七、总结:
经过调整,应用的运行内存大大减少。其中不连接网络,只加载本地图片资源的情况下,主界面的图片占用内存为2.590MB,连接网络后,主界面的图片占用内存也仅为8.595MB,相比优化前大大减少。
只加载本地图片资源 加载本地和网络图片资源为项目瘦身之后,顿时感觉神清气爽!立竿见影的是项目能够来回顺畅切换,而不至于因为内存溢出而崩溃了,终于可以继续优化自己的项目啦!接下来还是要在图片清晰度和内存使用合理程度上找到平衡,既保持界面的美观,也要保证应用能够顺畅运行,避免崩溃。毕竟自己当时也是没注意图片的尺寸,看到图片内容OK就直接往资源文件夹塞,所以希望这篇文章能够为Android的初学者提供一点帮助。
网友评论