Bitmap压缩、占用内存大小计算

3,105 阅读7分钟

png,jpg只是图片信息的容器,经过相应的压缩算法将图片的像素点信息保存,图片文件的大小和图片在显示设备中所占的内存大小完全不是一个概念。

一、压缩

采样压缩:改变图片宽高,减小内存占用;
质量压缩:改变图片色彩深度等因素,宽高不变,不能减小内存占用,可减小保存文件大小;

1.宽高压缩

Android中图片压缩分析(下)

改变图片宽高,减小内存占用

在 Android 中图片重采样提供了两种方法:

  • 邻近采样(宽高压缩?sampleSize个像素取一个)
  • 双线性采样(Matrix*Matrix个像素取权重生成一个新的);
  • 除了 Android 中这两种常用的重采样方法之外,还有另外比较常见的两种:双立方/双三次采样(Bicubic Resampling) 和 Lanczos Resampling。除此之外,还有一些其他个人或机构发明的算法 Hermite Resampling,Bell Resampling,Mitchell Resampling。
设置inSampleSize
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resourceId, options);
int sampleSize = getSampleSize(options, targetWidth, targetHeight);

options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
bitmap = BitmapFactory.decodeResource(getResources(), resourceId, options);
setImageBitmap(bitmap);

/**
 * 获取缩放比例
 */
private int getSampleSize(BitmapFactory.Options options, int targetWidth, int targetHeight){
    int inSampleSize;
    float ssWidth = (float)options.outWidth / targetWidth;
    float ssHeight = (float)options.outHeight/targetHeight;
    inSampleSize = (int) (ssWidth > ssHeight ? ssWidth : ssHeight);
    return inSampleSize;
}

2.质量压缩

Android中图片压缩分析(上)

改变图片色彩深度等因素,宽高不变
不能减小内存占用,可减小保存文件大小

  • compress,将位图的压缩版本写入指定的输出流。如果返回true,则可以通过将相应的输入流传递给BitmapFactory.decodeStream()来重建位图。
  • 它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,不会减少图片的像素。
    经过它压缩的图片文件大小会变小,但是解码成bitmap后占得内存是不变的。
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//quality 为0~100,0表示最小体积,100表示最高质量,对应体积也是最大
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
//toByteArray().length()
Bitmap newBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(outputStream.toByteArray()), null, null);

二、计算Bitmap占用内存大小

Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?
android 图片占用内存大小及加载解析
Android中Bitmap内存优化

size = 宽 * 缩放 * 高 * 缩放 * 色彩格式

  • 图片宽高: 像素个数
  • 色彩格式:ARGB_8888(4byte)
  • 缩放:inTargetDensity(屏幕dpi) / densityDpi(不同dpi文件夹)

不同资源区别

  • drawable文件夹下
    缩放:inTargetDensity(屏幕dpi) / densityDpi(不同dpi文件夹)
  • 网络资源、本地资源
    加载到不同手机上,占用内存大小相同;
    除非专门设置:BitmapFactory.Options的inDensity或是inTargetDensity参数来调整图片的缩放比

结论

  • 在对手机进行屏幕适时,可以只切一套图适配所有的手机。
    • 如果只切一套小图,那在高屏幕密度手机上,会对图片进行放大,这样图片占用的内存往往比切相应图片放在高密度文件夹下,占用的内存还要大。
    • 如果只切一套大图,在小屏幕密度手机上,会缩小显示,按道理是行得通的。
      但如果图片缩放比较狠,可能导致图片出现抖动或是毛边。
    • 所以最好切出不同比例的图片放在不同幕度的文件夹下,对于性能要求不大的图片,可以只切一套大图;
  • 一张图片占用内存=图片长 * 图片宽 * (手机屏幕密度/资源图片文件密度)^2 * 每一象素占用字节数
  • 对于网络或是assets/手机本地图片
    • 在不同屏幕密度的手机上加载出来,占用内存是一样的。
    • 如果想通过设置Options里的inDensity或是inTargetDensity参数来调整图片的缩放比,必须两个参数均设置才能起作用,只设置一个,不会起作用。
  • drawable和mipmap文件夹存放图片的区别
    • 首先图片放在drawable-xhdpi和mipmap-xhdpi下,两者占用的内存是一样的,
    • 把图片放到mipmaps可以提高系统渲染图片的速度,提高图片质量,减少GPU压力。其他并没有什么区别。
    • 但Google建议只把启动图标放到mipmap文件夹里

