Hiten's Blog.

Glide4.8源码拆解(四)Bitmap解析之下采样浅析

字数统计: 3.8k阅读时长: 17 min
2019/01/13 Share

前言

Glide归根结底是一个图片加载框架,它一定会涉及到BitmapFactory相关API把Bitmap读取到内存;可能大家已经很熟悉如何高效的加载Bitmap(比如使用inSample等),这一章还是要看一看Glide是如何玩转的;

本文主要分析这两个类:

  • DownsampleStrategy
  • Downsampler

从”Glide会对原图进行放大”案例开始

假设我们有一张宽高100x200的网络图片,需要加载到300x300像素的ImageView上(scaleType属性为CenterCrop),用Glide加载,不做任何处理会得到多大的Bitmap?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Glide.with(MainActivity.this).load(URL).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}

@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (resource instanceof BitmapDrawable){
Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
Log.d("onResourceReady",bitmap.toString());
}
return false;
}
}).into(vh.imageView);

不出意外会得到300*300的Bitmap,Bitmap比例显然被放大了;

我怀疑是进行了Transformation操作,所以我们要禁用Transformation;

假设我们对RequestOptions加上noTransformation的属性,比如这个样子:

1
2
RequestOptions requestOptions = RequestOptions.noTransformation();
Glide.with(MainActivity.this).load(URL).apply(requestOptions).listener(xxx).into(vh.imageView);

不出意外会得到300*600的Bitmap,Bitmap比例依然在被放大,甚至更大了;

如果我们不想让Bitmap被放大,可以用加载原图尺寸的方式,比如设置override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)或者用SimpleTarget这样的Target,但是加载原图终究不是解决方案,为什么?内存的原因,大部分情况下我们还是希望Glide来对图片进行缩小,加载原图的操作等于是直接把APP往OOM送近了一步;

Bitmap被放大不是Glide发明的,是Android官方带的头,比如同一张图片,放在res中不同drawable文件夹下,得到的尺寸不一样;既然如此,Bitmap被放大肯定是有积极意义的,也难怪Glide很少提及这事;

但是,总会有强迫症患者不想Bitmap被放大,Glide肯定也想到了这一点,具体怎么限制Bitmap不会放大,答案的使用RequestOptions.downsample()方法;

RequestOptions.downsample()方法接受参数类型为:DownsampleStrategy,那么我们从DownsampleStrategy开始分析;

DownsampleStrategy

DownsampleStrategy顾名思义就是下采样策略,下采样是图像进行缩小的一种方式;

DownsampleStrategy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class DownsampleStrategy {
//获取缩放比例
public abstract float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
int requestedHeight);

//获取SampleSize策略
public abstract SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
int requestedWidth, int requestedHeight);
}

public enum SampleSizeRounding {
//内存优先
MEMORY,
//图片质量优先
QUALITY,
}

DownsampleStrategy是抽象类,提供两个抽象方法,getScaleFactor()顾名思义是获取缩放比例的,getSampleSizeRounding()可能是获取SampleSize的,DownsampleStrategy真是的实现类在其内部,几个嵌套内部类FitCenter,CenterOutside,AtLeast,AtMost,None,CenterInside,除此之外,DownsampleStrategy还定义对应类的静态变量;

DownsampleStrategy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public static final DownsampleStrategy FIT_CENTER = new FitCenter();

public static final DownsampleStrategy CENTER_OUTSIDE = new CenterOutside();

public static final DownsampleStrategy AT_LEAST = new AtLeast();

public static final DownsampleStrategy AT_MOST = new AtMost();

public static final DownsampleStrategy CENTER_INSIDE = new CenterInside();

public static final DownsampleStrategy NONE = new None();

public static final DownsampleStrategy DEFAULT = CENTER_OUTSIDE;

其中DEFAULT应该是Glide默认的策略,它指向的是CENTER_OUTSIDE,我们简单看一下CenterOutside的逻辑;

CenterOutside.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static class CenterOutside extends DownsampleStrategy {

@Synthetic
CenterOutside() { }

@Override
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
int requestedHeight) {
//宽度比例(控件/图片)
float widthPercentage = requestedWidth / (float) sourceWidth;
//高度比例(控件/图片)
float heightPercentage = requestedHeight / (float) sourceHeight;
//谁大取谁
return Math.max(widthPercentage, heightPercentage);
}

