Bitmap

Bitmap 用来描述一张图片的长、宽、颜色等信息。通常情况下,我们可以使用 BitmapFactory 来将某一路径下的图片解析为 Bitmap 对象。


1
2
Bitmap bitmap = BitmapFactory.decodeResource(getResource(),R.drawable.xxx);
img.setBitmap(bitmap);

xxx 是保存在 res/drawable-xhdpi 目录下的一张 600*600,大小为 65Kb 的图片。

默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节。

通过 Bitmap.getAllocationByteCount() 方法获取 Bitmap 占用的字节大小:
bitmap size is 6006004 = 1440000

BitmapFactory 在解析图片的过程中,会根据当前设备屏幕密度和图片所在的 drawable 目录来做一个对比,根据这个对比值进行缩放操作。具体公式为如下所示:

缩放比例 scale = 当前设备屏幕密度 / 图片所在 drawable 目录对应屏幕密度
Bitmap 实际大小 = 宽 * scale * 高 * scale * Config 对应存储像素数

  • drawable-mdpi:densityDpi = 160(默认值)
  • drawable-hdpi:densityDpi = 240
  • drawable-xhdpi:densityDpi = 320
  • drawable-xxhdpi:densityDpi = 480
  • drawable-xxxhdpi:densityDpi = 640

补充:dp = px / (dpi/160) 其中dpi是设备屏幕密度

assets 中的图片大小

xxx 是保存在 assets 目录下的一张 600*600,大小为 65Kb 的图片。

1
2
InputStream in = getAssets().open(“xxx.png”);
Bitmap bitmap = BitmapFactory.decodeStream(in);

通过 Bitmap.getAllocationByteCount() 方法获取 Bitmap 占用的字节大小:
bitmap size is 6006004 = 1440000

可以看出,加载 assets 目录中的图片,系统并不会对其进行缩放操作。

Bitmap 加载优化

修改图片加载的 Config
1
2
BitmapFactory.Options op = new BitmapFactory.Options();
op.inPreferredConfig = Bitmap.Config.RGB_565;

这种存储方式一个像素占用 2 个字节,所以最终占用内存直接减半
bitmap size is 6006002 = 720000

inSampleSize 参数

可以实现 Bitmap 采样压缩,这个参数的含义是宽高维度上每隔 inSampleSize 个像素进行一次采集。

1
op.inSampleSize = 2

因为宽高都会进行采样,所以最终图片会被缩略 4 倍
bitmap size is 720000 / 4 = 180000 = 170KB

Bitmap 复用

场景:bitmap切换显示,导致频繁创建+GC回收,造成内存抖动。

使用 Options.inBitmap 优化
第一次显示之后,内存中已经存在了一个 Bitmap 对象。每次切换图片只是显示的内容不一样,我们可以重复利用已经占用内存的 Bitmap 空间

1
2
3
4
5
6
7
8
9
Bitmap reuseBitmap;
……

op.inJustDecodeBounds = true
if(canUseForInBitmap(reuseBitmap,op)){
op.inMutable = true
op.inBitmap = reuseBitmap;
}
op.inJustDecodeBounds = false

//创建一个可以用来复用的 Bitmap 对象。

//将 options.inBitmap 赋值为之前创建的 reuseBitmap 对象,从而避免重新分配内存。

复用 inBitmap 之前,需要调用 canUseForInBitmap 方法来判断 reuseBitmap 是否可以被复用。这是因为 Bitmap 的复用有一定的限制:

在 Android 4.4 版本之前,只能重用相同大小的 Bitmap 内存区域;

4.4 之后你可以重用任何 Bitmap 的内存区域,只要这块内存比将要分配内存的 bitmap 大就可以。

BitmapRegionDecoder 图片分片显示

图片可以以绝对路径、文件描述符、输入流的方式传递给 BitmapRegionDecoder

BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(in,false);
Bitmap bitmap = decoder.decodeRegion(new Rect(0,0,200,200),op);

只显示部分区域,可以通过touch实现滑动展示其余部分,具体可以参考开源库:
https://github.com/LuckyJayce/LargeImage

BitmapRegionDecoder 容易造成内存抖动,卡顿,写法繁琐,不推荐

推荐:Glide+SubsamplingScaleImageView
https://github.com/davemorrissey/subsampling-scale-image-view
https://juejin.cn/post/6955427322291814431
https://github.com/LuckyJayce/LargeImage

Bitmap 缓存

1
2
3
4
5
6
7
8
9
10
11
12
//内存缓存
private LruCache<String, Bitmap> mLruCache;
//Lru就是利用LinkedHashMap的顺序访问
new LinkedHashMap<K, V>(0, 0.75f, true)

---------------------------------------------

//磁盘缓存
//implementation 'com.jakewharton:disklrucache:2.0.2'
//内部也用到了LinkedHashMap,还有文件的读写
private DiskLruCache mDiskLruCache;

都是基于LinkedHashMap(有序)

LinkedHashMap继承于HashMap,底层基于HashMap和双向链表来实现的。

”三级缓存“的逻辑:
  • 加载时 先从内存缓存获取,有就返回bitmap绘制图片到view,若没有就从磁盘缓存获取;
  • 磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;
  • 网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。
图片储存位置
  • 3.0 - 7.0 存在java堆
  • 3.0之前,8.0及之后,存native堆