看完了文档里关于图片介绍的几篇文章,结合项目和一些网上的文章,谈下在android平台显示图片的一些个人想法。
1.从设计角度,最好不要让手机显示大于手机屏幕数倍的大图片,如果源图是个大图,加载时把图片缩小到屏幕大小,使用在decode图片时使用option选项,直接加载缩小后的图片。网上不少人说先读取图片InputStream流,再通过BitmapFactory.decodeStream(InputStream
is)加载速度会快些,我看了api level
14的代码实现,无论调用哪个decode方法,最后都是调用的这个方法。电脑上没低版本的源码,不确定低版本是不是也是这样实现。
2.加载一张图片,无论是为控件设背景图还是显示一张图片,如果图片源不是在内存中的,最好不要直接在UI线程中加载它,推荐使用AsyncTask异步加载图片。原因很简单,在UI线程加载图片,加载期间会阻塞UI线程,如果加载图片时间太长了,会产生ANR问题。
这里有个疑惑,如果图片是资源文件,要不要使用异步加载?我认为资源文件没有必要使用异步加载。通常使用的方法有BitmapFactory.decodeResource(Resource
res, int id, Options opts) 或者 setBackgroundResource(int
resid)。他们最终都是通过AssetManager.openNonAsset(int cookie, String
fileName, int
accessMode)获取一个InputStream,而这个方法是个native方法,没有去读它的native实现,但android引入资源文件这一机制,而这一机制并没有为写代码时带来太多帮助,那么它应该是文件加载优化的产物。
3.对于缓存,文档建议不要超过初始分配内存的1/8。我觉得这个不要滥用,因为不需要缓存的,你缓存了,白白被消耗一部分内存。也不要拘泥于这个百分比,因为在android中图片是消耗内存的大户,普通手机1/8的初始内存,还缓存不了两张全屏的大图,命中率太低了还不如不用缓存。对于需要大量显示图片的应用或模块,你内存不给它用,还留着干什么。
4.谨防超大屏幕。对于超大屏,显示图片消耗的内存是惊人的,所以你的应用在显示图片时,要提醒自己它有可能应用到超大屏的手机上。如果一个activity中需要持有几个大图(或者源为小图,但需要被拉伸为大图)的引用,一定要记得在activity销毁时,单独释放图片的内存,因为你退出activity,通常系统并不会销毁它,所以它持有的引用也会保留。如果需要几张大图叠加实现某种效果,最好单独加载每张图片,把它画到一张画布上,把原来的图片释放。这样的话,最终只消耗一张图片大小的内存。
自己开发中遇到过一个case:
有几款手机,在进入某个activity时很容易crash,我检查了代码,没有什么错误。实现的功能就是显示一个普通的全屏场景,场景是由几张图叠加出来的,一张背景,一张草地等等,UI基本是加载layout.xml实现,在代码中仅仅对UI做了少量微调。分析下内存发现,一进入这个activity,堆内存就增加35M左右,而且退出activity后,这些内存并没有减少。之前觉得不合理,后来发现手机竟然是1960x1080的分辨率,一张全屏的图片大小差不多要消耗8M内存,这一堆图加起来如果有3张全屏图大小,就得24M内存,再加上一些动画和其它缓存,占三十多兆也不足为怪了。最后解决办法是分别加载每张图片,画到一张上面,释放原来的内存。在activity结束后,把图片内存释放。测试结果是,这样优化后,不仅解决了crash,整个应用速度也流畅不少。