@Override
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
int requestedWidth, int requestedHeight) {
return SampleSizeRounding.QUALITY;
}
}

主要关注是CenterOutsidegetScaleFactor()逻辑,该逻辑非常简单,主要是拿控件宽高/图片宽高,得到尺寸取最大值,我们回到文章开头的那个例子,假设此时控件宽高是300x300,图片宽高100x200:

widthPercentage = 300/100 = 3.0f;

heightPercentage = 300/200 = 1.5f;

Math.max(widthPercentage, heightPercentage) = 3.0f;

从这个推理来看,文章开头那个图片确实会被放大3倍;

文章开头试图找控制缩放比例不超过1的,就是只缩小不放大的,有没有这样的策略,其实是有的,比如AtLeast,AtMost

AtLeast.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static class AtLeast extends DownsampleStrategy {

@Synthetic
AtLeast() { }

@Override
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
int requestedHeight) {
int minIntegerFactor = Math.min(sourceHeight / requestedHeight, sourceWidth / requestedWidth);
return minIntegerFactor == 0 ? 1f : 1f / Integer.highestOneBit(minIntegerFactor);
}

@Override
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
int requestedWidth, int requestedHeight) {
return SampleSizeRounding.QUALITY;
}
}

AtLeastgetScaleFactor的逻辑简要分析:

获取图片宽高/布局宽高的比例的最小值,转成int值;

如果这个值等于0,直接返回1,等于0其实就意味着图片宽高/布局宽高至少有一个是小于1的,也就屏蔽了需要放大的情况;

如果minIntegerFactor不等一0,肯定是需要缩小的,最后返回1f / Integer.highestOneBit(minIntegerFactor),其中Integer.highestOneBit(minIntegerFactor)是取minIntegerFactor的二进制形式最左边的最高一位且高位后面全部补零,最后返回int型的结果;

其余的DownsampleStrategy实现类就不讲解了,DownsampleStrategy是负责计算缩放比例和SampleSize策略,那么真正去进行Bitmap计算的是Downsampler;

重头戏Downsampler

Downsampler.java

Downsampler这个类在我们之前分析Decode流程时已经遇到过它,该类的主要职责是从输入流中解析出Bitmap,核心功能的入口方法在decode();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
+ " mark()");

byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
//获取默认的BitmapFactory
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;
//decodeFormat主要是RGB_8888||RGB_565
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
//获取DownsampleStrategy
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
//是否fixedBitmapSize
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
//是否支持硬件位图
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

try {
//解析出Bitmap
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
//BitmapResource包装Bitmap
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}

首先调用getDefaultOptions()获取默认的BitmapFactory;

默认的BitmapFactory

默认的BitmapFactory使用一个队列缓存,初始化设置在resetOptions()方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
decodeBitmapOptions.inTempStorage = null;
decodeBitmapOptions.inDither = false;
decodeBitmapOptions.inScaled = false;
decodeBitmapOptions.inSampleSize = 1;
decodeBitmapOptions.inPreferredConfig = null;
decodeBitmapOptions.inJustDecodeBounds = false;
decodeBitmapOptions.inDensity = 0;
decodeBitmapOptions.inTargetDensity = 0;
decodeBitmapOptions.outWidth = 0;
decodeBitmapOptions.outHeight = 0;
decodeBitmapOptions.outMimeType = null;
decodeBitmapOptions.inBitmap = null;
decodeBitmapOptions.inMutable = true;
}

继续decode()方法分析,首先从Option中获取decodeFormatfixBitmapToRequestedDimensionsisHardwareConfigAllowed等,这个Option是从RequestOptions传递过来,代表用户的配置;

  • decodeFormat表示解析格式,RGB_565或ARGB_8888;
  • fixBitmapToRequestedDimensions表示是否填充尺寸,不进行缩放
  • isHardwareConfigAllowed是否允许硬件位图,详细了解可以看Glide官方文档:https://muyangmin.github.io/glide-docs-cn/doc/hardwarebitmaps.html;
  • BitmapResource包装Bitmap返回;
    真正的加载Bitmap逻辑在decodeFromWrappedStreams()方法:

decodeFromWrappedStreams

