BitmapPool 了解吗?Glide 是如何实现 Bitmap 复用的?

}

BitmapPool 通过定义一个 Pool 来让用户复用 Bitmap 对象。在 Glide 中,BitmapPool 有一个默认的实现 LruBitmapPool. 顾名思义,也是基于 LRU 的理念设计的。

前面我们提到过 inBitmap 以 Android 4.4 为分水岭,之前和之后的版本在使用上存在版本差异,那么 BitmapPool 是如何处理这个差异的呢?答案是策略模式。Glide 定义了 LruPoolStrategy 接口,该接口内部定义了增删相关操作。真实的 Bitmap 数据根据尺寸和颜色等映射关系存储到 LruPoolStrategy 中。BitmapPool 的 get 和 put 也是通过 LruPoolStrategy 的 get 和 put 完成的。

interface LruPoolStrategy {
void put(Bitmap bitmap);
@Nullable Bitmap get(int width, int height, Bitmap.Config config);
@Nullable Bitmap removeLast();
String logBitmap(Bitmap bitmap);
String logBitmap(int width, int height, Bitmap.Config config);
int getSize(Bitmap bitmap);
}

LruPoolStrategy 默认提供了三个实现,分别是 AttributeStrategySizeConfigStrategySizeStrategy. 其中,AttributeStrategy 适用于 Android 4.4 以下的版本,SizeConfigStrategy 和 SizeStrategy 适用于 Android 4.4 及以上的版本。

AttributeStrategy 通过 Bitmap 的 width(图片宽度)、height(图片高度) 和 config(图片颜色空间,比如 ARGB_8888 等) 三个参数作为 Bitmap 的唯一标识。当获取 Bitmap 的时候只有这三个条件完全匹配才行。而 SizeConfigStrategy 使用 size(图片的像素总数) 和 config 作为唯一标识。当获取的时候会先找出 cofig 匹配的 Bitmap(一般就是 config 相同),然后保证该 Bitmap 的 size 大于我们期望的 size 并且小于期望 size 的 8 倍即可复用(可能是为了节省内存空间)。

所谓的 LRU 就是 BitmapPool 通过 LruPoolStrategy 实现的,具体操作是,在往 BitmapPool 中 put 数据之后会执行下面的操作调整空间大小:

private synchronized void trimToSize(long size) {
while (currentSize > size) {
// 移除尾部的
final Bitmap removed = strategy.removeLast();
if (removed == null) {
currentSize = 0;
return;
}
currentSize -= strategy.getSize(removed);
// …
// 回收
removed.recycle();
}
}

4、Bitmap 加载和复用

下面我们来复习下一般的 Bitmap 加载的步骤。常规的图片加载过程如下,

// 设置 inJustDecodeBounds 为 true 来获取图片尺寸
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);

// 设置 inJustDecodeBounds 为 false 来真正加载
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
options.inJustDecodeBounds = false;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);

也就是说,首先通过设置 options.inJustDecodeBounds 为 true 来获取图片真实的尺寸,以便设置采样率。因为我们一般不会直接加载图片的所有的像素,而是采样之后再按需加载,以减少图片的内存占用。当真正需要加载的时候,设置 options.inJustDecodeBounds 为 false,再调用 decode 相关的方法即可。

那么 Bitmap 复用是如何使用的呢?很简单,只需要在加载的时候通过 options 的 inBitmap 参数指定一个 Bitmap 对象再 decode 即可:

options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);

5、Glide 是如何加载 Bitmap 的

之前分析 Glide 的源码的时候,注重的是整个流程,对于很多细节没用照顾到,这里我简化下逻辑。首先,Glide 的 Bitmap 加载流程位于 Downsampler 类中。当从其他渠道,比如网络或者磁盘中获取到一个输入流 InputStream 之后就可以进行图片加载了。下面是 Downsampler 的 decodeFromWrappedStreams 方法,这里是执行图片加载的流程,主要代码的逻辑和功能已经备注到了注释上面:

private Bitmap decodeFromWrappedStreams(InputStream is,
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat, …) throws IOException {
long startTime = LogTime.getLogTime();
// 通过设置 inJustDecodeBounds 读取图片的原始尺寸信息
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
String sourceMimeType = options.outMimeType;

// …

// 读取图片的 exif 信息,如果需要的话,先对图片进行旋转
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;

ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

// 根据要求计算需要记载的图片大小和 config,计算结果直接设置给 options 即可
calculateScaling(imageType, is, …, options);
calculateConfig(is, …, options, targetWidth, targetHeight);

boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
// …
// 根据图片的期望尺寸到 BitmapPool 中获取一个 Bitmap 以复用
if (expectedWidth > 0 && expectedHeight > 0) {
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
// 开始执行 decode 逻辑
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
callbacks.onDecodeComplete(bitmapPool, downsampled);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的点击这里>Android IOC架构设计免费获取。
群内还有免费的高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

pics/618156601)免费获取。
群内还有免费的高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。**

image

  • 29
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值