这两天在整理关于Android中图片的内存管理问题,这里稍微做一下总结,本文主要目的是总结知识点,不会列出详细实现方式,但是会列出从哪里能学习到这些知识点。
一、bitmap所占内存大小
首先列出一条公式,
一张bitmap占用内存 = 长度 * 宽度 * 一个像素占用的内存
此处的长度和宽度对应图片像素,而一个像素占用多少内存,这是通过我们平时经常看到的ALPHA_8,RGB_565,ARGB_4444,ARGB_8888来设置的。这四个值是Bitmap对象的一个内部枚举类Bitmap.Config的四个属性,它们各自的特点如下:
ALPHA_8:一个像素占用一个字节,此处的8是指一个字节的透明度,图片不显示颜色值,只显示透明度,其实这个属性如何应用我还没完全探索明白,后续再研究。
RGB_565:一个像素占用2个字节,此处的565是值三个字节的RGB的三原色,这个值可以应用于无关透明与半透明的图片,可以大大降低图片所占内存。
RGB_4444:一个像素占用2个字节,该属性已经被废弃,其特点和RGB_8888类似,只是降低了图片质量和内存占用,从API=19开始,使用这个值也会默认成使用RGB_8888.
RGB_8888:一个像素占用4个字节,android系统使用该值做为默认值,毋庸置疑,该值是图片质量最高的,也是占用内存最多的。
Bitmap.Config的设置主要在两个地方使用,一个是Bitmap的createBitmap方法
另一个是BitmapFactory内部类Option的inPreferredConfig属性,平时我们经常使用isSampleSize属性来压缩图片的像素,其实还可以通过inPreferredConfig设置每个像素占用几个字节,对于不透明的图片,可以使用RGB_565减少每个像素占用的内存。
二、图片压缩
平时我们都是通过isSampleSize压缩图片,这个知识点应该是总所周知的。需要注意的是,如上所说,我们同时也可以借助inPreferredConfig.
三、listview等控件中并发的问题。
当使用viewholder的模式加载listview,某些正在下载图片中的view,可能已经被回收给其他view复用了,当图片下载完,会使得在新的view上呈现其他view的图片,从而导致顺序错乱,为了解决这个并发异步的问题,需要建立url–Imageview–AsyncTask的一一对应关系,这样就可以完美解决这个问题,这个知识点可以参考以下两篇文章
http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html
http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
四、图片缓存
平时大家都使用LruCache作为内存缓存,DiskLruCache作为本地缓存。前者可以在UI线程进行,后者则不可以,并且后者得考虑并发的问题,因为可能DiskLruCache初始化还没完成,已经有线程准备去操作DiskLruCache了。所以需要做类似如下操作:
此处提一个小技巧,为了避免LruCache跟着Activity被销毁(比如屏幕旋转),可以将LruCache放在一个设置了setRetainInstance(true)的没有UI的fragment中。以下代码片段来自于Displaying Bitmaps Efficiently几篇文章中的例子DisplayingBitmaps,其中借助RetainFragment保存ImageCache对象,ImageCache对象不只持有LruCache,还有DiskLruCache。
五、内存回收以及内存共享
3.0之前
3.0之前,Bitmap对象保存在java堆Dalvik heap中,而图片像素数据保存在native memory中,前者有垃圾回收机制可以回收内存,recycle()方法是针对后者加快回收内存的,所以,在3.0以前,内存的回收推荐使用recycle()方法。
单个Bitmap的使用场景,我们很容易知道何时调用recycle()方法,而在listview这种类型的控件中,为了把握Bitmap被回收的时机,我们可以借助引用计数,使Bitmap在没有被使用没有被缓存时调用recycle()。
3.0开始
3.0开始,Bitmap对象和像素数据都保存在java堆Dalvik heap中,二者的回收都有垃圾回收机制可以负责,所以3.0开始,recycle()并没有调用的必要性。但是依然有优化内存的方法,我们可以使用 BitmapFactory.Options.inBitmap属性,该属性可以使得多个Bitmap对象共用内存,这里说共用内存其实不准确,说复用可能更准确,其实是一个不被使用的Bitmap,其内存会被记录起来,后续被应用于其他Bitmap对象。以下是 BitmapFactory.Options.inBitmap字段的文档。
从API=19开始,被复用的Bitmap占用内存大小只要大于新的Bitmap即可.在API=19之前,新的Bitmap必须和被复用的Bitmap占用内存大小一样,而且新的Bitmap需要jpeg或者png格式,isSampleSize需要等于1,而且inPreferredConfig也会被复用。
六、学习资源
1,Android官网的Displaying Bitmaps Efficiently几篇文章绝对是精华中的精华,以上很多知识点启示网上很多人都有写,但是我相信都是来自阅读这几篇文章。
2,Displaying Bitmaps Efficiently这一系列文章有一个demo叫DisplayingBitmaps,这个demo结合了这几篇文章的所有知识点。