decodeFromWrappedStreams()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
private Bitmap decodeFromWrappedStreams(InputStream is,
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
int requestedHeight, boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks) throws IOException {
long startTime = LogTime.getLogTime();
//解析出输入流图片尺寸
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
//得到mimeType
String sourceMimeType = options.outMimeType;
if (sourceWidth == -1 || sourceHeight == -1) {
isHardwareConfigAllowed = false;
}
//获取图片旋转方向
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;
//获取image类型
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
//计算缩放
calculateScaling();
//计算其他config
calculateConfig();

boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
//处理inBitmap
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
int expectedWidth;
int expectedHeight;
if (sourceWidth >= 0 && sourceHeight >= 0
&& fixBitmapToRequestedDimensions && isKitKatOrGreater) {
expectedWidth = targetWidth;
expectedHeight = targetHeight;
} else {
float densityMultiplier = isScaling(options)
? (float) options.inTargetDensity / options.inDensity : 1f;
int sampleSize = options.inSampleSize;
int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
expectedWidth = Math.round(downsampledWidth * densityMultiplier);
expectedHeight = Math.round(downsampledHeight * densityMultiplier);
}
//设置inBitmap
if (expectedWidth > 0 && expectedHeight > 0) {
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
//BitmapFactory.Option配置完毕,调用decodeStream解析
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
callbacks.onDecodeComplete(bitmapPool, downsampled);

Bitmap rotated = null;
if (downsampled != null) {
//重新设置density
downsampled.setDensity(displayMetrics.densityDpi);
//处理旋转信息
rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
if (!downsampled.equals(rotated)) {
//添加进BitmapPool
bitmapPool.put(downsampled);
}
}

return rotated;
}
  • decodeFromWrappedStreams()方法首先调用getDimensions()获取输入流图片尺寸和mimeType
  • 获取图片的方向和旋转角度;
  • 确认目标控件的宽高,获取图片的ImageType;
  • 调用calculateScaling()计算缩放信息;
  • 调用calculateConfig()计算其他配置信息;
  • 根据配置,调用setInBitmap()设置inBitmap;
  • 上诉流程完成BitmapFactory.Option配置,调用decodeStream()解析输入流;
  • 得到Bitmap,对Bitmap做最后的操作;

getDimensions()

1
2
3
4
5
6
7
8
9
private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
//只解析Bounds
options.inJustDecodeBounds = true;
decodeStream(is, options, decodeCallbacks, bitmapPool);
//重置为false;
options.inJustDecodeBounds = false;
return new int[] { options.outWidth, options.outHeight };
}

getDimensions()使用options.inJustDecodeBounds = true读取输入流图片信息;

计算缩放

calculateScaling()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
private static void calculateScaling(
ImageType imageType,
InputStream is,
DecodeCallbacks decodeCallbacks,
BitmapPool bitmapPool,
DownsampleStrategy downsampleStrategy,
int degreesToRotate,
int sourceWidth,
int sourceHeight,
int targetWidth,
int targetHeight,
BitmapFactory.Options options) throws IOException {
//尺寸不能为0
if (sourceWidth <= 0 || sourceHeight <= 0) {
return;
}

final float exactScaleFactor;
//根据方向和角度获取exactScaleFactor
if (degreesToRotate == 90 || degreesToRotate == 270) {
exactScaleFactor = downsampleStrategy.getScaleFactor(sourceHeight, sourceWidth,
targetWidth, targetHeight);
} else {
exactScaleFactor =
downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight);
}
//exactScaleFactor不能小于等于0
if (exactScaleFactor <= 0f) {
throw new IllegalArgumentException("");
}
//获取SampleSizeRounding
SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
sourceHeight, targetWidth, targetHeight);
if (rounding == null) {
throw new IllegalArgumentException("Cannot round with null rounding");
}
//获取Bitmap输出宽高
int outWidth = round(exactScaleFactor * sourceWidth);
int outHeight = round(exactScaleFactor * sourceHeight);
//转成int类型的factor
int widthScaleFactor = sourceWidth / outWidth;
int heightScaleFactor = sourceHeight / outHeight;
//根据SampleSizeRounding得到scaleFactor
int scaleFactor = rounding == SampleSizeRounding.MEMORY
? Math.max(widthScaleFactor, heightScaleFactor)
: Math.min(widthScaleFactor, heightScaleFactor);

