BaseRequestOptions<?> requestOptions = this
... // 根据 ImageView 原生的 scale type 构建 Glide 的 scale type
Request = buildRequest(target, targetListener, options) // 这里最终调用的是 SingleRequest.obtain() 来创建 request
requestManager.track(target, request)
在 tarck() 方法中执行了 targetTracker.track(target),而这行代码就是用来跟踪生命周期的
如果我们是从网络加载图片,当图片下载成功后会回调 SingleRequest#onResourceReady(Resource<?> resource, DataSource dataSource)
方法。
而图片的下载及解码起始于 SingleRequest#onSizeReady
,然后调用 Engine#load()
开始下载及解码:
... //省略分别从内存,disk 读取图片代码
EnginJob<R> engineJob = engineJobFactory.build()
DecodeJob<R> decodeJob = decodeJobFacotry.build()
josbs.put(key, enginJob)
engineJob.addCallback(cb)
engineJob.start(decodeJob)
复制代码
最后调用 DecodePath#decodeResourceWithList()
,关键代码:
Resource<ResourceType> result = null
for (int i = 0, size = decoders.size()
ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i)
result = decoder.decode(data, width, height, options)
return result
复制代码
图片解码
接下来分析图片的解码过程。
首先我们需要搞清楚 decoders 是怎么来的,原来在初始化 Glide 时会将 Glide 支持的所有 Decoder 注册到 decoderRegistry 中,最终调用 ResourceDecoderRegistry#getDecoders()
方法来获取所需要的 decoders:
public synchronized <T, R> List<ResourceDecoder<T, R>> getDecoders(@NonNull Class<T> dataClass,
@NonNull Class<R> resourceClass) {
List<ResourceDecoder<T, R>> result = new ArrayList<>();
for (String bucket : bucketPriorityList) {
List<Entry<?, ?>> entries = decoders.get(bucket);
if (entries == null) {
continue;
for (Entry<?, ?> entry : entries) {
if (entry.handles(dataClass, resourceClass)) {
result.add((ResourceDecoder<T, R>) entry.decoder);
return result;
复制代码
在Glide中 ResourceDecoder 的实现类有很多,如下图所示
Glide 根据图片的资源类型会调用不同的 Decoder 进行解码,现在我们以最常见的场景,加载网络图片来说明。加载网络图片(PNG格式)调用的是 ByteBufferBitmapDecoder。
不管是加载网络图片还是加载本地资源,都是通过 ByteBufferBitmapDecoder 类进行解码
public class ByteBufferBitmapDecoder implements ResourceDecoder<ByteBuffer, Bitmap> {
private final Downsampler downsampler;
public ByteBufferBitmapDecoder(Downsampler downsampler) {
this.downsampler = downsampler;
@Override
public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
return downsampler.handles(source);
@Override
public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
@NonNull Options options)
throws IOException {
InputStream is = ByteBufferUtil.toStream(source);
return downsampler.decode(is, width, height, options);
复制代码
该类很简单,最主要的是调用Downsampler#decode
方法,Downsampler 直译向下采样器,接下来就重点看下该类。
Downsampler
首先来看 Downsampler
对外提供的方法 decode
方法
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()")
/* 开始构建 BitmpFactory.Options */
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class)
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions()
bitmapFactoryOptions.inTempStorage = bytesForOptions
DecodeFormat decodeFormat = options.get(DECODE_FORMAT)
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION)
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 result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks)
return BitmapResource.obtain(result, bitmapPool)
} finally {
releaseOptions(bitmapFactoryOptions)
byteArrayPool.put(bytesForOptions)
复制代码
该方法首先为 BitmapFactory.Options 设置所需要的参数
inTempStorage
Temp storage to use for decoding. Suggest 16K or so. Glide 在这里用的是 64k
decodeFormat
解码格式, glide 中的图片主要为两种模式 ARGB_8888, RGB_565
fixBitmapToRequestedDimensions
默认为 false(暂时不太理解这个属性的含义,也无法设置成 true)
isHardwareConfigAllowed
boolean isHardwareConfigSafe =
dataSource == DataSource.RESOURCE_DISK_CACHE || decodeHelper.isScaleOnlyOrNoTransform()
Boolean isHardwareConfigAllowed = options.get(Downsampler.ALLOW_HARDWARE_CONFIG)
接下来通过 decodeFromWrappedStream 获取 bitmap,该方法主要逻辑如下:
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool)
int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight
calculateScaling()
calculateConfig()
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool)
callbacks.onDecodeComplete(bitmapPool, downsampled)
复制代码
我们先来理清这几个size,以 width 为例
sourceWidth: 即你从网络下载的原始图片的宽
requestedWidth: 默认为 ImageView 的宽
targeWidth: 最终生成的 bitmap 的宽
接下来分析 calculateScaling 方法
由于都是计算相关,所以举个栗子,假设图片的sourceWidth 为 1000, targetWidth为 200, sourceHeight为 1200, targetWidth 为 300
final float exactScaleFactor = downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight)
SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
sourceHeight, targetWidth, targetHeight)
int outWidth = round(exactScaleFactor * sourceWidth)
int outHeight = round(exactScaleFactor * sourceHeight)
int widthScaleFactor = sourceWidth / outWidth
int heightScaleFactor = sourceHeight / outHeight
int scaleFactor = rounding == SampleSizeRounding.MEMORY //scaleFactor = 4
? Math.max(widthScaleFactor, heightScaleFactor)
: Math.min(widthScaleFactor, heightScaleFactor)
int powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor))
if (rounding == SampleSizeRounding.MEMORY
&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
powerOfTwoSampleSize = powerOfTwoSampleSize << 1
options.inSampleSize = powerOfTwoSampleSize
// 这里暂时还不太理解,看算法这里的 inTragetDesity 和 inDensity 的比值永远为 1
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
复制代码
我们简单看下 CenterOutside
类,代码很简单:
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;
复制代码
接下来通过调用calculateConfig为 options 设置其他属性
if (hardwareConfigState.setHardwareConfigIfAllowed(
targetWidth,
targetHeight,
optionsWithScaling,
format,
isHardwareConfigAllowed,
isExifOrientationRequired)) {
return
// Changing configs can cause skewing on 4.1, see issue
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) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Cannot determine whether the image has alpha or not from header"
+ ", format " + format, e)
optionsWithScaling.inPreferredConfig =
hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565
if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
optionsWithScaling.inDither = true
复制代码
最终调用 ecodeStream
方法,该方法通过对 android api BitmapFactory#decodeStream
对图片进行压缩获得了 bitmap 对象
特别注意的是我们在使用 Glide 时加载的网络图片时,默认都是根据 ImageView 的尺寸大小进行了一定比例的,详细的计算过程在上文中也已经提到。但在实际应用中会有希望让用户看到原图场景,这个时候我们可以这样操作
ImgurGlide.with(vh.imageView)
.load(image.link)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.into(vh.imageView);
复制代码
Skia 库
在 android 中, BitmapFactory.decodeStream
调用的是 natvie 方法,该函数最终调用的是 skia 库中的encodeStream函数来对图片进行压缩编码。接下来大致介绍一下skia库。
Skia 是一个 c++实现的代码库,在android 中以扩展库的形式存在,目录为external/skia/。总体来说skia是个相对简单的库,在android中提供了基本的画图和简单的编解码功能。另外,skia 同样可以挂接其他第3方编码解码库或者硬件编解码库,例如libpng和libjpeg。在Android中skia就是这么做的,\external\skia\src\images文件夹下面,有几个SkImageDecoder_xxx.cpp文件,他们都是继承自SkImageDecoder.cpp类,并利用第三方库对相应类型文件解码,最后再通过SkTRegistry注册,代码如下所示
static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libjpeg_dfactory);
static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_jpeg);
static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libjpeg_efactory);
复制代码
Android编码保存图片就是通过Java层函数——Native层函数——Skia库函数——对应第三方库函数(例如libjpeg),这一层层调用做到的。
最后推荐一个第三方库glide-transformations,可以实现很多图片效果,比如圆角,高斯模糊,黑白。