在介绍源码调用流程这篇文章时,对图片处理这块没有详细讲。只是说明了Glide将网络请求回来的数据解码成了相应的Resource。下面我们跟进源码看下解码的详细实现。
解码是发生在DecodeJob#decodeFromRetrievedData方法中。
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
接下来到了decodeFromData。
private Resource decodeFromData(DataFetcher> fetcher, Data data,
DataSource dataSource) throws GlideException {
try {
if (data == null) {
return null;
}
long startTime = LogTime.getLogTime();
Resource result = decodeFromFetcher(data, dataSource);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded result " + result, startTime);
}
return result;
} finally {
fetcher.cleanup();
}
}
decodeFromData里面又调用了decodeFromFetcher。
private Resource decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());
return runLoadPath(data, dataSource, path);
}
decodeFromFetcher方法里面,获取到了加载的路径。然后调用了runLoadPath
private Resource runLoadPath(Data data, DataSource dataSource,
LoadPath path) throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
DataRewinder rewinder = glideContext.getRegistry().getRewinder(data);
try {
// ResourceType in DecodeCallback below is required for compilation to work with gradle.
return path.load(
rewinder, options, width, height, new DecodeCallback(dataSource));
} finally {
rewinder.cleanup();
}
}
DataRewinder。接下来执行到LoadPath.load方法
public Resource load(DataRewinder rewinder, @NonNull Options options, int width,
int height, DecodePath.DecodeCallback decodeCallback) throws GlideException {
List throwables = Preconditions.checkNotNull(listPool.acquire());
try {
return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
} finally {
listPool.release(throwables);
}
}
上面方法的throwables是一个List集合,作为参数传到loadWithExceptionList中,用于存储解码的异常。下面看下LoadPath.loadWithExceptionList方法。
private Resource loadWithExceptionList(DataRewinder rewinder,
@NonNull Options options,
int width, int height, DecodePath.DecodeCallback decodeCallback,
List exceptions) throws GlideException {
Resource result = null;
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = decodePaths.size(); i < size; i++) {//遍历所有可能的DecodePath。有可能是
DecodePath path = decodePaths.get(i);
try {
result = path.decode(rewinder, width, height, options, decodeCallback);
} catch (GlideException e) {
exceptions.add(e);
}
if (result != null) {
break;
}
}
if (result == null) {
throw new GlideException(failureMessage, new ArrayList<>(exceptions));
}
return result;
}
接下来执行DecodePath.decode方法
public Resource decode(DataRewinder rewinder, int width, int height,
@NonNull Options options, DecodeCallback callback) throws GlideException {
Resource decoded = decodeResource(rewinder, width, height, options);//解码出原始资源
Resource transformed = callback.onResourceDecoded(decoded);//新建变换的资源
return transcoder.transcode(transformed, options);//根据设置,变换成目标资源
}
先看decodeResource方法。
@NonNull
private Resource decodeResource(DataRewinder rewinder, int width,
int height, @NonNull Options options) throws GlideException {
List exceptions = Preconditions.checkNotNull(listPool.acquire());//
try {
return decodeResourceWithList(rewinder, width, height, options, exceptions);
} finally {
listPool.release(exceptions);
}
}
直接看decodeResourceWithList方法
@NonNull
private Resource decodeResourceWithList(DataRewinder rewinder, int width,
int height, @NonNull Options options, List exceptions) throws GlideException {
Resource result = null;
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = decoders.size(); i < size; i++) {
ResourceDecoder decoder = decoders.get(i);//拿到资源解码器
try {
DataType data = rewinder.rewindAndGet();//获取原始数据类型
if (decoder.handles(data, options)) {//判断该解码器能否解码该数据类型的数据。
data = rewinder.rewindAndGet();//如果可以解码
result = decoder.decode(data, width, height, options);
}
// Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
// instead log and continue. See #2406 for an example.
} catch (IOException | RuntimeException | OutOfMemoryError e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Failed to decode data for " + decoder, e);
}
exceptions.add(e);
}
if (result != null) {
break;
}
}
if (result == null) {//如果为空,抛出异常,由上级捕获。然后遍历下一个DecodePath
throw new GlideException(failureMessage, new ArrayList<>(exceptions));
}
return result;
}
我的不是Gif图,对应的解码器为ByteBufferBitmapDecoder。我们来看下
@Override
public Resource decode(@NonNull ByteBuffer source, int width, int height,
@NonNull Options options)
throws IOException {
InputStream is = ByteBufferUtil.toStream(source);//将ByteBuffer转为InputStream
return downsampler.decode(is, width, height, options);
}
Downsampler这个类主要完成采样压缩、解码、旋转的功能。下面执行到了它的decode方法。
public Resource decode(InputStream is, int outWidth, int outHeight,
Options options) throws IOException {
return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
}
public Resource 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);//从byteArrayPool取出一个大小为65536数组,用于本次解码。byteArrayPool默认大小为4M。
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();//获取默认的decode bitmap参数。inSampleZize=1,表示1:1采样。
bitmapFactoryOptions.inTempStorage = bytesForOptions;//将缓存数组关联到解码Option配置里。
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);//默认解码格式为PREFER_ARGB_8888
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);//图片的缩放策略,fit_center、fit_xy等等。
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
// 查询是否支持Bitmap.Config#HARDWARE。硬件位图是Android 8.0新推出的一种位图。详细介绍见文章最后。
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
try {
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);//解码完成后,将分配的临时数组放回到byte数组池中。
}
}
真正解码操作在decodeFromWrappedStreams这个方法里。
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];
String sourceMimeType = options.outMimeType;//资源类型
// If we failed to obtain the image dimensions, we may end up with an incorrectly sized Bitmap,
// so we want to use a mutable Bitmap type. One way this can happen is if the image header is so
// large (10mb+) that our attempt to use inJustDecodeBounds fails and we're forced to decode the
// full size image.
if (sourceWidth == -1 || sourceHeight == -1) {
isHardwareConfigAllowed = false;
}
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);//获取原始图片的方向。根据解析图片的EXIF(可交换图像文件格式)信息来获得。
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);//获取原始图片类型。
//计算缩放值
calculateScaling(
imageType,
is,
callbacks,
bitmapPool,
downsampleStrategy,
degreesToRotate,
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
options);
//计算配置。包括inPreferredConfig和inDither
calculateConfig(
is,
decodeFormat,
isHardwareConfigAllowed,
isExifOrientationRequired,
options,
targetWidth,
targetHeight);
boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {//如果Android版本大于4.4或者不进行采样率压缩
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);
**//公式:输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)**
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Calculated target [" + expectedWidth + "x" + expectedHeight + "] for source"
+ " [" + sourceWidth + "x" + sourceHeight + "]"
+ ", sampleSize: " + sampleSize
+ ", targetDensity: " + options.inTargetDensity
+ ", density: " + options.inDensity
+ ", density multiplier: " + densityMultiplier);
}
}
// If this isn't an image, or BitmapFactory was unable to parse the size, width and height
// will be -1 here.
if (expectedWidth > 0 && expectedHeight > 0) {
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);//根据options配置,解码最终的图片。
callbacks.onDecodeComplete(bitmapPool, downsampled);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logDecode(sourceWidth, sourceHeight, sourceMimeType, options, downsampled,
requestedWidth, requestedHeight, startTime);
}
Bitmap rotated = null;
if (downsampled != null) {
// If we scaled, the Bitmap density will be our inTargetDensity. Here we correct it back to
// the expected density dpi.
downsampled.setDensity(displayMetrics.densityDpi);
rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);//如果需要旋转,则对图片进行旋转
if (!downsampled.equals(rotated)) {
bitmapPool.put(downsampled);//如果采样的之后的Bitmap和旋转之后的不是同一个,说明有进行旋转操作。将采样的这个Bitmap丢到bitmap池子中。
}
}
return rotated;
}
我们来看一下获取原始图片的实现
private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
options.inJustDecodeBounds = true;//设置为true,表示解码的时候不给bitmap分配内存,只返回bitmap的边界信息。
decodeStream(is, options, decodeCallbacks, bitmapPool);//将inputstream解码。
options.inJustDecodeBounds = false;//解码得到宽高信息后,将inJustDecodeBounds恢复为false。
return new int[] { options.outWidth, options.outHeight };
}
private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
if (options.inJustDecodeBounds) {
is.mark(MARK_POSITION);
} else {
// Once we've read the image header, we no longer need to allow the buffer to expand in
// size. To avoid unnecessary allocations reading image data, we fix the mark limit so that it
// is no larger than our current buffer size here. We need to do so immediately before
// decoding the full image to avoid having our mark limit overridden by other calls to
// mark and reset. See issue #225.
callbacks.onObtainBounds();
}
// BitmapFactory.Options out* variables are reset by most calls to decodeStream, successful or
// otherwise, so capture here in case we log below.
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) {
IOException bitmapAssertionException =
newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to decode with inBitmap, trying again without Bitmap re-use",
bitmapAssertionException);
}
if (options.inBitmap != null) {
try {
is.reset();
bitmapPool.put(options.inBitmap);
options.inBitmap = null;
return decodeStream(is, options, callbacks, bitmapPool);
} catch (IOException resetException) {
throw bitmapAssertionException;
}
}
throw bitmapAssertionException;
} finally {
TransformationUtils.getBitmapDrawableLock().unlock();
}
if (options.inJustDecodeBounds) {
is.reset();
}
return result;
}
计算缩放值的方法calculateScaling
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 {
// We can't downsample source content if we can't determine its dimensions.
if (sourceWidth <= 0 || sourceHeight <= 0) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Unable to determine dimensions for: " + imageType
+ " with target [" + targetWidth + "x" + targetHeight + "]");
}
return;
}
final float exactScaleFactor;
//计算缩放比例,DownsampleStrategy不同,计算方式也不一样。
//默认是DownsampleStrategy是FIT_CENTER
//exactScaleFactor计算方式:requestedWidth/sourceWidth,再取宽高的较小值
if (degreesToRotate == 90 || degreesToRotate == 270) {//如果原始图片旋转了90度或者270度,交换一下getScaleFactor的参数就好。
// If we're rotating the image +-90 degrees, we need to downsample accordingly so the image
// width is decreased to near our target's height and the image height is decreased to near
// our target width.
//noinspection SuspiciousNameCombination
exactScaleFactor = downsampleStrategy.getScaleFactor(sourceHeight, sourceWidth,
targetWidth, targetHeight);
} else {
exactScaleFactor =
downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight);
}
if (exactScaleFactor <= 0f) {
throw new IllegalArgumentException("Cannot scale with factor: " + exactScaleFactor
+ " from: " + downsampleStrategy
+ ", source: [" + sourceWidth + "x" + sourceHeight + "]"
+ ", target: [" + targetWidth + "x" + targetHeight + "]");
}
SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
sourceHeight, targetWidth, targetHeight);
if (rounding == null) {
throw new IllegalArgumentException("Cannot round with null rounding");
}
int outWidth = round(exactScaleFactor * sourceWidth);//向上取整
int outHeight = round(exactScaleFactor * sourceHeight);
int widthScaleFactor = sourceWidth / outWidth;//宽缩放倍数
int heightScaleFactor = sourceHeight / outHeight;//高缩放倍数
int scaleFactor = rounding == SampleSizeRounding.MEMORY
? Math.max(widthScaleFactor, heightScaleFactor)
: Math.min(widthScaleFactor, heightScaleFactor);
int powerOfTwoSampleSize;
// BitmapFactory does not support downsampling wbmp files on platforms <= M. See b/27305903.
if (Build.VERSION.SDK_INT <= 23
&& NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
powerOfTwoSampleSize = 1;//Android 6.0以下不支持采样率压缩。
} else {
powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
if (rounding == SampleSizeRounding.MEMORY
&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
powerOfTwoSampleSize = powerOfTwoSampleSize << 1;//如果Sample策略是SampleSizeRounding.MEMORY即是省内存的,并且计算出来的inSampleSize<(原始宽/request宽),那么再把inSampleSize扩大一倍。
}
}
// Here we mimic framework logic for determining how inSampleSize division is rounded on various
// versions of Android. The logic here has been tested on emulators for Android versions 15-26.
// PNG - Always uses floor
// JPEG - Always uses ceiling
// Webp - Prior to N, always uses floor. At and after N, always uses round.
options.inSampleSize = powerOfTwoSampleSize;//把最终计算好的inSampleSize设置到options里
int powerOfTwoWidth;
int powerOfTwoHeight;
if (imageType == ImageType.JPEG) {//如果是JPEG图片
// libjpegturbo can downsample up to a sample size of 8. libjpegturbo uses ceiling to round.
// After libjpegturbo's native rounding, skia does a secondary scale using floor
// (integer division). Here we replicate that logic.
int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);//向上取整,计算宽
powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);//向上取整,计算高
int secondaryScaling = powerOfTwoSampleSize / 8;
if (secondaryScaling > 0) {//如果powerOfTwoSampleSize>=8,计算第二次缩放后的宽高
powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
}
} else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {//如果是png图片
powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);//根据之前计算的inSampleSize来计算宽高
powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
} else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
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) {
// If we're not confident the image is in one of our types, fall back to checking the
// dimensions again. inJustDecodeBounds decodes do obey inSampleSize.
int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
// Power of two downsampling in BitmapFactory uses a variety of random factors to determine
// rounding that we can't reliably replicate for all image formats. Use ceiling here to make
// sure that we at least provide a Bitmap that's large enough to fit the content we're going
// to load.
powerOfTwoWidth = dimensions[0];
powerOfTwoHeight = dimensions[1];
} else {
powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
}
double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);
// Density scaling is only supported if inBitmap is null prior to KitKat. Avoid setting
// densities here so we calculate the final Bitmap size correctly.
//计算TargetDensity和Density的值,用于计算最终Bitmap的大小
//公式:输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
options.inDensity = getDensityMultiplier(adjustedScaleFactor);
}
if (isScaling(options)) {
options.inScaled = true;
} else {
options.inDensity = options.inTargetDensity = 0;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Calculate scaling"
+ ", source: [" + sourceWidth + "x" + sourceHeight + "]"
+ ", target: [" + targetWidth + "x" + targetHeight + "]"
+ ", power of two scaled: [" + powerOfTwoWidth + "x" + powerOfTwoHeight + "]"
+ ", exact scale factor: " + exactScaleFactor
+ ", power of 2 sample size: " + powerOfTwoSampleSize
+ ", adjusted scale factor: " + adjustedScaleFactor
+ ", target density: " + options.inTargetDensity
+ ", density: " + options.inDensity);
}
}
到这里Glide的图片处理就算是完成了。总结一下主要步骤:
下面关于硬件位图的介绍,摘自官方文档
Bitmap.Config.HARDWARE 是一种 Android O 添加的新的位图格式。硬件位图仅在显存 (graphic memory) 里存储像素数据,并对图片仅在屏幕上绘制的场景做了优化。
因为硬件位图仅储存像素数据的一份副本。一般情况下,应用内存中有一份像素数据(即像素字节数组),而在显存中还有一份副本(在像素被上传到 GPU之后)。而硬件位图仅持有 GPU 中的副本,因此:
硬件位图仅需要一半于其他位图配置的内存;
硬件位图可避免绘制时上传纹理导致的内存抖动。