int powerOfTwoSampleSize;
//不支持下采样
if (Build.VERSION.SDK_INT <= 23
&& NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
powerOfTwoSampleSize = 1;
} else {
//在scaleFactor再进行处理,保证是2的指数幂
powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
if (rounding == SampleSizeRounding.MEMORY
&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
}
}
//设置到inSampleSize
options.inSampleSize = powerOfTwoSampleSize;
int powerOfTwoWidth;
int powerOfTwoHeight;
//针对不同图片格式,重新计算采样后的宽高
if (imageType == ImageType.JPEG) {
//libjpeg引擎最大采样size=8,skia会进行二次采样
int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);
powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);
//如大大于8,skia会对剩下的进行二次采样计算逻辑
int secondaryScaling = powerOfTwoSampleSize / 8;
if (secondaryScaling > 0) {
powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
}
} else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {
powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
} else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
//不同版本采样不同的计算方式round或者floor
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
powerOfTwoWidth = Math.round(sourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = Math.round(sourceHeight / (float) powerOfTwoSampleSize);
} else {
powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
}
} else if (
sourceWidth % powerOfTwoSampleSize != 0 || sourceHeight % powerOfTwoSampleSize != 0) {
int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
powerOfTwoWidth = dimensions[0];
powerOfTwoHeight = dimensions[1];
} else {
powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
}
//计算采样后进行缩放的比例
double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);
//计算inTargetDensity和inDensity进行缩放
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//这一块计算方法没有弄明白
options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
options.inDensity = getDensityMultiplier(adjustedScaleFactor);
}
//设置inScaled
if (isScaling(options)){
options.inScaled = true;
} else {
options.inDensity = options.inTargetDensity = 0;
}
}

calculateScaling代码看似复杂,但是仔细分析流程还是很清晰的,该方法主要计算BitmapFactory.FactoryinSampleSizeinTargetDensityinDensityinSampleSize主要是进行向下采样,采样后自然会对图片进行缩小,但是可能不满足目标缩放比例,所以再配合inTargetDensityinDensity进行二次缩放;主要针对两个缩放变量powerOfTwoSampleSizeadjustedScaleFactor;具体流程分析:

流程一

  1. 计算powerOfTwoSampleSize过程:powerOfTwoSampleSize是最终赋值给options.inSampleSize的;
  • 根据图片角度不同,分别调用downSampleStategy.getScaleFactor()方法,得到exactScaleFactor;
  • 通过exactScaleFactor计算出outWidthoutHeight,再重新计算widthScaleFactorheightScaleFactor(ps:widthScaleFactor和heightScaleFactor是int类型,而exactScaleFactor是浮点型,结果是不一样的);
  • 通过策略得到int类型的比例scaleFactor
  • 判断是否支持下采样,计算出最终的缩放比例powerOfTwoSampleSize
  • 赋值给options.inSampleSize

流程二

  1. 计算adjustedScaleFactor过程:adjustedScaleFactor是最终计算options.inTargetDensityoptions.inDensity的;
  • 不同格式和版本的下采样逻辑不同,Glide分别实现采样逻辑的计算,然后生成采样后的宽高尺寸powerOfTwoWidthpowerOfTwoHeight;
  • 再此调用downsampleStrategy.getScaleFactor()得到缩放比adjustedScaleFactor;
  • 调用adjustTargetDensityForError()getDensityMultiplier()得到inTargetDensityinDensity并赋值;
  • 上面inDensity依赖options.inScaled = true,最终还得判断是否能够进行scale;

计算硬件位图/Bitmap.Config

calculateConfig()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private void calculateConfig(
InputStream is,
DecodeFormat format,
boolean isHardwareConfigAllowed,
boolean isExifOrientationRequired,
BitmapFactory.Options optionsWithScaling,
int targetWidth,
int targetHeight) {
//如果支持硬件位图
if (hardwareConfigState.setHardwareConfigIfAllowed(
targetWidth,
targetHeight,
optionsWithScaling,
format,
isHardwareConfigAllowed,
isExifOrientationRequired)) {
return;
}
//这种情况,直接配置ARGB_8888
if (format == DecodeFormat.PREFER_ARGB_8888
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888;
return;
}
//是否有透明的通道
boolean hasAlpha = false;
try {
hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha();
} catch (IOException e) {

}
//有透明的通道,需要配置成ARGB_8888
optionsWithScaling.inPreferredConfig =
hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
optionsWithScaling.inDither = true;//565支持抖动界面
}
}

