今天整篇就围绕一个数据后获取阶段,所谓后获取指的是数据从网络请求成功回本地后到转换成所需的数据类型过程,总结出来就是两个问题:
- Glide的数据后获取阶段的流程?
- Glide的编解码阶段流程?
1.数据后获取阶段流程
在SourceGenerator
中,下载成功后会把数据存在本地再从本地读取,再回忆下这个过程。刚开始run1和run2dataToCache和sourceCacheGenerator
为空,会走run3获取数据,成功请求后回调run4,给dataToCache
赋值,因为状态没变,所以DecodeJob
还是会调用SourceGenerator
,现在会走run1,接着往后走。
// SourceGenerator.java
@Override
public boolean startNext() {
// run 1
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
// run 2
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
// run 3
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// run 4
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
在run1
里面会调用cacheData
, 会从DecodeHelper
中获取一个编码器,把数据通过编码器写到本地文件中,文件缓存通过DiskCache
管理,然后会给sourceCacheGenerator
赋值,它其实是一个DataCacheGenerator
类型,负责从本地加载数据。接着上面就走到run2, 从本地加载数据然后返回true.
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder
很明显,接下来执行到DataCacheGenerator
中的startNext
方法,在run1中根据File
这种类型从注册的ModelRegistry
中取出对应的ModelLoader
,取出来的可能会有多个,然后循环遍历这些ModelLoader
,在run2中会构造出DataFetcher
,run3判断拿到的DataFetcher
转换后的数据类型是否有对应的Decoder
,如果有就用这个DataFetcher
加载从缓存拿到的cacheFile
文件。
// DataCacheGenerator.java
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
// run 1
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
// run 2
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
// run 3
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
看下对应的File,会取到哪些ModelLoader
,会取到有四个,分别是ByteBufferFileLoader,FileLoader,UnitModelLoader
,其中两个FileLoader分别对应InputStream,FileDescriptor
。
上面的循环会先取到第一个ByteBufferFileLoader
,然后用ByteBufferFileFetcher
去加载数据,看下这个类,它是ByteBufferFileLoader
的内部类, 它的DataSource
是DataSource.LOCAL
,表示从数据来源是本地, 取到的数据类型是ByteBuffer.class
, 在loadData
中通过NIO内存映射读入文件。
// ByteBufferFileLoader.java
private static final class ByteBufferFetcher implements DataFetcher {
private final File file;
@Synthetic
@SuppressWarnings("WeakerAccess")
ByteBufferFetcher(File file) {
this.file = file;
}
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback super ByteBuffer> callback) {
ByteBuffer result;
try {
result = ByteBufferUtil.fromFile(file);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
}
callback.onLoadFailed(e);
return;
}
callback.onDataReady(result);
}
...
@NonNull
@Override
public Class getDataClass() {
return ByteBuffer.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}
文件成功映射后回调到DataCacheGenerator
, 在这里是简单的调用回调函数,到这里数据来源变成了DataSource.DATA_DISK_CACHE
,意思是已经做了映射,但是未做修改的原始数据。这里的cb
是在SourceGenerator
中传过来的。
// DataCacheGenerator.java
@Override
public void onDataReady(Object data) {
cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
}
在SourceGenerator
接口中只是简单的再调用它上一级传给它的cb
,也就是DecodeJob
, 到了这里就是赋值一个关键变量,data就是上面的ByteBufferFileFetcher
取到的ByteBuffer
数据,然后会调用decodeFromRetrievedData
进行数据解码。
// DecodeJob.java
@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 {
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}
在decodeFromRetrievedData
中会一路调用,来到decodeFromFetcher
, 会根据Data的类型ByteBuffer
从DecodeHelper
中获取LoadPath
,然后runLoadPath
。
// DecodeJob.java
private Resource decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());
return runLoadPath(data, dataSource, path);
}
private Resource runLoadPath(Data data, DataSource dataSource,
LoadPath path) throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
DataRewinder rewinder = glideContext.getRegistry().getRewinder(data);
try {
// ResourceType in DecodeCallback below is required for compilation to work with gradle.
return path.load(
rewinder, options, width, height, new DecodeCallback(dataSource));
} finally {
rewinder.cleanup();
}
}
后面经过解码和转码得到了数据,会notifyEncodeAndRelease
通知到上层,调用到notifyComplete
回调到EngineJob
:
// DecodeJob.java
private void decodeFromRetrievedData() {
...
Resource resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
if (resource instanceof Initializable) {
((Initializable) resource).initialize();
}
...
notifyComplete(result, dataSource);
...
}
private void notifyComplete(Resource resource, DataSource dataSource) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource);
}
在EngineJob
中,会给主线程发送MSG_COMPLETE
消息,回调到主线程执行handleResultOnMainThread
方法,在run 1中会回调onEngineJobComplete
,其中listener是Engine
。接着在run2中通知监听者Request
,在Request
里面调用Target
的onResourceReady
:
// EngineJob.java
@Override
public void onResourceReady(Resource resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
public boolean handleMessage(Message message) {
EngineJob> job = (EngineJob>) message.obj;
switch (message.what) {
case MSG_COMPLETE:
job.handleResultOnMainThread();
break;
...
default:
throw new IllegalStateException("Unrecognized message: " + message.what);
}
return true;
}
void handleResultOnMainThread() {
...
engineResource.acquire();
// run 1
listener.onEngineJobComplete(this, key, engineResource);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
// run 2
cb.onResourceReady(engineResource, dataSource);
}
}
// Our request is complete, so we can release the resource.
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
上面调到的onEngineJobComplete
是在Engine
中,这里会先把资源放到active resources
这一层内存缓存中。注册一个resourcelistener
,在资源释放的时候回调,把资源从active resource
中移除,挪到cache
这一层的内存缓存中:
// Engine
public void onEngineJobComplete(EngineJob> engineJob, Key key, EngineResource> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
public void onResourceReleased(Key cacheKey, EngineResource> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
2.LoadPath
所以看下LoadPath
的源码,它负责把获取到的ByteBuffer
放到一个个的DecodePath
中进行解码转换,有三个泛型,其中Data类型是获取到的数据类型(比如ByteBuffer, InputStream), ResourceType是中间类型(可能有Gif/Bitmap),Transcode就是最后返回给应用层的最终类型(可能有Drawable)等
// LoadPath.java
/**
* For a given {@link com.bumptech.glide.load.data.DataFetcher} for a given data class, attempts to
* fetch the data and then run it through one or more
* {@link com.bumptech.glide.load.engine.DecodePath}s.
*
* @param The type of data that will be fetched.
* @param The type of intermediate resource that will be decoded within one of the
* {@link com.bumptech.glide.load.engine.DecodePath}s.
* @param The type of resource that will be returned as the result if the load and
* one of the decode paths succeeds.
*/
public class LoadPath {
private final Class dataClass;
private final Pool> listPool;
private final List extends DecodePath> decodePaths;
private final String failureMessage;
...
}
知道了什么是LoadPath
后再返回到前面的decodeFromFetcher
中,看下LoadPath
是怎么拿到的,先从缓存取如果没有会从新构造,构造需要有一个参数DecodePath
,会从decoderRegistry和transcoderRegistry
分别获取ResourceDecoder和ResourceTranscoder
,分别用于解码和转码。
// DecodeHelper.java
LoadPath getLoadPath(Class dataClass) {
return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
}
// Registry.java
public LoadPath getLoadPath(
@NonNull Class dataClass, @NonNull Class resourceClass,
@NonNull Class transcodeClass) {
LoadPath result =
loadPathCache.get(dataClass, resourceClass, transcodeClass);
if (loadPathCache.isEmptyLoadPath(result)) {
return null;
} else if (result == null) {
List> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
if (decodePaths.isEmpty()) {
result = null;
} else {
result =
new LoadPath<>(
dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
}
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}
private List> getDecodePaths(
@NonNull Class dataClass, @NonNull Class resourceClass,
@NonNull Class transcodeClass) {
List> decodePaths = new ArrayList<>();
List> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);
for (Class registeredResourceClass : registeredResourceClasses) {
List> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
for (Class registeredTranscodeClass : registeredTranscodeClasses) {
List> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);
ResourceTranscoder transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
DecodePath path =
new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
decoders, transcoder, throwableListPool);
decodePaths.add(path);
}
}
return decodePaths;
}
看下调试过程中有哪些具体的ResourceClasses和TranscodeClasses
,可以看到ResourceClasses
有三个
- GifDrawable
- Bitmap
- BitmapDrawable
TranscodeClasses
有一个Drawable
,也就是最后返回给应用层的会是Drawable
.
构造LoadPath
需要一个很重要的参数DecodePath
,接下来看看它。
3.DecodePath
再看看DecodePath
的源码, 三个泛型和前面的LoadPath
是一样的。解码的时候回调用decode
方法,这个方法完成下面三件事:
- 调用
decodeResource
完成数据的解码,得到decoded
- 解码完成后调用回调的
onResourceDecoded
,在回调里面回做一些比如图片的变换,我们设置的transform
就是在这里起作用 - 对上面解码得到的
decoded
进行转码,返回转码成功的资源
public class DecodePath {
private static final String TAG = "DecodePath";
private final Class dataClass;
private final List extends ResourceDecoder> decoders;
private final ResourceTranscoder transcoder;
private final Pool> listPool;
private final String failureMessage;
public DecodePath(Class dataClass, Class resourceClass,
Class transcodeClass,
List extends ResourceDecoder> decoders,
ResourceTranscoder transcoder, Pool> listPool) {
this.dataClass = dataClass;
this.decoders = decoders;
this.transcoder = transcoder;
this.listPool = listPool;
failureMessage = "Failed DecodePath{" + dataClass.getSimpleName() + "->"
+ resourceClass.getSimpleName() + "->" + transcodeClass.getSimpleName() + "}";
}
public Resource decode(DataRewinder rewinder, int width, int height,
@NonNull Options options, DecodeCallback callback) throws GlideException {
Resource decoded = decodeResource(rewinder, width, height, options);
Resource transformed = callback.onResourceDecoded(decoded);
return transcoder.transcode(transformed, options);
}
@NonNull
private Resource decodeResource(DataRewinder rewinder, int width,
int height, @NonNull Options options) throws GlideException {
List exceptions = Preconditions.checkNotNull(listPool.acquire());
try {
return decodeResourceWithList(rewinder, width, height, options, exceptions);
} finally {
listPool.release(exceptions);
}
}
@NonNull
private Resource decodeResourceWithList(DataRewinder rewinder, int width,
int height, @NonNull Options options, List exceptions) throws GlideException {
Resource result = null;
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = decoders.size(); i < size; i++) {
ResourceDecoder decoder = decoders.get(i);
try {
DataType data = rewinder.rewindAndGet();
if (decoder.handles(data, options)) {
data = rewinder.rewindAndGet();
result = decoder.decode(data, width, height, options);
}
if (result != null) {
break;
}
}
return result;
}
...
interface DecodeCallback {
@NonNull
Resource onResourceDecoded(@NonNull Resource resource);
}
}
看一个具体的DecodePath
的例子,从failureMessage中可以看到三个泛型分别是DirectByteBuffer->Bitmap->Drawable
.同时资源也有设置宽高等属性。
再返回到前面获取LoadPath
的过程中。
4.解码
看下最终根据数据类型获取到的DecodePath
有三个:
- dataClass: DirectByteBuffer, decoder: GifDecoder, transcoder: UnitTranscoder
- dataClass: DirectByteBuffer, decoder: ByteBufferBitmapDecoder, transcoder: BitmapDrawableTranscoder
- dataClass: DirectByteBuffer, decoder: BitmapDrawableDecoder, transcoder: UnitTranscoder
很明显我们这里会是第二个起作用。看下ByteBufferBitmapDecoder
中的源码,通过Downsampler
进行对ByteBuffer
进行解码成Bitmap,Downsampler
里面根据采样率、图片大小等构造一个Bitmap返回。
/**
* Decodes {@link android.graphics.Bitmap Bitmaps} from {@link java.nio.ByteBuffer ByteBuffers}.
*/
public class ByteBufferBitmapDecoder implements ResourceDecoder {
private final Downsampler downsampler;
public ByteBufferBitmapDecoder(Downsampler downsampler) {
this.downsampler = downsampler;
}
@Override
public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
return downsampler.handles(source);
}
@Override
public Resource 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);
}
}
拿到Resource
后会通过BitmapDrawableTranscoder
进行转码,返回Resource
:
// BitmapDrawableTranscoder.java
/**
* An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts {@link
* android.graphics.Bitmap}s into {@link android.graphics.drawable.BitmapDrawable}s.
*/
public class BitmapDrawableTranscoder implements ResourceTranscoder {
private final Resources resources;
// Public API.
@SuppressWarnings("unused")
public BitmapDrawableTranscoder(@NonNull Context context) {
this(context.getResources());
}
/**
* @deprecated Use {@link #BitmapDrawableTranscoder(Resources)}, {@code bitmapPool} is unused.
*/
@Deprecated
public BitmapDrawableTranscoder(
@NonNull Resources resources, @SuppressWarnings("unused") BitmapPool bitmapPool) {
this(resources);
}
public BitmapDrawableTranscoder(@NonNull Resources resources) {
this.resources = Preconditions.checkNotNull(resources);
}
@Nullable
@Override
public Resource transcode(@NonNull Resource toTranscode,
@NonNull Options options) {
return LazyBitmapDrawableResource.obtain(resources, toTranscode);
}
}
最后是通过LazyBitmapDrawableResource.obtain
返回的数据, 这个可以实现懒加载,只有在真正调用get
方法的时候才会分配BitmapDrawable
:
// LazyBitmapDrawableResource.java
/**
* Lazily allocates a {@link android.graphics.drawable.BitmapDrawable} from a given
* {@link android.graphics.Bitmap} on the first call to {@link #get()}.
*/
public final class LazyBitmapDrawableResource implements Resource,
Initializable {
private final Resources resources;
private final Resource bitmapResource;
...
@Nullable
public static Resource obtain(
@NonNull Resources resources, @Nullable Resource bitmapResource) {
if (bitmapResource == null) {
return null;
}
return new LazyBitmapDrawableResource(resources, bitmapResource);
}
private LazyBitmapDrawableResource(@NonNull Resources resources,
@NonNull Resource bitmapResource) {
this.resources = Preconditions.checkNotNull(resources);
this.bitmapResource = Preconditions.checkNotNull(bitmapResource);
}
@NonNull
@Override
public Class getResourceClass() {
return BitmapDrawable.class;
}
@NonNull
@Override
public BitmapDrawable get() {
return new BitmapDrawable(resources, bitmapResource.get());
}
@Override
public int getSize() {
return bitmapResource.getSize();
}
@Override
public void recycle() {
bitmapResource.recycle();
}
@Override
public void initialize() {
if (bitmapResource instanceof Initializable) {
((Initializable) bitmapResource).initialize();
}
}
}
5.总结
Glide在数据请求成功后如果能允许缓存,就会缓存完成后通过读取本地数据,然后完成解码和转码返回给应用层,也是基于门面模式,所以支持类型的转接码都会在Glide初始化的时候注册到对应的Registry
中,数据下载成功后根据数据类型从Registry
组装LoadPath和DecodePath
,完成解码转码工作。