1、为什么要缓存图片?
这个机制并非是处理内存占据大小的,而是优化用户体验,节省流量的(去网络获取,这种耗时长且损耗流量)。
PS: 由于我们的图片都是直接读取本地文件,所以,缓存图片意义不是很大。但官方既然这样设计了,估计还是有利于性能提升的。
2、为什么要压缩图片?
Android根据设备屏幕尺寸和dpi的不同,给系统分配的单应用程序内存大小也不同,具体如下表:
屏幕尺寸 DPI 应用内存
small/normal/large ldpi/mdpi 16MB small/normal/large tvdpi/hdpi 32MB small/normal/large xhdpi 64MB small/normal/large 400dpi 96MB small/normal/large xxhdpi 128MB xlarge mdpi 32MB xlarge tvdpi/hdpi 64MB xlarge xhdpi 128MB xlarge 400dpi 192MB xlarge xxhdpi 256MB
现在一些新款的手机,可以到192MB或者256MB,一些较老的(比如Android 4.2,2G内存),建议控制在64M以内。
显示图片,有两种方式,第一种Android自带的<ImageView>标签,如下:
<ImageView android:id="@+id/cover" android:src="@drawable/default_cover">
第二种,绘图:Canvas.drawBitmap(bitmap)
第一种是否占用APP内存,不得而知。但第二种Bitmap是占用内存的。
Bitmap即位图,图片定义为由像素点组成,每个像素点可以由多种色彩表示。
Bitmap占用的内存为:像素总数 * 每个像素占用的内存。在Android中,Bitmap有四种像素类型:ARGB_8888、ARGB_4444、ARGB_565、ALPHA_8,他们每个像素占用的字节数分别为4、2、2、1。
具体来说:
ARGB_8888:ARGB分别代表的是透明度,红色,绿色,蓝色,每个值分别用8bit来记录,也就是一个像素会占用4byte,共32bit。
ARGB_4444:ARGB的是每个值分别用4bit来记录,一个像素会占用 2byte,共16bit.
RGB_565:R=5bit,G=6bit,B=5bit,不存在透明度,每个像素会占用2byte, 共16bit.
ALPHA_8:该像素只保存透明度,会占用1byte,共8bit.
在实际应用中而言,建议使用ARGB_8888以及RGB_565。如果你不需要透明度,选择RGB_565,可以减少一半的内存占用。
一张1920*1080的分辨率图片,使用默认的ARGB_8888,那么大小是:1920*1080*(32/8)/1024/1024 = 7.91M
这样看来,一张8M的图片,还是不至于导致内存溢出(OOM),但是,倘若一个歌曲列表中,20首歌封面都显示原图,那就是160MB。
所以,要根据使用场景的图片尺寸,进行压缩:歌曲列表中的cover,都是小图,即使压缩很多倍,视觉上也没什么影响;但歌曲轮播图,是大图,压缩严重了,就影响视觉了。
图片缓存和压缩的实现细节是:
1、使用SQLite(covercache.db)存储图片缓存,存储格式为blob,获取是直接select by id。
2、使用图片压缩技术(BitmapFactory.Options),设置了两个级别的最大像素(SMALL:44sp, LARGE:200sp),压缩比例计算公式如下:
int sampleSize=1; // 100% long hasPixels = bopts.outHeight * bopts.outWidth; if(hasPixels > maxPxCount) { sampleSize = Math.round((int)Math.sqrt((float) hasPixels / (float) maxPxCount)); }
假设图片尺寸为1920*1080,压缩后:
sampleSize=平方根(1920*1080/200)=102
注意,实际生效的inSampleSize只能是2的次方,如计算结果是7会按4进行压缩,计算结果是15会按8进行压缩。
所以,LARGE级别的图片,实际压缩是64倍。
图片压缩的核心代码如下:
BitmapFactory.Options bopts = new BitmapFactory.Options(); bopts.inPreferredConfig = Bitmap.Config.RGB_565; bopts.inJustDecodeBounds = true; final int inSampleSize = getSampleSize(sampleInputStream, bopts, maxPxCount); /* reuse bopts: we are now REALLY going to decode the image */ bopts.inJustDecodeBounds = false; bopts.inSampleSize = inSampleSize; Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, bopts);
图片压缩技术说明:
将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的Width、Height和MimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。
由于不需要透明度,设置inPreferredConfig为RGB_565,可以减少一半的内存占用。