calculateConfig()主要是对BitmapFactory.Option的硬件位图和inPreferredConfig进行计算;如果用户配置8888直接配置,如果用户配置565,还需要判断是否有透明度通道,如果有透明度通道,依然采用8888解码器;

硬件位图

HardwareConfigState.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
boolean setHardwareConfigIfAllowed(
int targetWidth,
int targetHeight,
BitmapFactory.Options optionsWithScaling,
DecodeFormat decodeFormat,
boolean isHardwareConfigAllowed,
boolean isExifOrientationRequired) {
if (!isHardwareConfigAllowed
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|| isExifOrientationRequired) {
Android O以上支持
return false;
}
//对尺寸有要求;
boolean result =
targetWidth >= MIN_HARDWARE_DIMENSION
&& targetHeight >= MIN_HARDWARE_DIMENSION
&& isFdSizeBelowHardwareLimit();

if (result) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
optionsWithScaling.inMutable = false;
}
return result;
}

硬件位图Bitmap.Config.HARDWARE 是一种 Android O 添加的新的位图格式。硬件位图仅在显存 (graphic memory) 里存储像素数据,并对图片仅在屏幕上绘制的场景做了优化。

###设置InBitmap

setInBitmap()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
// Avoid short circuiting, it appears to break on some devices.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//硬件位图不设置
if (options.inPreferredConfig == Config.HARDWARE) {
return;
}
//inJustDecodeBoudes时候有可能可以获取到
expectedConfig = options.outConfig;
}

if (expectedConfig == null) {
expectedConfig = options.inPreferredConfig;
}

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

设置成硬件位图的不能进行inBitmap操作,最终会设置options.inBitmap,其中缓存的位图从bitmapPool中获取;

解析输出流

decodeStream()

万事具体只差东风,设置完BitmapFactory.Options之后,正在解析输入流的代码就在decodeStream():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
if (options.inJustDecodeBounds) {
is.mark(MARK_POSITION);
} else {
callbacks.onObtainBounds();
}
int sourceWidth = options.outWidth;
int sourceHeight = options.outHeight;
String outMimeType = options.outMimeType;
final Bitmap result;
TransformationUtils.getBitmapDrawableLock().lock();
try {
//真正的解析调用
result = BitmapFactory.decodeStream(is, null, options);
} catch (IllegalArgumentException e) {
if (options.inBitmap != null) {
try {
//发生异常要做inBitmap回收
is.reset();
bitmapPool.put(options.inBitmap);
options.inBitmap = null;//不适用inBitmap
return decodeStream(is, options, callbacks, bitmapPool);
} catch (IOException resetException) {
throw bitmapAssertionException;
}
}
throw bitmapAssertionException;
} finally {
TransformationUtils.getBitmapDrawableLock().unlock();
}
if (options.inJustDecodeBounds) {
is.reset();
}
return result;
}

decodeStream()核心的方法是调用BitmapFactory.decodeStream(is, null, options)做解析工作,剩下的代码都是对异常解析,如果发现异常时inBitmap不为空,设置inBitmapnull并重试一次;

总结

本文主要是对Glide下采样做了简单介绍,从代码流程上分析,可以分为对BitmapFactory.Options的配置、调用BitmapFactory解析输入流以及对Bitmap最后的处理三个步骤,其中Options配置阶段在计算缩放上最为重要,其中在缩放因子计算阶段,涉及native以及libjpeg底层的逻辑尤为重要,值得学习;

CATALOG
  1. 1. 前言
  2. 2. 从”Glide会对原图进行放大”案例开始
  3. 3. DownsampleStrategy
  4. 4. 重头戏Downsampler
    1. 4.1. 默认的BitmapFactory
    2. 4.2. decodeFromWrappedStreams
    3. 4.3. 计算缩放
      1. 4.3.1. 流程一
      2. 4.3.2. 流程二
    4. 4.4. 计算硬件位图/Bitmap.Config
    5. 4.5. 硬件位图
    6. 4.6. 解析输出流
  5. 5. 总结