Android图片优化(一)

857 阅读5分钟

Bitmap到底占多大内存?

从本地加载或者从网络加载可以用下面的公式计算:

  • Bitamp 所占内存大小 =图片的长度 * 图片的宽度 * 一个像素点占用的字节数

Bitmap在资源目录中的计算公式为:

  • Bitamp 所占内存大小 = 宽度像素 x (inTargetDensity /inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小

    • 这里 inDensity 表示目标图片的 dpi(放在哪个资源文件夹下),inTargetDensity 表示目标屏幕的 dpi,所以你可以发现 inDensityinTargetDensity 会对Bitmap 的宽高进行拉伸,进而改变 Bitmap 占用内存的大小。
    • 如果使用的低分辨率的文件夹的图片在高分辨率的手机上展示,会放大图片,使得图片占用内存更多。因此,现在一般做法是,适配最主流的高分辨率设备的图片。这样在低分辨率设备使用的时候,图片缩小没有影响图片质量。反之,放大图片会降低图片质量,还会占用更多内存。

图片缓存

软引用缓存

Java的四种引用

  • 从网络上获取图片,可以通过软引用缓存起来,可以一定程度上防止因为加载了过多图片而出现OOM。
  • 但是在实践中,只使用软引用作为图片缓存的手段效率是比较低的,因为不能控制软引用的图片什么时候被系统回收。而需要被频繁使用的图片也可能被回收,导致要重新从网络上下载。因此,不推荐单独使用软引用作为App中缓存图片的唯一形式。

二级缓存:内存缓存+磁盘缓存

二级缓存的流程图

  • 二级缓存的流程如图所示,加载图片时先从内存缓存中查找,没有找到再去磁盘缓存,再去网络加载。网络加载之后把图片加入到磁盘缓存和内存缓存中。
  • 内存缓存的生命周期是直到App被系统销毁和回收。磁盘缓存是以文件的形式缓存在设备上,继续App重启仍然可以加载到磁盘缓存的内容。
  • 内存缓存常用的有LruCache,磁盘缓存常用给的有开源库DisLruCache。

LruCache的简单介绍

  • LruCache是Android所提供的一个缓存类。
  • LruCache使用了LRU算法的思想,底层的数据结构是 LinkedHashMap。
    • LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
  • LinkedHashMap = LinkedList + HashMap
    • HashMap的操作的时间复杂度是O(1),但缺点是,存放元素是无序的。
    • LinkedHashMap通过维护一个额外的双向链表保证了顺序。这个顺序可以是插入顺序,也可以是访问顺序。

采样率压缩

  • BitmapFactory 给我们提供了一个解析图片大小的参数类 BitmapFactory.Options ,把这个类的对象的 inJustDecodeBounds 参数设置为 true,可以在不把图片加载到内存的情况下获得图片的宽高。这是一个轻量级的操作。

  • BitmapFactory.Options缩放图片主要用到inSampleSize采样率,

    inSampleSize = 1,采样后图片的宽高为原始宽高

    inSampleSize > 1,例如2,宽高均为原图的宽高的1/2,内存占用为原来的4分之一

  • 比如我们有一张10241024像素的图片(假设图片是ARGB_8888类型,即每个像素点占用4个字节) ,将inSampleSize的值设置为2,就可以把这张图片压缩成512512像素。原本加载这张图片需要占用4M的内存,压缩后就只需要占用1M了,内存占用为原来的4分之一。

    • 比如我们有一张10241024像素的图片(假设图片是ARGB_8888类型,即每个像素点占用4个字节) ,将inSampleSize的值设置为2,就可以把这张图片压缩成512512像素。原本加载这张图片需要占用4M的内存,压缩后就只需要占用1M了,内存占用为原来的4分之一。
  • 下面的方法可以根据传入的宽和高,计算出合适的inSampleSize值:

public static int calculateInSampleSize(BitmapFactory.Options options,
		int reqWidth, int reqHeight) {
	// options中获取源图片的高度和宽度
	final int height = options.outHeight;
	final int width = options.outWidth;
	int inSampleSize = 1;
	if (height > reqHeight || width > reqWidth) {
		// 计算出实际宽高和目标宽高的比率,
		final int heightRatio = Math.round((float) height / (float) reqHeight);
		final int widthRatio = Math.round((float) width / (float) reqWidth);
		// 选择最小的比率作为inSampleSize的值
		inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
	}
	return inSampleSize;
}

Bitmap的颜色和对应占用内存

  • Bitmap.Config ARGB_4444:即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位

    Bitmap.Config ARGB_8888:即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位

    Bitmap.Config RGB_565:即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位

    Bitmap.Config ALPHA_8:只有透明度,没有颜色

  • Glide加载图片默认格式RGB565,Picasso为ARGB8888,默认情况下,Glide占用内存会比Picasso低,色彩不如Picasso鲜艳,自然清晰度就低。

  • 我们通常会使用Bitmap.Config.RGB_565这个配置,因为

    • Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,
    • Bitmap.Config.ARGB_4444显示图片不清楚,

    • Bitmap.Config.ARGB_8888占用内存最多。

  • 可以通过改变图片的颜色编码来减少内存占用:RGB_565(一个像素占2个字节)替代ARGB_8888(一个像素4个字节)

//改变图片的色彩编码
options.inPreferredConfig = Bitmap.Config.RGB_565;