扩展:drawable和mipmap文件夹区别

mipmap 是纹理映射技术。android 中的 mipmap 技术主要为了应对图片大小缩放的处理,在android 中我们提供一个 bitmap 图片,由于应用的需要(比如缩放动画),可能对这个 bitmap 进行各种比例的缩小,为了提高缩小的速度和图片的质量,android 通过 mipmap 技术提前对按缩小层级生成图片预先存储在内存中,这样就提高了图片渲染的速度和质量。

  • api 中通过 Bitmap 的 setHasMipMap (boolean hasMipMap) 方法可以让系统渲染器尝试开启 Bitmap 的 mipmap 技术。但是这个方法只能建议系统开启这个功能,至于是否正真开启,还是由系统决定。
  • res 目录下面 mipmap 和 drawable 的区别也就是上面这个设置是否开启的区别。mipmap 目录下的图片默认 setHasMipMap 为 true,drawable 默认 setHasMipMap 为 false。
  • google 建议只把 app 的启动图标放在 mipmap 目录中,其他图片资源仍然放在 drawable 下面。

三、超大图片局部加载

Android 高清加载巨图方案 拒绝压缩图片

    # BitmapRegionDecoder、Rect
    
    InputStream inputStream = getAssets().open("tangyan.jpg");
    //获得图片的宽、高
    BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
    tmpOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(inputStream, null, tmpOptions);
    int width = tmpOptions.outWidth;
    int height = tmpOptions.outHeight;

    //设置显示图片的中心区域
    BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
    mImageView.setImageBitmap(bitmap);

四、多图片加载,三级缓存

LRUCache

Android缓存机制-LRU cache原理与用法
Android面试收集录 LruCache原理解析
Android DiskLruCache完全解析,硬盘缓存的最佳方案
Android照片墙应用实现,再多的图片也不怕崩溃

Least Recently Used,“最近最少使用”
核心思想是当缓存达到上限时,会先淘汰最近最少使用的缓存
LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。

LRUCache使用
<!--LRUCache使用-->
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
	// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
	// LruCache通过构造函数传入缓存值,以KB为单位。
	int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
	// 使用最大可用内存值的1/8作为缓存的大小。
	int cacheSize = maxMemory / 8;
	mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
		@Override
		protected int sizeOf(String key, Bitmap bitmap) {
			// 重写此方法来衡量每张图片的大小,默认返回图片数量。
			return bitmap.getByteCount() / 1024;
		}
	};
}
 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
	if (getBitmapFromMemCache(key) == null) {
		mMemoryCache.put(key, bitmap);
	}
}
 
public Bitmap getBitmapFromMemCache(String key) {
	return mMemoryCache.get(key);
}
LRUCache原理
  • LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。
  • put()方法时,在集合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。
  • get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头

image

五、三级缓存

Android中图片的三级缓存

  • 内存,优先加载
  • 本地,次优先加载
  • 网络,最后加载
# AsyncTask、LRUCache

取:
先取内存 -> 再取本地 -> 最后网络下载(下载完以后存)

存:
每次存入:内存、本地

六、Bitmap优化

内存

size = 宽 * 缩放 * 高 * 缩放 * 色彩格式

  • 大小:采样压缩,或减小图片尺寸
  • 质量:使用低色彩的解析模式,如RGB565(2字节)
  • 缩放:合理放入资源文件,减少缩放比例
  • 复用:API方法,复用Bitmap,需宽高一直
  • 缓存:LRUCache(不能优化Bitmap内存占用,但可以节省流量、加快加载速度...)