本篇文章基于Glide4.11.0源码进行分析,主要是从宏观上分析Glide加载一张图片的完整流程,本篇的风格尽量减少大量源码的粘贴,对于比较重要的源码能用伪代码展示尽量使用伪代码展示,否则对待分析的代码片段进行详细的注释,不太重要的部分或者笔者觉得大家都能耳熟能详的源码会通过文字描述或者图片的方式代替。
本篇文章开始先是给出一张Glide加载图片的整体流程图,然后对源码中各个部分针对性去分析,以及对某些部分分析完后进行小小的总结,最后再详细介绍了解码逻辑以及变换流程。
1.基本执行流程
Glide.with(context).load("xxx").into(imageView);
执行上面代码会执行以下流程
2.各个模块介绍
2.1 生命周期模块
2.1.1 生命周期模块UML图
2.1.2 各个部分介绍
-
LifecycleListener
生命周期接口类,当Gldie所依附的活动生命周期发生改变时,就会回调实现
LifecycleListener接口类中的onStart(),onStop(),onDestroy()。根据UML可知,RequestManager实现了LifecycleListener,所以RequestManager可以根据生命周期变化来控制请求的暂停,恢复以及取消。 -
Lifecycle
管理生命周期接口类 即管理实现了
LifecycleListener接口的类,也就是添加和移除生命周期观察。 -
ActivityFragmentLifecycle
Lifecycle的实现类,用来统一分发生命周期,通过调用 addListener(LifecycleListener listener)将实现
LifecycleListener的实例添加到集合中,通过removeListener(LifecycleListener listener)从集合中移除,通过内部定义的onStart(),onStop(),onDestory() 遍历集合来统一分发生命周期。 -
SupportRequestManagerFragment
生命周期发射源,即当Glide在
FragmentActivity或androidx.fragment.app.Fragment(如果是androidx以前则是supportv4 包下的Fragment) 子类中加载时,会为当前活动添加一个透明的Fragment,该Fragment就是SupportRequestManagerFragment;目的是为了感知生命周期变化,用来暂停,恢复以及取消请求。 -
Request
请求类的顶层接口,其中定义了请求开始,暂停,取消等操作。
-
RequestTracker
用于跟踪、取消和重新启动的请求以及正在进行、已完成和失败的请求的类。
-
Target
所有不同方式的加载最终都通过Target#onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition),进行回调成功结果,而不同的Target有不同展示资源的方式,比如加载bitmap和加载drawable,展现方式就不同即通过setImageBitmap(resource)和setImageDrawable(resource),加载失败回调也是同理;由此可知Target设计是为了将不同资源展示到不同目标或者相同目标的一个接口设计。
其中Target的生命周期事件如下:
- onLoadStarted 开始加载时调用
- onResourceReady 资源加载成功时调用
- onLoadCleared 在取消加载并释放其资源时调用
- onLoadFailed 资源加载失败时调用
典型的生命周期是onLoadStarted ->onResourceReady或onLoadFailed ->onLoadCleared;然而,这并不能保证。如果资源在内存中,则不能调用onLoadStarted如果由于模型对象为空而导致加载失败。类似地,onLoadCleared也可能永远不会被调用。
-
RequestManager
从UML图上可知,RequestManager聚合了LifeCycle、RequestTracker、TargetTracker,以及定义了load(xxx)一些列方法,asBitmap(),asDrawable()等,说明RequestManager它的定义是为了管理Target以及Request,以及在生命周期变化时对Target和Request可以统一的进行通知,更重要的是它是调用层获取一系列api的入口即GlideBuilder。
2.1.3 生命周期模块总结
由于整个生命周期模块代码比较简单,所以这里不会对代码分析,只是说明下与该模块相关的各个类发挥的作用。整个生命周期模块其实就是从Glide.with(context)加载完毕后会创建一个透明的Fragment用来观察活动的生命周期变化,通过ActivityFragmentLifecycle将生命周期变化分发给RequestManager,而RequestManager则可以在不同的生命周期回调方法内对请求智能地停止、和重新请求。
2.2 请求初始化模块
2.2.1 请求初始化模块代码分析
请求初始化是从into函数的调用开始,第一个参数target,结合文章开头加载图片方式这里的target为DrawableImageViewTarget,targetListener这里没设置则为null,options就是调用当前into方法的实例对象,即RequestBuilder(RequestBuilder是BaseRequestOptions子类),callbackExecutor主线程执行器(封装了handler)
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
//结合文章开头加载图片方式这里的request是SingleRequest
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
//是否可以重用前一个请求
//重用条件:1:和前一个请求相同;2:前一个请求没有完成或者当前请求的操允许内存缓存
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
//如果前一个请求已经完成,则重新开始加载
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
//到这一步则说明是一个新的请求,则需要清除target绑定的旧的request
requestManager.clear(target);
//target绑定新的request
target.setRequest(request);
//将target添加到requestManager内部的targetTracker;将request添加到requestManager内部的requestTracker
//并开始执行请求
requestManager.track(target, request);
return target;
}
从上面代码分析可知,在执行请求之前会先判断是否可以重用旧的请求,如果可以重用则根据状态判断是否需要重新执行请求;如果不能重用则从target中清除旧请求绑定,然后绑定新的request,最后在requestManager#track(target, request)中执行请求。从这点可以验证前面我们在生命周期模块中描述RequestManager职责时说RequestManager 它的定义是为了管理Target以及Request。
requestManager.track(target, request) 代码如下:
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
//将target添加到targetTracker中
targetTracker.track(target);
//将request添加到requestTracker中,并执行
requestTracker.runRequest(request);
}
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
//执行请求
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
在分析into方法实现时已说明request实例为SingleRequest ,所以接下来看SingleRequest#begin()
@Override
public void begin() {
synchronized (requestLock) {
if (model == null) {
...
//url为空,加载失败
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.COMPLETE) {
//已加载完成,避免重新加载
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//如果是指定宽高,则开始加载
onSizeReady(overrideWidth, overrideHeight);
} else {
//如果不是则注册ViewTreeObserver.OnPreDrawListener监听,等获取到实际大小则再调用onSizeReady开始加载
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
//设置placeholder
target.onLoadStarted(getPlaceholderDrawable());
}
}
}
@Override
public void onSizeReady(int width, int height) {
synchronized (requestLock) {
...
//通过engine#load加载,Engine的创建是在Glide创建阶段完成,全局唯一
engine.load(xxx 省略大量参数);
...
}
}
简单说下Engine是干嘛的,它是负责启动加载以及管理活动缓存与内存缓存
执行流程如下:
- 检查当前使用的活动缓存,如果存在,则返回活动资源
- 检查内存缓存,如果存在,并提供缓存资源,并将内存缓存资源移动到活动缓存中
- 检查当前正在进行的加载,并将加载结果回调添加到正在进行的加载
- 开始新的加载。
ps:缓存相关源码在后续缓存模块会进行分析
engine#load()伪代码如下
public <R> LoadStatus load(...) {
EngineKey key =...
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
//活动缓存或者内存缓存中没有,则开始新的加载或者复用可利用的请求任务
return waitForExistingOrStartNewJob(...);
}
}
//活动缓存或者内存缓存中存在,则直接回调成功
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
那么按照Engine执行流程,前三个是已存在加载好的资源或者已经存在可利用的请求任务情况,那么这里我们直接看第四步,开始新的加载。
那么这里直接从waitForExistingOrStartNewJob开始分析
private <R> LoadStatus waitForExistingOrStartNewJob(ResourceCallback cb,
boolean onlyRetrieveFromCache,
Executor callbackExecutor,...//省略大量参数) {
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
//存在可复用的请求任务
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
//伪代码创建EngineJob<R>
EngineJob<R> engineJob =EngineJob<R>();
//伪代码创建DecodeJob并添加解码回调
DecodeJob<R> decodeJob =new DecodeJob<R>(DecodeJob.Callback<R> callback)
//缓存即将执行的任务,目的是为了复用任务
jobs.put(key, engineJob);
//为engineJob添加执行结果回调ResourceCallback与主线程执行器callbackExecutor
engineJob.addCallback(cb, callbackExecutor);
//开始执行
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
从上面代码分析可知,EngineJob它是任务执行完成回调到Target的桥梁,同时它也负责执行DecodeJob,DecodeJob是具体的任务,以及任务执行完后进行解码工作。在创建DecodeJob时设置了 DecodeJob.Callback 监听, EngineJob实现了DecodeJob.Callback,也就是请求解码完成后的结果回调给EngineJob,而创建完EngineJob后为它设置了ResourceCallback监听以及主线程执行器,也就是说从解码完成后回调给EngineJob,通过设置给EngineJob的主线程执行器,将结果在主线程中回调给ResourceCallback,这样结果从EngineJob回调到SingleRequest,从SingleRequest再回调到Target,而这里的Target为DrawableImageViewTarget,在DrawableImageViewTarget回调结果中会对ImageView进行setImageDrawable(resource)。
2.2.3 请求初始化总结
总结一下请求初始化模块所干的事情,从调用into函数开始,创建对应的Target与Request,创建完后将Target与Request进行双向绑定,然后开始执行请求,如果指定了加载的图片宽高,则开始加载;否则对ImageView进行监听,等获取到真实大小后再开始加载。加载流程如下:
- 检查当前使用的活动缓存,如果存在,则返回活动资源
- 检查内存缓存,如果存在,并提供缓存资源,并将内存缓存资源移动到活动缓存中
- 检查当前正在进行的加载,并将加载结果回调添加到正在进行的加载
- 开始新的加载。
其中第1,2,3步骤属于内存缓存的资源获取和对正在加载的请求进行监听;第4步通过EngineJob来执行DecodeJob,等执行完后回调给EngineJob,再从EngineJob->SingleRequest->DrawableImageViewTarget->setImageDrawable(resource)。
2.3 数据请求模块
数据请求模块分为从磁盘缓存中请求和网络中请求。磁盘中没有,则从网络中获取。不管哪种请求方式,最终输出都是一样,那么针对不同的请求方式的不同或相同参数输入,如何转换为相同的输出,这就是Glide需要对数据的输入与输出模型进行更抽象化的设计,即组件化设计。
2.3.1 Glide组件化设计
在创建Glide时会创建Registry对象,并将具体的输入->输出 模型注册到Registry相应的模块中。 Registry中按功能模块分为:
- ModelLoaderRegistry :数据加载模块
- EncoderRegistry:编码存储模块,提供将数据持久化存储到磁盘文件中的功能
- ResourceDecoderRegistry:解码模块,能够将各种类型数据,例如文件、byte数组等数据解码成bitmap或者drawable等资源
- ResourceEncoderRegistry:编码存储模块,提供将bitmap或者drawable等资源文件进行持久化存储的功能
- DataRewinderRegistry :数据流重定向模块,例如重定向ByteBuffer中的position或者stream中的指针位置等
- TranscoderRegistry: 转码模块,提供将不同资源类型进行转码能力,例如将bitmap转成drawable等
- ImageHeaderParserRegistry 图片头解析模块
Glide(...) {
registry = new Registry();
//...省略大量register和append
registry.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
glideContext = new GlideContext(registry,...);
}
那么这里与我们数据请求模块息息相关的模块有ModelLoaderRegistry,EncoderRegistry,ResourceDecoderRegistry,TranscoderRegistry,即获取数据和对数据进行编码缓存到磁盘上然后将编码后的产物解码,解码后进行转码最后进行展示。
整个Glide组件化设计思想都相同,这里仅以ModelLoaderRegistry和EncoderRegistry进行分析。
- ModelLoaderRegistry 分析
当调用registry.append时会代理给ModelLoaderRegistry,而ModelLoaderRegistry又代理给MultiModelLoaderFactory,所以最终append的数据在MultiModelLoaderFactory中存储
//MultiModelLoaderFactory#append
synchronized <Model, Data> void append(
@NonNull Class<Model> modelClass,
@NonNull Class<Data> dataClass,
@NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
add(modelClass, dataClass, factory, /*append=*/ true);
}
private <Model, Data> void add(
@NonNull Class<Model> modelClass,
@NonNull Class<Data> dataClass,
@NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory,
boolean append) {
Entry<Model, Data> entry = new Entry<>(modelClass, dataClass, factory);
entries.add(append ? entries.size() : 0, entry);
}
将数据封装到Entry中并保存到entries集合中,那么数据获取最终也就是从entries获取。
往Entry中封装的是什么数据?modelClass是什么,dataClass又是什么?还有factory?他们有什么用?为什么要保存它们?
这就要说到Glide在数据加载时进行抽象化的概念,
当Gldie被创建时会往ModelLoaderRegistry中添加多组数据,比如从网络中获取,从本地路径中获取,以及从assets输入流中获取,不同的输入,有不同的结果与不同的处理方式,所以这里需要告诉Glide输入的是什么类型,输出的是什么类型,以及处理方式是什么,当Glide进行加载时会对输入,输出类型匹配,来选择不同的处理方式,比如这里以网络获取资源举例:
registry.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
其中GlideUrl是对网络url的包装,那么输入就是GlideUrl,输出为InputStream,处理方式是HttpGlideUrlLoader。
那么理清组件化概念后,我们详细看下数据加载的类图关系即ModelLoader以及与它相关的类关系。
这里仅仅是以HttpGlideUrlLoader举例,不同的ModelLoader创建方式都是通过ModelLoaderFactory#build创建,创建完后通过ModelLoader#buildLoadData创建包含具体加载方式的LoadData对象,再通过LoadData中DataFetcher对数据进行加载将加载结果通过DataCallback回调给调用层。
所以到这里ModelLoaderRegistry数据加载模块分析完毕,通过多态机制,定义抽象模型,存储具体类型,在运行时根据输入,输出类型查找具体的处理模型,来完成请求到响应的过程。
- EncoderRegistry 分析
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))
在Gldie创建时会注册两个编码组件,分别针对ByteBuffer和InputStream类型的输入进行编码缓存到磁盘上。
UML图如下:
最终append的数据被保存到EncoderRegistry#encoders 集合中,获取也是遍历encoders根据输入类型选择对应的编码器,然后将输入数据写入到文件中。
2.3.2 数据请求模块源码分析
在数据加载模块中我们分析到EngineJob启动DecodeJob,DecodeJob实现了Runnable,EngineJob封装了线程池执行的方法,所以调用engineJob.start(decodeJob)时,就是往线程池提交了一个任务,DecodeJob中run()被调用。
整个run方法执行逻辑完全可以使用下面伪代码体现:
public void run() {
try {
if (isCancelled) {
GlideException e = new GlideException("Failed to load resource", new ArrayList<>(throwables));
callback.onLoadFailed(e);
return;
}
//从ResourceCacheGenerator中获取
Object data = findResourceCache();
if(data==null){
//从DataCacheGenerator中获取
data = findDataCache();
if(data==null){
//从SourceGenerator中获取
Object sourceData = requestData();
//如果可以缓存,则将数据缓存到磁盘上
if(canCache){
DataCacheGenerator.saveCache(sourceData);
data = findDataCache();
}else{
data = sourceData;
}
}
}
//解码获取的数据
decodeFromRetrievedData(data);
} catch (Throwable t) {
callback.onLoadFailed(e);
throw t;
}
}
无论从哪里获取资源,都是从ModelLoaderRegistry 模块中根据输入和输出类型匹配相应的ModelLoader,通过ModelLoader#buildLoadData创建包含具体加载方式的LoadData对象,再通过LoadData中DataFetcher对数据进行加载将加载结果通过DataCallback回调给调用层。
整个数据请求流程用文字描述如下:
在默认的缓存策略下,即diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
- 从解码变换后的磁盘缓存中获取,如果存在则返回进行解码
- 从源文件磁盘缓存中获取,如果存在则返回进行解码
- 从源头获取,获取到将数据缓存到源文件缓存中,并返回进行解码
2.4 解码与转码模块
在分析之前先要了解Glide中解码解的是什么?转码转的是什么?
在Glide创建时会注册一些解码组件和转码组件到ResourceDecoderRegistry和TranscoderRegistry中
Q1:解码解的是什么?解码后是什么?
| 具体解码器 | 描述 |
|---|---|
| ByteBufferGifDecoder | 将ByteBuffer解码为GifDrawable |
| ByteBufferBitmapDecoder | 将ByteBuffer解码为Bitmap |
| ResourceDrawableDecoder | 将资源Uri解码为Drawable |
| ResourceBitmapDecoder | 将资源ID解码为Bitmap |
| BitmapDrawableDecoder | 将数据类型解码为BitmapDrawable |
| StreamBitmapDecoder | 将InputStreams解码为Bitmap |
| StreamGifDecoder | 将InputStream数据转换为BtyeBufferr,再解码为GifDrawable |
| GifFrameResourceDecoder | 解码gif帧 |
| FileDecoder | 包装File成为FileResource |
| UnitDrawableDecoder | 将Drawable包装为DrawableResource |
| UnitBitmapDecoder | 包装Bitmap成为BitmapResource |
| VideoDecoder | 将本地视频文件解码为Bitmap |
| InputStreamBitmapImageDecoderResourceDecoder | 将InputStream解码为Bitmap(api 28) |
Q2:转码转的是什么?转码后是什么?
| 具体转码器 | 描述 |
|---|---|
| BitmapDrawableTranscoder | 将Bitmap转码为BitmapDrawable |
| BitmapBytesTranscoder | 将Bitmap转码为Byte arrays |
| DrawableBytesTranscoder | 将BitmapDrawable转码为Byte arrays |
| GifDrawableBytesTranscoder | 将GifDrawable转码为Byte arrays |
| UnitTranscoder | 不转换,输入什么,输出就是什么 |
刚才我们分析了数据请求的流程,那么最终数据获取成功后回调到DecodeJob#onDataFetcherReady中,那这个模块的分析就从DecodeJob#onDataFetcherReady开始。
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;
...
decodeFromRetrievedData();
}
private void decodeFromRetrievedData() {
//decodeFromData中最终调用decodeFromFetcher
Resource<R> resource = decodeFromData(currentFetcher, currentData, currentDataSource);
...
notifyEncodeAndRelease(resource, currentDataSource);
}
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
//获取LoadPath
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
//执行LoadPath#load()来完成解码,转码工作
return runLoadPath(data, dataSource, path);
}
由上面代码调用链可知,执行LoadPath#load(...)来完成解码工作。
先来看LoadPath的获取
public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
Class<Data> dataClass,
Class<TResource> resourceClass,
Class<Transcode> transcodeClass) {
...
//获取decodePaths
List<DecodePath<Data, TResource, Transcode>> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
//将decodePaths封装到LoadPath中并返回
return new LoadPath<>(dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool)
}
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);
//将数据类型,转码类型,解码类型,以及符合从数据类型->指定解码类型的一组解码器, 解码类型->转码类型的转码器封装到DecodePath中
DecodePath<Data, TResource, Transcode> path =
new DecodePath<>(
dataClass,
registeredResourceClass,
registeredTranscodeClass,
decoders,
transcoder,
throwableListPool);
decodePaths.add(path);
}
}
return decodePaths;
}
上面两层for循环只是为了在Glide的解码模块和转码模块中筛选出符合条件且配对的解码器和转码器,筛选出来的产物保存在LoadPath#decodePaths中
那么根据Glide.with(context).load("xxx").into(imageView);这种方式加载图片,最终会筛选出来三个DecodePath,并将每个DecodePath保存到decodePaths中,每个DecodePath内容如下:
从数据类型到解码类型再到转码类型如下:
| 数据类型 | 解码类型|解码器 | 转码类型|转码器 |
|---|---|---|
| ByteBuffer | GifDrawable|ByteBufferGifDecoder | Drawable|UnitTranscoder |
| ByteBuffer | Bitmap|ByteBufferBitmapDecoder | Drawable|BitmapDrawableTranscoder |
| ByteBuffer | BitmapDrawable|BitmapDrawableDecoder | Drawable|UnitTranscoder |
既然筛选出来符合条件的数据类型,解码类型|解码器,转码类型|转码器了,那么接下来就是应用了,也就是先将数据解码,再将解码后的产物转码,那么这里就是调用LoadPath#runLoadPath来应用筛选的结果了。
LoadPath#runLoadPath
private <Data, ResourceType> Resource<R> runLoadPath(
Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path)
throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
//数据流重定向器,例如重定向ByteBuffer中的position或者stream中的指针位置等
DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
try {
//最终调用LoadPath#loadWithExceptionList
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally {
rewinder.cleanup();
}
}
//LoadPath#loadWithExceptionList
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;
//循环遍历decodePaths中存储的DecodePath,并调用DecodePath#decode方法;即只要有一组DecodePath能处理则就结束循环,并返回结果,由于我们只是简单的调用Glide.with(context).load("xxx").into(imageView);,所以这里直接使用的是这一组
//数据类型 -> 解码类型|解码器 -> 转码类型|转码器
//ByteBuffer -> Bitmap|ByteBufferBitmapDecoder -> Drawable|BitmapDrawableTranscoder
for (int i = 0, size = decodePaths.size(); i < size; i++) {
DecodePath<Data, ResourceType, Transcode> 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<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);
//transform/变换 (这行代码最终调用DecodeJob#onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded),DecodeJob#onResourceDecoded中,完成transform,以及根据缓存策略判断是否需要缓存transform后的资源,即如果可以缓存transform后的资源,将transform后的资源缓存到ResourceCache中,后续获取时就可以从ResourceCacheGenerator中获取到)
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
//将应用transform后的资源进行转码。比如这里将transformed后的资源Resource<Bitmap>,经过转码后就是Resource<BitmapDrawable>。
return transcoder.transcode(transformed, options);
}
等转码完成,最终通过一层一层返回将转码后的资源回调到DrawableImageViewTarget。
DecodeJob->EngineJob->SingleRequest->DrawableImageViewTarget。
2.5 缓存模块
Glide 缓存分为两类,一种是内存缓存,一种是磁盘缓存。在数据请求模块我们已经分析,数据的请求先从解码变换后的磁盘缓存中获取,没获取到再从源文件磁盘缓存中获取,如果还没获取到再从源头获取。那本小节我们只分析内存缓存,内存缓存分析完后,那么整个缓存模块也就一目了然,最后通过一个缓存查找的流程描述整个Glide缓存机制。
内存缓存:
前面我们在分析请求初始化模块时说到当engine#load时先从内存缓存中查找,找不到再出发数据请求,那么这里就从engine#load开始分析
public <R> LoadStatus load(...) {
EngineKey key =...
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
//活动缓存或者内存缓存中没有,则开始新的加载或者复用可利用的请求任务
return waitForExistingOrStartNewJob(...);
}
}
//活动缓存或者内存缓存中存在,则直接回调成功
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
//从活动缓存中查找
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
return active;
}
//从内存缓存中查找
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
return cached;
}
return null;
}
整个内存缓存分为两个阶段
- 活动缓存:Map结构的弱引用存储
- 内存缓存:Lru结构的存储
活动缓存是什么?
活动缓存顾名思义就是缓存当前页面上的资源,当前页面销毁后活动缓存中的资源会被移除添加到内存缓存中,它存在的意义就是尽可能的减少列表中查找资源从磁盘缓存中查找。因为内存缓存大小有限制,使用的算法为Lru算法,旧的被淘汰,如果在一个列表中,多次来回滑动,就会造成滑出屏幕的被移除,如果此时再滑进屏幕,就需要从新去磁盘中查找。那么活动缓存这里使用Map结构的弱引用存储,防止了当前列表中旧的资源被回收,除非触发垃圾回收。减少了从磁盘缓存中查找的操作,提高了资源查找的性能。
内存缓存是什么?
内存缓存使用的是Lru结构的存储资源,那根据Lru特性,最近最少使用算法。即当缓存满时,移除最近没有使用的资源。从而避免缓存无限制的增长,从而产生OOM。
解释完活动和内存缓存的定义后那我们来看下代码实现,先从活动缓存分析
活动缓存中Map存储的是什么?
final class ActiveResources {
private final Executor monitorClearedResourcesExecutor;
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
private volatile boolean isShutdown;
...
ActiveResources(...,Executor monitorClearedResourcesExecutor) {
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
}
//将资源存储到活动缓存中
synchronized void activate(Key key, EngineResource<?> resource) {
//创建资源的弱引用包装类ResourceWeakReference
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
final Key key;
final boolean isCacheable;
Resource<?> resource;
ResourceWeakReference(
Key key,
EngineResource<?> referent,
ReferenceQueue<? super EngineResource<?>> queue,
boolean isActiveResourceRetentionAllowed) {
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource =
referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource())
: null;
isCacheable = referent.isMemoryCacheable();
}
void reset() {
resource = null;
clear();
}
}
由上面代码片段可知,它存储的是EngineResource弱引用包装类ResourceWeakReference,为什么要这样设计?为什么不直接存储EngineResource?这就要说到java引用关系,如果Map中直接存储EngineResource那就是强引用,除非主动clear(),不然垃圾回收时不会回收,所以这里采用弱引用。还有一件更有意思的在创建ResourceWeakReference时,也就是往Map中put时即调用ActiveResources#activate()时,传递了一个引用队列,这个引用队列的存在就是为了检测什么时候回收。
在ActiveResources构造方法中启动了一个检测线程,这个线程中调用了cleanReferenceQueue(),在cleanReferenceQueue()中会不停的循环,除非手动将isShutdown改为true,而在循环中resourceReferenceQueue#remove是阻塞式的,即没有元素时阻塞,有元素时会返回,那什么时候有元素呢?就是垃圾回收时,将回收的元素放入到ReferenceQueue中,此时remove返回回收的元素,然后将元素从活动缓存中移除
void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
//从资源缓存中移除,并将资源放到内存缓存中
cleanupActiveReference(ref);
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
// End for testing only.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
那么再分析完活动缓存结构后,我们再看下从活动缓存中查找缓存逻辑
private EngineResource<?> loadFromActiveResources(Key key) {
//从活动缓存中查找,查找到对资源引用计数+1,说明当前有一个地方在使用
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
//ActiveResources#get
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
//查找到如果被回收了则从map中移除,并将资源放到内存缓存中
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
如果活动缓存中未查找到,则从内存缓存中查找
private EngineResource<?> loadFromCache(Key key) {
//从Lru中查找,查找到则对资源进行引用计数+1,放到活动缓存中返回
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
那么整个内存缓存就分析完了,总结下,如果Glide中缓存策略使用默认的缓存策略,那么它获取资源的流程如下:
- 从活动缓存中查找,如果存在返回
- 从内存缓存中查找,如果存在放到活动缓存中,并返回
- 从解码变换后的磁盘缓存中查找,如果存在解码转码返回
- 从源文件磁盘缓存中查找,如果存在解码-变换-转码返回
- 从源头获取,成功后将资源缓存到源文件缓存中,解码-变换-转码返回
3 Glide解码详细分析
3.1 Bitmap基础知识
3.1.1 Bitmap内存占用计算
计算一个bitmap占用的内存有两种方式
- getByteCount()
- getAllocationByteCount()
两者区别如下:
- 两者一般情况下相等
- 通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap真实占用的内存大小。
- Android4.4(API 19)之后,通过复用解码的Bitmap,getByteCount()总是小于或等于getAllocationByteCount()
从res下不同drawable解码一张图片,占用内存计算
- targetDensity 手机像素密度
- inDensity 不同drawable下的像素密度
- 一个像素所占的内存
- ALPHA_8 -- (1B)
- RGB_565 -- (2B)
- ARGB_4444 -- (2B)
- ARGB_8888 -- (4B)
- RGBA_F16 -- (8B)
比如在一个像素密度为480手机上加载一个100*100的图片,如果使用默认编码即ARGB_8888
-
当图片放在xxhdpi下,解码出来的图片占用内存为
-
当图片放在xhdpi下,解码出来的图片占用内存为
其他 BitmapFactory.decodeXxx占用的内存为:
3.1.2 Bitmap内存模型
Android 2.3.3(API10)之前,Bitmap的像素数据存放在Native内存,而Bitmap对象本身则存放在Dalvik Heap中。而在Android3.0之后,Bitmap的像素数据也被放在了Dalvik Heap中。而在Android 8.0 之后,Bitmap的像素数据又被放到 Native 内存中。
3.1.3 Bitmap内存管理
Android 2.3.3及更低版本上,建议使用recycle()
只有当您确定位图已不再使用时才应该使用
recycle()。如果您调用recycle()并在稍后尝试绘制位图,则会收到错误:"Canvas: trying to use a recycled bitmap"。
Android 3.0及更高版本上管理内存
Android3.0(API 11之后)引入了BitmapFactory.Options.inBitmap字段,设置此字段之后解码方法会尝试复用inBitmap字段设置的Bitmap内存。这意味着Bitmap的内存被复用,避免了内存的回收及申请过程,显然性能表现更佳。不过,使用这个字段有几点限制:
inBitmap的限制
-
声明可被复用的Bitmap必须设置inMutable为true;
-
Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用
-
Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig
-
Android4.4(API 19)之后被复用的Bitmap的内存必须大于需要申请内存的Bitmap的内存
-
Android4.4(API 19)之前待加载Bitmap的Options.inSampleSize必须明确指定为1
3.1.4 Bitmap基础知识参考资料
3.2 解码详细分析
无论是加载本地图片,还是网络图片,基本都由Downsampler中5个参数的decode()完成,那么接下来会详细分析decode中的逻辑。
private Resource<Bitmap> decode(
ImageReader imageReader,//用来获取图片类型,以及方向;以及bitmap解码
int requestedWidth,//view宽
int requestedHeight,//view高
Options options,
DecodeCallbacks callbacks)
throws IOException {
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;
//获取解码格式 默认为PREFER_ARGB_8888,也可以通过builder.setDecodeFormat(DecodeFormat.PREFER_RGB_565)更改默认值
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
//获取目标色彩空间,8.0是SRGB,9.0以上可选择DISPLAY_P3,默认为SRGB
//https://juejin.cn/post/6844903865381289997
PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE);
//获取采样策略,默认为DownsampleStrategy#FIT_CENTER,取决于你ImageView设置的scaleType
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
//位图的大小与资源请求的宽度和高度是否一致,默认为false
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
//是否允许硬件位图
//https://muyangmin.github.io/glide-docs-cn/doc/hardwarebitmaps.html
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
try {
//采样和复用都在decodeFromWrappedStreams中完成
Bitmap result =
decodeFromWrappedStreams(
imageReader,
bitmapFactoryOptions,
downsampleStrategy,
decodeFormat,
preferredColorSpace,
isHardwareConfigAllowed,
requestedWidth,
requestedHeight,
fixBitmapToRequestedDimensions,
callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}
private Bitmap decodeFromWrappedStreams(
ImageReader imageReader,
BitmapFactory.Options options,
DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat,
PreferredColorSpace preferredColorSpace,
boolean isHardwareConfigAllowed,
int requestedWidth,
int requestedHeight,
boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks)
throws IOException {
//获取图片宽高
int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);
//获取图片宽
int sourceWidth = sourceDimensions[0];
//获取图片高
int sourceHeight = sourceDimensions[1];
//获取图片媒体类型 image/jpeg
String sourceMimeType = options.outMimeType;
...
//获取图片方向
int orientation = imageReader.getImageOrientation();
//获取图片旋转角度
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;
//获取图片格式 JPEG
ImageType imageType = imageReader.getImageType();
//计算设置inSampleSize,下面会详细分析
calculateScaling(
imageType,
imageReader,
callbacks,
bitmapPool,
downsampleStrategy,
degreesToRotate,
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
options);
//配置 BitmapFactory.Options inPreferredConfig参数
//inPreferredConfig说明:https://blog.csdn.net/ccpat/article/details/46834089
calculateConfig(
imageReader,
decodeFormat,
isHardwareConfigAllowed,
isExifOrientationRequired,
options,
targetWidth,
targetHeight);
boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
int expectedWidth;
int expectedHeight;
if (sourceWidth >= 0
&& sourceHeight >= 0
&& fixBitmapToRequestedDimensions
&& isKitKatOrGreater) {
expectedWidth = targetWidth;
expectedHeight = targetHeight;
} else {
//根据calculateScaling分析,isScaling(options)为false,此时 densityMultiplier=1f
float densityMultiplier =
isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;
int sampleSize = options.inSampleSize;
//downsampledHeigh=1000/2
int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
//downsampledHeigh=500=1000/2
int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
//expectedWidth=500
expectedWidth = Math.round(downsampledWidth * densityMultiplier);
//expectedWidth=500
expectedHeight = Math.round(downsampledHeight * densityMultiplier);
}
if (expectedWidth > 0 && expectedHeight > 0) {
//根据算出来的expectedWidth和expectedHeight从bitmap池中找一个可以复用的bitmap
//找出来设置给options.inBitmap参数
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
//设置目标色彩空间,8.0是SRGB,9.0以上可选择DISPLAY_P3,默认为SRGB
//https://juejin.cn/post/6844903865381289997
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 downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);
//回调进行转码
callbacks.onDecodeComplete(bitmapPool, downsampled);
...
return rotated;
}
calculateScaling 分析
private static void calculateScaling(
ImageType imageType,
ImageReader imageReader,
DecodeCallbacks decodeCallbacks,
BitmapPool bitmapPool,
DownsampleStrategy downsampleStrategy,
int degreesToRotate,
int sourceWidth,
int sourceHeight,
int targetWidth,
int targetHeight,
BitmapFactory.Options options)
throws IOException {
int orientedSourceWidth = sourceWidth;
int orientedSourceHeight = sourceHeight;
//计算缩放因子,比如图片
/**
DownsampleStrategy#FIT_CENTER
float widthPercentage = requestedWidth / (float) sourceWidth;
float heightPercentage = requestedHeight / (float) sourceHeight;
return Math.min(widthPercentage, heightPercentage);
*/
//比如图片宽为1000,高为1000,请求的宽高为500*500
//计算得出 0.5
final float exactScaleFactor =
downsampleStrategy.getScaleFactor(
orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight);
//SampleSizeRounding.QUALITY
SampleSizeRounding rounding =
downsampleStrategy.getSampleSizeRounding(
orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight);
//outWidth=0.5*1000=500
int outWidth = round(exactScaleFactor * orientedSourceWidth);
//outHeight=0.5*1000=500
int outHeight = round(exactScaleFactor * orientedSourceHeight);
//widthScaleFactor=2=1000/500
int widthScaleFactor = orientedSourceWidth / outWidth;
//heightScaleFactor=2=1000/500
int heightScaleFactor = orientedSourceHeight / outHeight;
//scaleFactor=2
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;
} else {
//powerOfTwoSampleSize=2 Integer.highestOneBit(scaleFactor)取Int二进制最高位
powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
if (rounding == SampleSizeRounding.MEMORY
&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
}
}
//inSampleSize=2
options.inSampleSize = powerOfTwoSampleSize;
int powerOfTwoWidth;
int powerOfTwoHeight;
//我们加载的图片是JPEG
if (imageType == ImageType.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.
//2
int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
//500
powerOfTwoWidth = (int) Math.ceil(orientedSourceWidth / (float) nativeScaling);
//500
powerOfTwoHeight = (int) Math.ceil(orientedSourceHeight / (float) nativeScaling);
//0
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(orientedSourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = (int) Math.floor(orientedSourceHeight / (float) powerOfTwoSampleSize);
}
...
//adjustedScaleFactor=1
double adjustedScaleFactor =
downsampleStrategy.getScaleFactor(
powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//inDensity = Integer.MAX_VALUE
options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
//inDensity = Integer.MAX_VALUE
options.inDensity = getDensityMultiplier(adjustedScaleFactor);
}
if (isScaling(options)) {
options.inScaled = true;
} else {
//由于inTargetDensity和inDensity相等则走else
options.inDensity = options.inTargetDensity = 0;
}
}
绕了一大圈,主要还是使用系统提供的inSampleSize来改变原图的尺寸以减小bitmap加载到内存的大小。
那么分析完Glide解码代码后,个人认为Glide对图片解码做了以下优化
-
inSampleSize来改变原图的尺寸以减小bitmap加载到内存的大小
-
合理的inPreferredConfig设置。 根据外部设置的inPreferredConfig,即(builder.setDecodeFormat(DecodeFormat.PREFER_RGB_565))合理判断是否使用设置的值,并不是你设置什么值,Glide就去解码什么格式。举例,比如设置RGB_565,去加载一张带透明度的图片,Glide会为你自动修改为Bitmap.Config.ARGB_8888。至于为什么请看 inPreferredConfig说明:blog.csdn.net/ccpat/artic…
-
通过inBitmap来复用已存在不使用的Bitmap,减少内存的申请与释放
-
通过ArrayPool来复用字节数组用于图片从InputStream中解码时的临时空间,用于减少内存的申请与释放
-
根据版本判断(Android8.0以上)以及是否启用硬件位图 来决定是否使用硬件位图,注意启用硬件位图,意味着bitmap不能复用。 muyangmin.github.io/glide-docs-…
是否启动硬件位图代码判断
//DecodeJob#getOptionsWithHardwareConfig private Options getOptionsWithHardwareConfig(DataSource dataSource) { Options options = this.options; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return options; } boolean isHardwareConfigSafe = dataSource == DataSource.RESOURCE_DISK_CACHE || decodeHelper.isScaleOnlyOrNoTransform(); Boolean isHardwareConfigAllowed = options.get(Downsampler.ALLOW_HARDWARE_CONFIG); // If allow hardware config is defined, we can use it if it's set to false or if it's safe to // use the hardware config for the request. if (isHardwareConfigAllowed != null && (!isHardwareConfigAllowed || isHardwareConfigSafe)) { return options; } // If allow hardware config is undefined or is set to true but it's unsafe for us to use the // hardware config for this request, we need to override the config. options = new Options(); options.putAll(this.options); options.set(Downsampler.ALLOW_HARDWARE_CONFIG, isHardwareConfigSafe); return options; }
4 transform(变换)流程分析
在前面分析Glide解码与转码模块时,当资源被解码后通过callback.onResourceDecoded(decoded);进行回调,最终回调到DecodeJob#onResourceDecoded来完成transform。那这里就详细分析下transform过程。
资源解码后进行回调代码片段
//DecodePath#decode
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);
//进行transform
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
//转码
return transcoder.transcode(transformed, options);
}
DecodeJob#onResourceDecoded代码片段
/**
DataSource 是个枚举类型表示从哪里加载资源的
具体值解释如下:
LOCAL 表示资源是从本地检索
REMOTE 从远程检索(网络)
DATA_DISK_CACHE 从源文件磁盘缓存检索
RESOURCE_DISK_CACHE 从transforme(变换)后的磁盘缓存检索
MEMORY_CACHE 从内存缓存检索
decoded 表示解码后的资源
*/
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
//如果不是从transform(变换)后的磁盘缓存检索的,则进行transform(变换)
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
//根据解码类型查找具体的变换处理类
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
//进行变换操作,返回变换后的资源
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
//解码的资源和变换后的资源不是同一个资源则回收解码的资源
if (!decoded.equals(transformed)) {
decoded.recycle();
}
//变换后的资源进行磁盘缓存时使用的编码策略
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
//根据变换后的资源判断是否有对应的编码器
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
//如果有则获取对应的编码器,如果变换后的资源是BitmapResource,那么这里编码器就是BitmapEncoder
encoder = decodeHelper.getResultEncoder(transformed);
//根据具体的编码器得到编码策略,如果是BitmapEncoder则这里得到的是EncodeStrategy.TRANSFORMED
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
//根据缓存策略判断是否可以缓存变换后的资源,默认缓存策略为DiskCacheStrategy.AUTOMATIC
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
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);
//将缓存key,以及变换后的资源和该资源对应的编码器传递给DecodeJob内部类DeferredEncodeManager,稍后将最终结果回调给EngineJob之后 完成变换后的资源的缓存工作,即,将变换后的资源缓存到ResourceCache层。
deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
return result;
}
啰嗦了这么多,重要的就这两行代码完成 变换器的查找和 解码后的资源 变换为 变换后的资源
//根据解码的资源类型查找具体的变换处理类
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
//进行变换操作,返回变换后的资源
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
变换器的查找逻辑代码就别贴了,DecodeHelper中一个为transformations的Map中查找,即根据key,找value的过程,匹配到就返回,那么transformations中存储的这些变换器在哪进行添加的呢?
通过追踪代码调用,是从RequestBuilder中一层一层传递过来的,具体添加过程是当我们调用into时,根据ImageView#scaleType判断需要添加哪些变换操作,ImageView默认的scaleType为FIT_CENTER,所以这里触发了optionalFitCenter(),经过层层调用最终调用 BaseRequestOptions#transform()
//BaseRequestOptions#transform
T transform(@NonNull Transformation<Bitmap> transformation, boolean isRequired) {
if (isAutoCloneEnabled) {
return clone().transform(transformation, isRequired);
}
//由于ImageView的scaleType为FIT_CENTER,所以这里transformation为FitCenter
DrawableTransformation drawableTransformation =
new DrawableTransformation(transformation, isRequired);
//将FitCenter添加到transformations中
transform(Bitmap.class, transformation, isRequired);
transform(Drawable.class, drawableTransformation, isRequired);
transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired);
transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);
return selfOrThrowIfLocked();
}
根据上面代码分析,当调用into时,Glide自动注册了4个transform。
回到刚才查找变换器入口代码
//根据解码的资源类型查找具体的变换处理类
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
假如这里resourceSubClass类型为Bitmap,那么查找到的就是FitCenter,那么变换就是调用FitCenter#transform。
FitCenter继承体系如下
如果你只需要变换
Bitmap,最好是从继承BitmapTransformation。BitmapTransformation为我们处理了一些基础的东西,例如,如果你的变换返回了一个新修改的 Bitmap ,BitmapTransformation将负责提取和回收原始的 Bitmap
具体如何自定义Transformation,请参考Glide中文文档: