Glide
-
- Glide是啥?
- 为什么选择Glide?
- 基本使用方式
-
- `Glide.with(context)做了什么?此处以Activity为例`
- `RequestManager.load( url : String)做了什么?`
- `RequestBuilder.into(ImageView view)`做了什么?(此处假设传入的是ImageView)
- `Request.begin()`是如何发起请求?
- `Engine`如何执行具体的目标资源请求?(`Engine.load`做了什么?)
- 啥时候才是完成了整条链路请求啊?
- `Request`是在哪里确定尺寸大小的?如何确认?确认了之后又是如何使用该尺寸的?
- `Glide`中是如何判断资源是否为`Gif`图呢?啥时候用到?
- `Glide`是如何进行解码的?
- `Glide`中是如何进行过变换的?比如圆角等这些。
- `Glide`中做了什么优化,在哪里体现?
- `Glide`中的设计模式
如果看完这篇文章你们有收获的话,恳请你们抽出一秒的时间给我点个赞!这是对我最大的鼓励!Thanks♪(・ω・)ノ
Glide是啥?
是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术,支持拉取,解码和展示视频快照,图片,和GIF动画。
为什么选择Glide?
- 图片解码速度
- 智能缓存机制
- 资源重用(包括网络请求线程池等)
- 生命周期观测
基本使用方式
Glide.with(context).load(url).into(imageView)
👆🏻上面的一段简简单单的代码发生了什么?又有什么值得我们去学习的呢?让我带你们一步步深挖其内功!
Glide.with(context)做了什么?此处以Activity为例
- Glide的创建中,解析器或者回调都能通过注解的Module类配置,其中用到的方法为APT+AMS,通过APT标识需要自定义的Module类,重写目标方法
registerComponent
和applyOptions
,在Glide.get()
方法中,生成GeneratedAppGlideModuleImpl
(若存在注解时生成),然后在Glide#initializeGlide()
方法中applyOptions
和registerComponents
.代码如下:
@NonNull
public static Glide get(@NonNull Context context) {
if (glide == null) {
GeneratedAppGlideModule annotationGeneratedModule =
//若存在注解,则通过反射生成。
getAnnotationGeneratedGlideModules(context.getApplicationContext());
if (glide == null) {
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
return glide;
}
private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules(Context context) {
GeneratedAppGlideModule result = null;
try {
Class<GeneratedAppGlideModule> clazz = (Class<GeneratedAppGlideModule>) Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
result = clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext());
return result;
}
private static void initializeGlide(
@NonNull Context context,
@NonNull GlideBuilder builder,
@Nullable GeneratedAppGlideModule annotationGeneratedModule) {
Context applicationContext = context.getApplicationContext();
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
//若提供了注解类,则需要启能Manifest解析
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
manifestModules = new ManifestParser(applicationContext).parse();
}
//设置自定义RequestManager工厂类(用于生成RequestManger对象)
RequestManagerRetriever.RequestManagerFactory factory =
annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory()
: null;
builder.setRequestManagerFactory(factory);
//引入外部配置
for (GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
Glide glide = builder.build(applicationContext);
//注册一些自定义的解析,可以看官方的例子,暂时用不上
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.registerComponents(applicationContext, glide, glide.registry);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
传入一个context,做了什么?
- 通过
context
获取对应的FragmentManager
,跟着创建一个空白的RequestManagerFragment
,将该Fragment
add如Fm中,从而使RequestManager具备生命周期感知的能力。
@NonNull public RequestManager get(@NonNull FragmentActivity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); //获取context对应的FragmentManager,若不是Fragment或Activity,则会使用ApplicationManager,该RequestManager只会回调onStart. FragmentManager fm = activity.getSupportFragmentManager(); return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } } @SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"}) @Deprecated @NonNull private RequestManager fragmentGet( @NonNull Context context, @NonNull android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { //创建了一个RequestManagerFragment RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible); //初始状态下RequestManagerFragment中的requestManager为空 RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); //通过RequestManagerFactory工厂类创建,默认为DEFAULT_FACTORY,可以通过Anotation注解类配置修改 requestManager = //build requestManager时,在构造函数中,会将requestManager添加到LifeCycle factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; }
- 通过
生命感知在哪里体现了?有啥作用?
RequestManagerFragment
在初始化时会创建一个ActivityFragmentLifecycle
对象,在关键生命周期中会进行回调,该对象暴露给外部访问
- 构造
RequestManager
时会通过该fragment获取到ActivityFragmentLifecycle
,从而使得RequestManager也保持有对应的生命周期,那为啥不直接创建好RequestManager
后扔给该RequestManagerFragment
去直接调用requestManager#onStart/onStop/onDestroy
呢? - 在
RequestManager
中会通过该Lifecycle
对象调用addListener(this)
从而关联生命周期,详见下面代码。
RequestManager(
Glide glide,
//RequestManagerFragment的lifecycle
Lifecycle lifecycle,
RequestManagerTreeNode treeNode,
RequestTracker requestTracker,
ConnectivityMonitorFactory factory,
Context context) {
connectivityMonitor =
factory.build(
context.getApplicationContext(),
new RequestManagerConnectivityListener(requestTracker));
// 如果我们是应用程序级别的请求管理器,我们可能会在后台线程上创建。在这种情况下
//,我们不能冒险同步暂停或恢复请求,因此我们通过发布到主线程来延迟将自己添加为生命周期侦听器来解决这个问题。这应该是完全安全的。
if (Util.isOnBackgroundThread()) {
Util.postOnUiThread(addSelfToLifecycle);
} else {
//此处关联生命周期
lifecycle.addListener(this);
}
//监听网络变化监听,在网络恢复时会进行一次restartRequests操作
lifecycle.addListener(connectivityMonitor);
//Glide初始化时通过Builder生成defaultRequestListeners,或为emptyList
defaultRequestListeners =
new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners());
setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());
//这里会注册一个Glide全局的低内存观测,在低内存时回调`onTrimMemory`
glide.registerRequestManager(this);
}
@Override
public void onTrimMemory(int level) {
//通知所有的requestManager去让其内部的request停止
trimMemory(level);
}
protected synchronized void setRequestOptions(@NonNull RequestOptions toSet) {
requestOptions = toSet.clone().autoClone();
}
RequestManager.load( url : String)做了什么?
**创建RequestBuilder
,通过RequestBuilder
创建Request
(非常典型的建造者模式,流式的参数设置,只不过没有暴露buildRequest方法,而是使用into()或其它方法构造,并发起请求 **
- 选择目标解码器
transcodeClass
给到RequestBuilder
,后续会在创建Request
对象时,将解码器传入到该对象中,比如下述代码,传入了目标解码为Drawable
/**
尝试始终使用可以解码Drawable的任何子类的任何已注册的ResourceDecoder加载资源。
默认情况下,可能会返回BitmapDrawable或GifDrawable ,但如果为其他Drawable子类注册了其他解码器,则也可能会返回这些子类中的任何一个。
回报:
用于加载Drawable的新请求构建器。
**/
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
//看上边的注释,“如果为其他Drawable子类注册了其他解码器”,在哪?okay,是在Registry
//TResource为TargetResource
/**例如
*registry.append(Registry.BUCKET_BITMAP, Bitmap.class, Bitmap.class, new UnitBitmapDecoder())
**/
- 创建出
Request
对象并且设置目标资源对象model
(url,File等)
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
if (isAutoCloneEnabled()) {
return clone().loadGeneric(model);
}
//object对象,支持所有
this.model = model;
isModelSet = true;
return selfOrThrowIfLocked();
}
我这里只是简单的说明了load(String)
的用法,事实上,RequestBuilder
可以设置的非常多
由于Glide在构建Request
时,可以修改requestOption
的入口很多,例如全局的,RequestBuilder
中的,还有单独设置的(errorId()
,placeHolder()
)这些,所以,需要在保持原有的配置不变的情况下进行设置,Glide是这么做的。在设置某个Field
时,会将对应的标志Bit位置为1,从而判断isSet()
,若为true则修改,否则,保持原来的配置.
@NonNull
@CheckResult
public T apply(@NonNull BaseRequestOptions<?> o) {
if (isAutoCloneEnabled) {
return clone().apply(o);
}
BaseRequestOptions<?> other = o;
if (isSet(other.fields, SIZE_MULTIPLIER)) {
sizeMultiplier = other.sizeMultiplier;
}
if (isSet(other.fields, USE_UNLIMITED_SOURCE_GENERATORS_POOL)) {
useUnlimitedSourceGeneratorsPool = other.useUnlimitedSourceGeneratorsPool;
}
if (isSet(other.fields, USE_ANIMATION_POOL)) {
useAnimationPool = other.useAnimationPool;
}
if (isSet(other.fields, DISK_CACHE_STRATEGY)) {
diskCacheStrategy = other.diskCacheStrategy;
}
if (isSet(other.fields, PRIORITY)) {
priority = other.priority;
}
if (isSet(other.fields, ERROR_PLACEHOLDER)) {
errorPlaceholder = other.errorPlaceholder;
errorId = 0;
fields &= ~ERROR_ID;
}
if (isSet(other.fields, ERROR_ID)) {
errorId = other.errorId;
errorPlaceholder = null;
fields &= ~ERROR_PLACEHOLDER;
}
if (isSet(other.fields, PLACEHOLDER)) {
placeholderDrawable = other.placeholderDrawable;
placeholderId = 0;
fields &= ~PLACEHOLDER_ID;
}
if (isSet(other.fields, PLACEHOLDER_ID)) {
placeholderId = other.placeholderId;
placeholderDrawable = null;
fields &= ~PLACEHOLDER;
}
if (isSet(other.fields, IS_CACHEABLE)) {
isCacheable = other.isCacheable;
}
if (isSet(other.fields, OVERRIDE)) {
overrideWidth = other.overrideWidth;
overrideHeight = other.overrideHeight;
}
if (isSet(other.fields, SIGNATURE)) {
signature = other.signature;
}
if (isSet(other.fields, RESOURCE_CLASS)) {
resourceClass = other.resourceClass;
}
if (isSet(other.fields, FALLBACK)) {
fallbackDrawable = other.fallbackDrawable;
fallbackId = 0;
fields &= ~FALLBACK_ID;
}
if (isSet(other.fields, FALLBACK_ID)) {
fallbackId = other.fallbackId;
fallbackDrawable = null;
fields &= ~FALLBACK;
}
if (isSet(other.fields, THEME)) {
theme = other.theme;
}
if (isSet(other.fields, TRANSFORMATION_ALLOWED)) {
isTransformationAllowed = other.isTransformationAllowed;
}
if (isSet(other.fields, TRANSFORMATION_REQUIRED)) {
isTransformationRequired = other.isTransformationRequired;
}
if (isSet(other.fields, TRANSFORMATION)) {
transformations.putAll(other.transformations);
isScaleOnlyOrNoTransform = other.isScaleOnlyOrNoTransform;
}
if (isSet(other.fields, ONLY_RETRIEVE_FROM_CACHE)) {
onlyRetrieveFromCache = other.onlyRetrieveFromCache;
}
// Applying options with dontTransform() is expected to clear our transformations.
if (!isTransformationAllowed) {
transformations.clear();
fields &= ~TRANSFORMATION;
isTransformationRequired = false;
fields &= ~TRANSFORMATION_REQUIRED;
isScaleOnlyOrNoTransform = true;
}
fields |= other.fields;
options.putAll(other.options);
return selfOrThrowIfLocked();
}
RequestBuilder.into(ImageView view)
做了什么?(此处假设传入的是ImageView)
- 根据传入目标解析类型构造对应的
VIewTarget
,此处用到全局对象GlideContext
中的imageViewTargetFactory
创建对应的ViewTarget
,若未指定目标解码类型,默认为asDrawable(),则此时glideContext.buildImageViewTarget()
返回DrawableImageViewTarget
//RequestBuilder
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
//忽略
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
//GlideContext.java
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
//ImageViewTargetFactory
public <Z> ViewTarget<ImageView, Z> buildTarget(
@NonNull ImageView view, @NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
//默认我们目标转码类型为Drawable
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
- 通过
target
构造对应的Request
对象,主要用于回调,判断该target
是否已经发出过同样的请求,若是,则return,否则requestManager
将request
对象添加进requestTracker
中并发起请求request.begin()
- Glide在处理列表对象元素时,为了防止影响原列表,都会使用快照模式进行元素操作.
@NonNull
@SuppressWarnings("UseBulkOperation")
public static <T> List<T> getSnapshot(@NonNull Collection<T> other) {
List<T> result = new ArrayList<>(other.size());
for (T item : other) {
if (item != null) {
result.add(item);
}
}
return result;
}
Request.begin()
是如何发起请求?
首先判断该
Request
是否之前已经完成过请求,若已经完成,直接回调onResourceReady()
//request由于生命周期调用的resumeRequest不会走到这,会在begin之前提前判断 //通常是view.onAttach回调时会触发 if (status == Status.COMPLETE) { onResourceReady(resource, DataSource.MEMORY_CACHE); return; }
ViewTarget
@NonNull @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) public final ViewTarget<T, Z> clearOnDetach() { if (attachStateListener != null) { return this; } attachStateListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { //request.begin() resumeMyRequest(); } @Override public void onViewDetachedFromWindow(View v) { pauseMyRequest(); } }; maybeAddAttachStateListener(); return this; }
在
target.onLoadStarted()
时,会调用view.addOnAttachStateChangeListener()
方法进行attach事件的观测(前提是ViewTarget提前调用了clearOnDetach
),在onViewAttachedToWindow
时会执行request.begin()
,就会按照1.的方式判断是否需要加载。确定大小
status = Status.WAITING_FOR_SIZE; if (Util.isValidDimensions(overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { //this为回调,会回调到onSizeReady(就上边那个) target.getSize(this); }
CustomViewTarget
void getSize(@NonNull SizeReadyCallback cb) { int currentWidth = getTargetWidth(); int currentHeight = getTargetHeight(); if (isViewStateAndSizeValid(currentWidth, currentHeight)) { cb.onSizeReady(currentWidth, currentHeight); return; } // We want to notify callbacks in the order they were added and we only expect one or two // callbacks to be added a time, so a List is a reasonable choice. if (!cbs.contains(cb)) { cbs.add(cb); } if (layoutListener == null) { ViewTreeObserver observer = view.getViewTreeObserver(); layoutListener = new SizeDeterminerLayoutListener(this); observer.addOnPreDrawListener(layoutListener); } } //当View,在preDraw回调中(该回调在测量完毕之后,onDraw之前回调)最终执行到这 @Synthetic void checkCurrentDimens() { if (cbs.isEmpty()) { return; } int currentWidth = getTargetWidth(); int currentHeight = getTargetHeight(); if (!isViewStateAndSizeValid(currentWidth, currentHeight)) { return; } //回调到SingleRequest的onSizeReady notifyCbs(currentWidth, currentHeight); clearCallbacksAndListener(); }
Engine
执行具体的目标资源请求engine.load(n个参数)
Engine
如何执行具体的目标资源请求?(Engine.load
做了什么?)
Key!通过
EngineKeyFactory#build
方法确认EngineKey
EngineKey key = keyFactory.buildKey( model, signature, width, height, transformations, resourceClass, transcodeClass, options); //这个Key会在缓存到内存时作为Key //会在持久化到Disk中时,重新封装为DataCacheKey或ResourceCacheKey,再转换为String作为文件名
DecodeJob.class
:先跟大家演示下key
的使用场景@Synthetic @NonNull /** * 该方法会在LoadPath.load成功后回调,接着调用notifyEncodeAndRelease */ <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) { final Key key; //代码忽略,下面是伪代码,其中EncodeStrategy.XXX可外部传入 boolean isResourceCacheable = EncodeStrategy.XXX.isResourceCacheable if(!isResourceCacheable){ return } switch (encodeStrategy) { case SOURCE: key = new DataCacheKey(currentSourceKey, signature); break; case TRANSFORMED: key = new ResourceCacheKey( decodeHelper.getArrayPool(), currentSourceKey, signature, width, height, appliedTransformation, resourceSubClass, options); break; default: throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy); LockedResource<Z> lockedResult = LockedResource.obtain(transformed); //在这里初始化DeferredEncodeManager. deferredEncodeManager.init(key, encoder, lockedResult); result = lockedResult; return result; } /** * 通知!!可以进行encode和释放请求相关资源啦!释放的话,就是重置属性和将该job存入对象池中,下次复用 */ private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) { if (resource instanceof Initializable) { ((Initializable) resource).initialize(); } Resource<R> result = resource; LockedResource<R> lockedResource = null; if (deferredEncodeManager.hasResourceToEncode()) { lockedResource = LockedResource.obtain(resource); result = lockedResource; } notifyComplete(result, dataSource); stage = Stage.ENCODE; try { if (deferredEncodeManager.hasResourceToEncode()) { //执行持久化 deferredEncodeManager.encode(diskCacheProvider, options); } } finally { if (lockedResource != null) { lockedResource.unlock(); } } // Call onEncodeComplete outside the finally block so that it's not called if the encode process // throws. onEncodeComplete(); }
DecodeJob$DeferredEncodeManager.class
void encode(DiskCacheProvider diskCacheProvider, Options options) { GlideTrace.beginSection("DecodeJob.encode"); try { //此处最终会调用到DiskLruCacheWrapper中的put方法,此处的key就是上面说的经过封装后的Key diskCacheProvider .getDiskCache() //File写入操作 .put(key, new DataCacheWriter<>(encoder, toEncode, options)); } finally { toEncode.unlock(); GlideTrace.endSection(); } }
优先从内存缓存中获取目标资源,
key
为刚刚说的EngineKey
,其中缓存分为ActiveResources
(活跃资源,用于存储当前活跃在内存中的Resource
对象,通常是此图像当前在另一个视图中可见时)和MemoryCache
内存缓存。接着从Disk中尝试获取数据。
默认情况下,Glide 使用内存和磁盘缓存来避免不必要的网络调用,它会在对图像发起新的请求调用之前检查多层缓存。activeResources
是一个以弱引用资源为value, 的map,memoryCache
是使用LruResourceCache实现的。就是说,activeResources
是一个随时有可能被回收资源。它存在的意义在于,memoryCache
的强引用的频繁读写也有可能造成内存激增频繁GC, 而造成内存抖动。资源在使用的过程中将会保存在activeResources
中,而activeResources
是弱引用的,可以随时被系统回收,不会造成内存泄漏和过多的使用EngineResource<?> memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); @Nullable private EngineResource<?> loadFromMemory( EngineKey key, boolean isMemoryCacheable, long startTime) { if (!isMemoryCacheable) { //是否跳过缓存,强制重新请求,有一种情况异常情况,仅靠skipMemoryCache也不管用。 return null; } //先读活跃资源 EngineResource<?> active = loadFromActiveResources(key); if (active != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return active; } //再读内存缓存 EngineResource<?> cached = loadFromCache(key); if (cached != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return cached; } return null; }
waitForExistingOrStartNewJob
是尝试从硬盘中优先获取,还是木有的话,那么就会发起网络请求。Jobs
:由Engine
对象所持有,管理Key对应的EngineJobs
集合,俗话就是HashMapEngineJobFactory
:负责构造EngineJob对象EngineJob
:在加载完成后回调ResourceCallback#onResourceReady
.(SingleRequest
实现了该接口,对ImageViewTarget
进行了setImageDrawable()
),EngineJob
中也有start
方法是发起请求的初始入口DecodeJob
:负责调度不同的资源请求者从缓存数据或原始源解码出资源并应用转换和转码,在解码完成后回调DecodeJob.Callback#onResourceReady
·(EngineJob
实现了该接口)
private <R> LoadStatus waitForExistingOrStartNewJob( GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor, EngineKey key, long startTime) { EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); if (current != null) { current.addCallback(cb, callbackExecutor); return new LoadStatus(cb, current); } EngineJob<R> engineJob = engineJobFactory.build( key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); DecodeJob<R> decodeJob = decodeJobFactory.build( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, //作为DecodeJob.Callback传入 engineJob); jobs.put(key, engineJob); //完成后回调到Request中的onResourceReady engineJob.addCallback(cb, callbackExecutor); engineJob.start(decodeJob); return new LoadStatus(cb, engineJob); }
engineJob.start(decodeJob)
是开启了线程采用责任链的设计模式去获取对应的Generator和ModelLoader
,并通过ModelLoader
创造对应的LoadData
,其中LoadData
对象中包含DataFetcher
对象,具体最终会使用哪个LoadData
是根据Model
的类型,ResourceClassType
,ImageType
等共同决定的有意思的链式请求设计,DecodeJob中,会根据当前的
stage
来获取到对应的资源请求者的生成者xxxxGenerator
去创建对应的DataFetcher
,并且在构造函数中会传入FetcherReadyCallback
回调,若未搜索到,则会去获取下一个xxxxGenerator
,如果都没有,则notifyFailed
,当资源请求者准备完毕后,又会通过onDataFetcherReady()
回调回去并赋值this.currentFetcher = fetcher;
(若不是在发起进程发生的回调会重新run一遍,此时runReason = RunReason.DECODE_DATA
,会直接decodeFromRetrievedData
,并对currentFetcher进行cleanup()
操作),可以看下列代码:private DataFetcherGenerator getNextGenerator() { switch (stage) { case RESOURCE_CACHE: return new ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: return new DataCacheGenerator(decodeHelper, this); case SOURCE: return new SourceGenerator(decodeHelper, this); case FINISHED: return null; default: throw new IllegalStateException("Unrecognized stage: " + stage); } } private void runWrapped() { switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); } } private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; while (!isCancelled && currentGenerator != null //循环直到找到资源或三个Generator都执行搜索完 && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { //为SOURCE时,代表要执行decodeFromRetrievedData了。 reschedule(); return; } } // We've run out of stages and generators, give up. if ((stage == Stage.FINISHED || isCancelled) && !isStarted) { notifyFailed(); } // Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. } @Override public void onDataFetcherReady( Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey = sourceKey; this.currentData = data; this.currentFetcher = fetcher; this.currentDataSource = dataSource; this.currentAttemptingKey = attemptedKey; if (Thread.currentThread() != currentThread) { //设置runReason,用于重新执行runWrapped()时直接decodeFromRetrievedData,代表已经确定了DataFetcher和data,或是根本没解析到,都为空 runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData"); try { decodeFromRetrievedData(); } finally { GlideTrace.endSection(); } } }
1. `ResourceCacheGenerator`:从包含下采样/转换的资源数据的缓存文件中生成DataFetchers ,这个是转换后的资源类型 1. `DataCacheGenerator`:和上面的区别是key的来源,这个是原图类型
啥时候才是完成了整条链路请求啊?
- 可以认为是在
DecodeJob
的onDataFetchReady
后就已经完成了请求,接下来就是设置并缓存到ActivateResource
集合中,并通过回调在Target
对象中的onResourceReady
中将获取回来的object
对象传回.
Request
是在哪里确定尺寸大小的?如何确认?确认了之后又是如何使用该尺寸的?
是在构造
Request
时通过overrideWidth/Height
来确定的- 可以是ImageView尺寸自适应,此时
overrideWidth/Height
均为SIZE_ORIGIN
- 可以是ImageView指定了尺寸,次吃
overrideWidth/Height
会在View
测量完成后赋值,可以查看上面的Request.begin()是如何发起请求
章节 - 可以是指定,通过设置
requestOptions
指定.
- 可以是ImageView尺寸自适应,此时
确认后是在
onDataFetchReady
后会最终到解码阶段时使用,使用的具体方式类似如下(不同的类型对应不同的解码方式,但是大同小异,可以举一反三,这是展示的是BitmapImageDecoderResourceDecoder
)@Nullable @Override public final Resource<T> decode( @NonNull Source source, final int requestedWidth, final int requestedHeight, @NonNull Options options) throws IOException { final DecodeFormat decodeFormat = options.get(Downsampler.DECODE_FORMAT); final DownsampleStrategy strategy = options.get(DownsampleStrategy.OPTION); final boolean isHardwareConfigAllowed = options.get(Downsampler.ALLOW_HARDWARE_CONFIG) != null && options.get(Downsampler.ALLOW_HARDWARE_CONFIG); final PreferredColorSpace preferredColorSpace = options.get(Downsampler.PREFERRED_COLOR_SPACE); return decode( source, requestedWidth, requestedHeight, new OnHeaderDecodedListener() { @SuppressLint("Override") @Override public void onHeaderDecoded(ImageDecoder decoder, ImageInfo info, Source source) { Size size = info.getSize(); int targetWidth = requestedWidth; if (requestedWidth == Target.SIZE_ORIGINAL) { targetWidth = size.getWidth(); } int targetHeight = requestedHeight; if (requestedHeight == Target.SIZE_ORIGINAL) { targetHeight = size.getHeight(); } float scaleFactor = strategy.getScaleFactor( size.getWidth(), size.getHeight(), targetWidth, targetHeight); int resizeWidth = Math.round(scaleFactor * size.getWidth()); int resizeHeight = Math.round(scaleFactor * size.getHeight()); //大小 decoder.setTargetSize(resizeWidth, resizeHeight); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { boolean isP3Eligible = preferredColorSpace == PreferredColorSpace.DISPLAY_P3 && info.getColorSpace() != null && info.getColorSpace().isWideGamut(); decoder.setTargetColorSpace( ColorSpace.get( isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB)); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); } } }); }
Glide
中是如何判断资源是否为Gif
图呢?啥时候用到?
要解答该问题,需要认识以下几个重要的对象。
Registry
:存放在Glide
对象中的一个非常非常重要的类,管理着Glide
中所有的支持的ResourceDecoder
,Encoder
,ModelLoaderFactory
,LoadPath
等对象实例,当然,我们接下来要说的关键对象ImageHeaderParserRegistry
也在其中.ImageHeaderParseRegistry
:管理ImageHeaderParser
ImageHeaderParser
:图像解析类,可以通过IO流或Byte数组解析出图像type和orientationGlide(...){ registry.register(new DefaultImageHeaderParser()); // Right now we're only using this parser for HEIF images, which are only supported on OMR1+. // If we need this for other file types, we should consider removing this restriction. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { registry.register(new ExifInterfaceImageHeaderParser()); } }
实现方式:
@NonNull private ImageType getType(Reader reader) throws IOException { final int firstTwoBytes = reader.getUInt16(); // JPEG. if (firstTwoBytes == EXIF_MAGIC_NUMBER) { return JPEG; } final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); // PNG. if (firstFourBytes == PNG_HEADER) { // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha // -color-type reader.skip(25 - 4); int alpha = reader.getByte(); // A RGB indexed PNG can also have transparency. Better safe than sorry! return alpha >= 3 ? PNG_A : PNG; } // GIF from first 3 bytes. if (firstFourBytes >> 8 == GIF_HEADER) { return GIF; } // WebP (reads up to 21 bytes). See https://developers.google.com/speed/webp/docs/riff_container // for details. if (firstFourBytes != RIFF_HEADER) { return UNKNOWN; } // Bytes 4 - 7 contain length information. Skip these. reader.skip(4); final int thirdFourBytes = (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); if (thirdFourBytes != WEBP_HEADER) { return UNKNOWN; } final int fourthFourBytes = (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); if ((fourthFourBytes & VP8_HEADER_MASK) != VP8_HEADER) { return UNKNOWN; } if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) { // Skip some more length bytes and check for transparency/alpha flag. reader.skip(4); return (reader.getByte() & WEBP_EXTENDED_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP; } if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) { // See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt // for more info. reader.skip(4); return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP; } return ImageType.WEBP; }
啥时候用的?在解码时判断,举个例子
//如果是ImageType.GIF,则采用该ByteBufferGifDecoder public class ByteBufferGifDecoder implements ResourceDecoder<ByteBuffer, GifDrawable> { @Override public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) throws IOException { return !options.get(GifOptions.DISABLE_ANIMATION) && ImageHeaderParserUtils.getType(parsers, source) == ImageType.GIF; } }
当然,如果需要的话,我们也可以添加自己的ImageHeaderParser
,并通过preappend
或append
来决定是否要优先采用我们加入的ImageHeaderParser
进行解析。
Glide
是如何进行解码的?
想要了解Glide是如何解码,首先我们得知道解码器是在什么时候被添加?添加到哪?什么时候获取?如何获取?获取到了后咋解码的?
解码器是在什么时候被添加?
Glide的构造函数中,会通过
Registry
对象append
不同的解码器,最主要的是下面几个:public Glide(...) { registry = new Registry(); //Byte数组,Gif类型图片Decoder ByteBufferGifDecoder byteBufferGifDecoder = new ByteBufferGifDecoder(context, imageHeaderParsers, bitmapPool, arrayPool); //ParcelFileDescriptorDecoder ResourceDecoder<ParcelFileDescriptor, Bitmap> parcelFileDescriptorVideoDecoder = VideoDecoder.parcel(bitmapPool); //Byte数组,Bitmap图片Decoder ResourceDecoder<ByteBuffer, Bitmap> byteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler); //流解析Decoder ResourceDecoder<InputStream, Bitmap> streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool); registry .append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder) .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder) /* GIFs */ .append( Registry.BUCKET_GIF, InputStream.class, GifDrawable.class, new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool)) .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder) .append(GifDrawable.class, new GifDrawableEncoder()) }
注意
Registry#append
的参数,其中第一个参数bucket:String
(桶),用于目标ResourceDecoderRegistry
中的decoders:Map<String, List<Entry<?, ?>>>
中的key.其余参数封装成一个ResourceDecoderRegistry$Entry
(其实Encoder也是这个套路)。
这里Registry
使用了一个典型的外观者模式,具体append的对象由Registry
去执行,去根据目标对象取到具体的Registry
(例如Encoder,Decoder,Transcoder,ImageHeaderParser等)@NonNull public <Data, TResource> Registry append( @NonNull String bucket, @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass, @NonNull ResourceDecoder<Data, TResource> decoder) { decoderRegistry.append(bucket, decoder, dataClass, resourceClass); return this; }
public synchronized <T, R> void append( @NonNull String bucket, @NonNull ResourceDecoder<T, R> decoder, @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) { getOrAddEntryList(bucket).add(new Entry<>(dataClass, resourceClass, decoder)); } //加入bucket链表和<bucket, decoderEntry>的map @NonNull private synchronized List<Entry<?, ?>> getOrAddEntryList(@NonNull String bucket) { if (!bucketPriorityList.contains(bucket)) { // Add this unspecified bucket as a low priority bucket. bucketPriorityList.add(bucket); } List<Entry<?, ?>> entries = decoders.get(bucket); if (entries == null) { entries = new ArrayList<>(); decoders.put(bucket, entries); } return entries; }
解码器是什么时候获取的?
在图片数据已经通过
DataFetcher
获取完成后,会进行解码器的获取并解析。看下述代码://DecodeJob //啥时候回调fetcherReady? //在fetcher#loadData成功后回调 @Override public void onDataFetcherReady( Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey = sourceKey; this.currentData = data; this.currentFetcher = fetcher; this.currentDataSource = dataSource; this.currentAttemptingKey = attemptedKey; if (Thread.currentThread() != currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData"); try { //do this decodeFromRetrievedData(); } finally { GlideTrace.endSection(); } } } private void decodeFromRetrievedData() { if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey( "Retrieved data", startFetchTime, "data: " + currentData + ", cache key: " + currentSourceKey + ", fetcher: " + currentFetcher); } Resource<R> resource = null; try { //传入dataFetcher,和当前获取到的数据,传入的fetcher是用来释放资源 resource = decodeFromData(currentFetcher, currentData, currentDataSource); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); } if (resource != null) { notifyEncodeAndRelease(resource, currentDataSource); } else { runGenerators(); } } private <Data> Resource<R> decodeFromData( DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException { try { if (data == null) { return null; } long startTime = LogTime.getLogTime(); //do this Resource<R> result = decodeFromFetcher(data, dataSource); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded result " + result, startTime); } return result; } finally { fetcher.cleanup(); } } @SuppressWarnings("unchecked") private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource) throws GlideException { //获取到目标LoadPath,深入该方法,看下如何获取的LoadPath LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass()); //开始调度LoadPath去执行load return runLoadPath(data, dataSource, path); } <Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) { return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass); }
LoadPath
:分析属性,持有数据源Clz类,持有DecodePath
集合,用于调度DecodePath中的decoders#decode
.DecodePath
:持有数据源Clz类,持有ResourceTranscoder
(资源转换者),持有decoders:List<? extends ResourceDecoder<DataType, ResourceType>>
(解码者集合)
//Registry @Nullable public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath( @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass, @NonNull Class<Transcode> transcodeClass) { LoadPath<Data, TResource, Transcode> result = loadPathCache.get(dataClass, resourceClass, transcodeClass); if (loadPathCache.isEmptyLoadPath(result)) { return null; } else if (result == null) { List<DecodePath<Data, TResource, Transcode>> decodePaths = getDecodePaths(dataClass, resourceClass, transcodeClass); // It's possible there is no way to decode or transcode to the desired types from a given // data class. if (decodePaths.isEmpty()) { result = null; } else { result = new LoadPath<>( dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool); } //将LoadPath缓存起来 loadPathCache.put(dataClass, resourceClass, transcodeClass, result); } return result; } @NonNull private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths( @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass, @NonNull Class<Transcode> transcodeClass) { List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>(); List<Class<TResource>> registeredResourceClasses = decoderRegistry.getResourceClasses(dataClass, resourceClass); for (Class<TResource> registeredResourceClass : registeredResourceClasses) { List<Class<Transcode>> registeredTranscodeClasses = transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass); for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) { List<ResourceDecoder<Data, TResource>> decoders = decoderRegistry.getDecoders(dataClass, registeredResourceClass); ResourceTranscoder<TResource, Transcode> transcoder = transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass); @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") //取到dataClass对应的解码器和转换器,封装成DecodePath DecodePath<Data, TResource, Transcode> path = new DecodePath<>( dataClass, registeredResourceClass, registeredTranscodeClass, decoders, transcoder, throwableListPool); decodePaths.add(path); } } return decodePaths; }
//ResourceDecoderRegistry @NonNull @SuppressWarnings("unchecked") public synchronized <T, R> List<ResourceDecoder<T, R>> getDecoders( @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) { List<ResourceDecoder<T, R>> result = new ArrayList<>(); //别担心这里的双循环,不会每次都执行,比较耗时,后续相同的dataClass和resourceClass组合会采用localPathCache,不会再进到这执行双for循环。 for (String bucket : bucketPriorityList) { //此处的decoders就是上面的Map<String, List<Entry<?, ?>>表 List<Entry<?, ?>> entries = decoders.get(bucket); if (entries == null) { continue; } for (Entry<?, ?> entry : entries) { //entry的handles方法是判断源数据类型和目标类型是否一致,若是则添加到result-List中 if (entry.handles(dataClass, resourceClass)) { //还记得这个Entry对象吗,这个entry值得是ResourceDecoder#Entry,传入的三个参数分别是啥来的,你们自己去回忆一下哈 result.add((ResourceDecoder<T, R>) entry.decoder); } } } // TODO: cache result list. //这里的result被会添加到DecodePath中 //List<DecodePath>会被LoadPath所持有 //LoadPath会被LoadPathCache缓存下来 return result; }
解码器是咋使用的?
回忆到刚刚的
runLoadPath(data, dataSource, path)
方法private <Data, ResourceType> Resource<R> runLoadPath( Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path) throws GlideException { //将数据重读,比如buffer,position指向0(起始位置) DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data); //注意这里的with和height终于用到了,每次,限制大小是在解码的过程中执行的 return path.load( rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource)); }
//LoadPath public Resource<Transcode> load( DataRewinder<Data> rewinder, @NonNull Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException { //连异常都要用对象池缓存,恐怖至极 List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire()); try { return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables); } finally { listPool.release(throwables); } } private Resource<Transcode> loadWithExceptionList( DataRewinder<Data> rewinder, @NonNull Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback, List<Throwable> exceptions) throws GlideException { Resource<Transcode> result = null; for (int i = 0, size = decodePaths.size(); i < size; i++) { DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i); try { //通过DecodePath中持有的decoder去做decode result = path.decode(rewinder, width, height, options, decodeCallback); } catch (GlideException e) { exceptions.add(e); } if (result != null) { //解析到之后直接当场退出循环 break; } } return result; }
//DecodePath.java public Resource<Transcode> decode( DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException { //在这里进行decode Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options); //在这里进行变换,不过多分析,看后续 Resource<ResourceType> transformed = callback.onResourceDecoded(decoded); return transcoder.transcode(transformed, options); } @NonNull private Resource<ResourceType> decodeResource( DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options) throws GlideException { //do this return decodeResourceWithList(rewinder, width, height, options, exceptions); } @NonNull private Resource<ResourceType> decodeResourceWithList( DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException { Resource<ResourceType> result = null; //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = decoders.size(); i < size; i++) { ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i); DataType data = rewinder.rewindAndGet(); //具体要使用哪个解析器,这取决于decoder.handles()方法 if (decoder.handles(data, options)) { data = rewinder.rewindAndGet(); //真正执行decode的地方,是不是感觉和上面的path.decode方法很像,对的,基本一模一样。 result = decoder.decode(data, width, height, options); } if (result != null) { //解析到之后直接当场退出循环 break; } } return result; }
由于
decoder#decode
方法真的调用链很长,我举两个例子展示下(没全看,目前看到大部分的Decoder
都会先执行到下面的decode
,当然也不全是,比如File,Uri等,感兴趣可以自己去看,哦,对了,也可以自定义解析器):ByteBufferBitmapDecoder.java
:@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.java
:public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight, Options options) throws IOException { return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS); } public Resource<Bitmap> decode( InputStream is, int requestedWidth, int requestedHeight, Options options, DecodeCallbacks callbacks) throws IOException { //忽略了一堆的代码 //但保留一行吧,因为Glide这个Options太有意思了,后面会讲一下 boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS); try { //nice,又看到了熟悉的东西了,比如decodeFormat(默认ARGB_8888,这里建议封装时要进行一次优化,大部分的app只需要rgb_565即可),width,height,还有个有意思的downsampleStrategy(下采样策略,可以看下图) // Bitmap result = decodeFromWrappedStreams( is, bitmapFactoryOptions, downsampleStrategy, decodeFormat, //色彩空间,我也不知道它有什么鸟用 preferredColorSpace, isHardwareConfigAllowed, requestedWidth, requestedHeight, fixBitmapToRequestedDimensions, callbacks); return BitmapResource.obtain(result, bitmapPool); } finally { releaseOptions(bitmapFactoryOptions); byteArrayPool.put(bytesForOptions); } } private Bitmap decodeFromWrappedStreams( InputStream is, BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, DecodeFormat decodeFormat, PreferredColorSpace preferredColorSpace, boolean isHardwareConfigAllowed, int requestedWidth, int requestedHeight, boolean fixBitmapToRequestedDimensions, DecodeCallbacks callbacks) throws IOException { long startTime = LogTime.getLogTime(); //会通过options.inJustDecodeBounds 设置为仅decode,该标识位如果设置为 true,解码器将返回 null(无位图),但out...字段仍将设置,允许调用者查询位图而无需为其像素分配内存。 int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool); int sourceWidth = sourceDimensions[0]; int sourceHeight = sourceDimensions[1]; String sourceMimeType = options.outMimeType; //这里说了,若图片打大小超过10M,将会导致提前预获取图片尺寸失败,因为缓存区不够支持我们去完成操作 // 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); int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation); boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation); int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth) : requestedWidth; int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight) : requestedHeight; ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool); //获得optino.inSampleSize calculateScaling( imageType, is, callbacks, bitmapPool, downsampleStrategy, degreesToRotate, sourceWidth, sourceHeight, targetWidth, targetHeight, options); //设置format 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)) { 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); } // 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); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { boolean isP3Eligible = preferredColorSpace == PreferredColorSpace.DISPLAY_P3 && options.outColorSpace != null && options.outColorSpace.isWideGamut(); options.inPreferredColorSpace = ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } //真正解析出bitmap的地方 //内部使用的是BitmapFactory.decodeStream(is, null, options); Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool); callbacks.onDecodeComplete(bitmapPool, downsampled); 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)) { //若不是复用的对象,则放入Bitmap池中,下次被recycle后可以复用 bitmapPool.put(downsampled); } } return rotated; } 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) { //一旦出错,将BitmapPool 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(); //如果在解析的过程中配置了options.inBitmap,则会放进bitmapPool池中 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; }
TransformationUtils.java
:public static Bitmap rotateImageExif( @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int exifOrientation) { if (!isExifOrientationRequired(exifOrientation)) { return inBitmap; } final Matrix matrix = new Matrix(); //配置matrix,实现旋转翻转,例如 //matrix.setRotate(-90); //matrix.postScale(-1, 1); initializeMatrixForRotation(exifOrientation, matrix); // From Bitmap.createBitmap. final RectF newRect = new RectF(0, 0, inBitmap.getWidth(), inBitmap.getHeight()); //根据matrix实现rectF重定位 matrix.mapRect(newRect); final int newWidth = Math.round(newRect.width()); final int newHeight = Math.round(newRect.height()); Bitmap.Config config = getNonNullConfig(inBitmap); Bitmap result = pool.get(newWidth, newHeight, config); matrix.postTranslate(-newRect.left, -newRect.top); result.setHasAlpha(inBitmap.hasAlpha()); applyMatrix(inBitmap, result, matrix); return result; } @VisibleForTesting static void initializeMatrixForRotation(int exifOrientation, Matrix matrix) { switch (exifOrientation) { case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: matrix.setScale(-1, 1); break; case ExifInterface.ORIENTATION_ROTATE_180: matrix.setRotate(180); break; case ExifInterface.ORIENTATION_FLIP_VERTICAL: matrix.setRotate(180); matrix.postScale(-1, 1); break; case ExifInterface.ORIENTATION_TRANSPOSE: matrix.setRotate(90); matrix.postScale(-1, 1); break; case ExifInterface.ORIENTATION_ROTATE_90: matrix.setRotate(90); break; case ExifInterface.ORIENTATION_TRANSVERSE: matrix.setRotate(-90); matrix.postScale(-1, 1); break; case ExifInterface.ORIENTATION_ROTATE_270: matrix.setRotate(-90); break; default: // Do nothing. } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nwbO33LF-1658974224925)(/Users/yuechou.zhang/Library/Application Support/typora-user-images/image-20220725181737398.png)]
解码的过程采用了责任链模式,在
Glide
的初始化过程中会默认append
对应的ResourceDecoder
,由于数量比较多,又根据GIF
,Bitmap
,BitmapDrawable
分装到map中,通俗的说,分为GIF桶,Bitmap桶,和BtimapDrawable桶根据BucketList的append顺序来决定优先以什么资源目标的类型进行
decode
public Registry() { //...忽略 setResourceDecoderBucketPriorityList( Arrays.asList(BUCKET_GIF, BUCKET_BITMAP, BUCKET_BITMAP_DRAWABLE)); } public class ResourceDecoderRegistry { @NonNull @SuppressWarnings("unchecked") 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) { //根据bucket顺序返回具体的DecoderList 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); } } } // TODO: cache result list. return result; } }
Glide
中是如何进行过变换的?比如圆角等这些。
//DecodePath
public Resource<Transcode> decode(
DataRewinder<DataType> rewinder,
int width,
int height,
@NonNull Options options,
DecodeCallback<ResourceType> callback)
throws GlideException {
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
//变换入口
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
return transcoder.transcode(transformed, options);
}
private final class DecodeCallback<Z> implements DecodePath.DecodeCallback<Z> {
private final DataSource dataSource;
@Synthetic
DecodeCallback(DataSource dataSource) {
this.dataSource = dataSource;
}
@NonNull
@Override
public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {
return DecodeJob.this.onResourceDecoded(dataSource, decoded);
}
}
@Synthetic
@NonNull
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
@SuppressWarnings("unchecked")
Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
//执行transform
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
// TODO: Make this the responsibility of the Transformation.
if (!decoded.equals(transformed)) {
//如果转换后和之前的decode对象不一致了,则将之前的decode对象放入到对象池中
decoded.recycle();
}
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
if (diskCacheStrategy.isResourceCacheable(
isFromAlternateCacheKey, dataSource, encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
switch (encodeStrategy) {
case SOURCE:
key = new DataCacheKey(currentSourceKey, signature);
break;
case TRANSFORMED:
key =
new ResourceCacheKey(
decodeHelper.getArrayPool(),
currentSourceKey,
signature,
width,
height,
appliedTransformation,
resourceSubClass,
options);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
return result;
}
Glide
中做了什么优化,在哪里体现?
- 使用参数
options.inJustDecode
来不分配对应内存,只是获取目标尺寸 - 使用
options.inBitmap和options.inMutable
来复用bitmap
- 使用大量的对象池进行cache,包括
BitmapPool
,ArrayPool
- 使用了多级缓存策略,从活动资源,内存缓存,资源磁盘缓存,最后再到网络请求,
ResourceCacheGenerator
,DataCacheGenerator
,DataCacheGenerator
获取到对应的DataFetcher
- 使用了Map<符合键Key,Value>,用于缓存使用过的解析器,解码器,转换器。
Glide
中的设计模式
对象池模式
ArrayPool
DecodeJobFactory#Pool
EngineJobFactory#Pool
工厂模式
责任链模式
@NonNull private Resource<ResourceType> decodeResourceWithList( DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException { Resource<ResourceType> result = null; //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = decoders.size(); i < size; i++) { ResourceDecoder<DataType, ResourceType> 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) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); } return result; }
Lru