前言
前面一篇文章对Glide
第一次加载网络图片的流程通过源码的方式一步步的带大家看了下,至少对Glide的整个框架有了一个大致的认识。这篇文章我们继续深入。
概要
这一篇主要介绍下,第一次加载图片缓存磁盘后,重新启动app,再次加载的时候,从磁盘读取的过程。中间还会涉及到Glide
其他的一些源码内容。
正文
从磁盘加载文件,那么肯定就有把图片存起来的这个步骤。我们先来看看Glide
是如何把图片存起来的。
上篇文章已经简单的介绍了,就是图片加载成功之后重新又调用了SourceGenerator
的startNext
方法。
public boolean startNext() { if (dataToCache != null) { Object data = dataToCache; dataToCache = null; cacheData(data); }... }复制代码
重点就是看下cacheData
这个方法,如何去缓存图片。上一篇文章,重在流程,所以一笔带过了,这次我们就继续深入。
private void cacheData(Object dataToCache) { long startTime = LogTime.getLogTime(); try { Encoder
其实最主要的就是这4行代码,我们一个个来解析。
- 获取编码器
Encoder getSourceEncoder(X data) throws Registry.NoSourceEncoderAvailableException { return glideContext.getRegistry().getSourceEncoder(data); }...public Encoder getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException { Encoder encoder = encoderRegistry.getEncoder((Class ) data.getClass()); if (encoder != null) { return encoder; } throw new NoSourceEncoderAvailableException(data.getClass()); }...public synchronized Encoder getEncoder(@NonNull Class dataClass) { for (Entry entry : encoders) { if (entry.handles(dataClass)) { return (Encoder ) entry.encoder; } } return null; }复制代码
一步步跟入,就是这3个方法,总结来说,就是一开始的时候在EncoderRegistry
对象中注册多个编码器,通过key-value
的形式保存起来,可以通过传入的数据类型来获取到自己对应的编码器。
这里就直接说下结果。前面网络请求返回对象是ContentLengthInputStream
类型的。
自己debug一下,就会比较清楚,encoders里面注册了2个Encoder,分别用来解析ByteBuffer
和InputStream
。
很显然,这里返回了StreamEncoder
来处理。
-
创建
DataCacheWriter
把第一步获取到的StreamEncoder
当作参数传入。只是创建个对象,没什么好说的。 -
创建缓存的DataCacheKey key-value的方式缓存数据,这里的key就是
DataCacheKey
。 -
缓存数据 这里就是重点了,开始进行磁盘缓存数据。
helper.getDiskCache().put(originalKey, writer);复制代码
首先我们要知道helper.getDiskCache()
获取到的对象是什么。因为三方框架中会使用很多接口,所以我们有时间直接看代码并不能马上知道具体对应是哪个实现。 这里有2个办法。
- debug跟入直接看
- 不停往前找,用
ctrl+左键
一直往前找引用,找赋值的地方
这里我就不带大家这样一步步找了,直接看关键的几个地方。 DecodeHelper->diskCacheProvider
=> DecodeJob->diskCacheProvider
=> DecodeJobFactory->diskCacheProvider
=> Engine->diskCacheProvider
这里就发现了
this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);复制代码
但是这里又有一个参数diskCacheFactory
我们还需要看这个是那里来的,继续往前找 Engine->diskCacheFactory
=> GlideBuilder->diskCacheFactory
这里发现了
if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); }复制代码
在构建Glide
对象的时候如果没有传入diskCacheFactory
,那么这里就会默认生成一个InternalCacheDiskCacheFactory
。
好了这样我们就可以重新再来看下
helper.getDiskCache().put(originalKey, writer);复制代码
其实就是
helper.getDiskCache().put(originalKey, writer);...DiskCache getDiskCache() { return diskCacheProvider.getDiskCache(); }... @Override public DiskCache getDiskCache() { if (diskCache == null) { synchronized (this) { if (diskCache == null) { diskCache = factory.build(); } if (diskCache == null) { diskCache = new DiskCacheAdapter(); } } } return diskCache; }复制代码
前面我们已经一鼓作气,把factory
也找出来了,也就是InternalCacheDiskCacheFactory
。
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory//build方法在DiskLruCacheFactory里面public DiskCache build() { File cacheDir = cacheDirectoryGetter.getCacheDirectory(); if (cacheDir == null) { return null; } if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) { return null; } return DiskLruCacheWrapper.create(cacheDir, diskCacheSize); }复制代码
这里就是创建缓存的文件。
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory { public InternalCacheDiskCacheFactory(Context context) { this(context, "image_manager_disk_cache", 262144000L); } public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) { this(context, "image_manager_disk_cache", diskCacheSize); } public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName, long diskCacheSize) { super(new CacheDirectoryGetter() { public File getCacheDirectory() { File cacheDirectory = context.getCacheDir(); if (cacheDirectory == null) { return null; } else { return diskCacheName != null ? new File(cacheDirectory, diskCacheName) : cacheDirectory; } } }, diskCacheSize); }}复制代码
在InternalCacheDiskCacheFactory
可以看出图片缓存的文件路径。
就是在context.getCacheDir()
里面的image_manager_disk_cache
文件夹。 我们可以发现,我这个设备里面已经缓存了一个文件。
如果说,你不想存在这个文件,你就自定义一个DiskLruCacheFactory
在前面我们会发现最后在build方法中创建的是
return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);复制代码
一个DiskLruCacheWrapper
对象,所以前面缓存的时候也是调用了DiskLruCacheWrapper
的put
方法。
public void put(Key key, Writer writer) { DiskLruCache diskCache = getDiskCache(); Value current = diskCache.get(safeKey); if (current != null) { return; } ... DiskLruCache.Editor editor = diskCache.edit(safeKey); ... File file = editor.getFile(0); if (writer.write(file)) { editor.commit(); } ... } finally { writeLocker.release(safeKey); } }...//writer.writepublic boolean write(@NonNull File file) { return encoder.encode(data, file, options); }复制代码
DiskLrcCache
具体的逻辑这里先不介绍,只要知道了是通过key-value
缓存到了本地。后面可以直接通过key获取到缓存的数据。
接下来,我们尝试下,重启App,让Glide
从磁盘加载图片。前面的步骤肯定都是一样的。我们就直接从DecodeJob
的run
方法开始看。
public void run() { ... runWrapped(); ... } private void runWrapped() { switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; ... } }private void runGenerators() { ... while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { reschedule(); return; } } ... }复制代码
之前是先在ResourceGenerator
,DataCacheGenerator
里面去找里面的loadData
能不能处理这个请求。 在第一次加载网络图片的时候,前面2个都不能处理。但是经过了前面的磁盘缓存后。我们再进入DataCacheGenerator
来看下里面的逻辑。
public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader()) { sourceIdIndex++; if (sourceIdIndex >= cacheKeys.size()) { return false; } Key sourceId = cacheKeys.get(sourceIdIndex); Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); cacheFile = helper.getDiskCache().get(originalKey); if (cacheFile != null) { this.sourceKey = sourceId; modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } } loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { ModelLoadermodelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions()); if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }复制代码
简单的来讲就是创建了一个DataCacheKey,然后helper.getDiskCache().get
获取到缓存的数据。然后再交给对应的loadData
去处理。
这里值得注意的是cacheKeys
,modelLoaders
我们先来看cacheKeys
,一步步往上找 DataCacheGenerator->cacheKeys
=> DecodeHelper->cacheKeys
最终是这里创建的,看下代码
ListgetCacheKeys() { ... List > loadData = getLoadData(); for (int i = 0, size = loadData.size(); i < size; i++) { LoadData data = loadData.get(i); if (!cacheKeys.contains(data.sourceKey)) { cacheKeys.add(data.sourceKey); } ... } } } return cacheKeys; }复制代码
cacheKeys
保存的其实是LoadData
的sourceKey
. 这里看下List<LoadData<?>>
是如何获取到的
List> getLoadData() { ... List > modelLoaders = glideContext.getRegistry().getModelLoaders(model); for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader modelLoader = modelLoaders.get(i); LoadData current = modelLoader.buildLoadData(model, width, height, options); if (current != null) { loadData.add(current); } } } return loadData; }复制代码
LoadData
其实ModelLoader. buildLoadData
生成的,所以,我们就继续往下看ModelLoader
是哪里来的。
List> modelLoaders = glideContext.getRegistry().getModelLoaders(model);//Registry.javapublic List > getModelLoaders(@NonNull Model model) { List > result = modelLoaderRegistry.getModelLoaders(model); ... return result; }//ModelLoadRegistry.javapublic List > getModelLoaders(@NonNull A model) { List List> modelLoaders = getModelLoadersForClass(getClass(model)); ... }//ModelLoadRegistry.javaprivate synchronized > getModelLoadersForClass( @NonNull Class modelClass) { List> loaders = cache.get(modelClass); if (loaders == null) { loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass)); cache.put(modelClass, loaders); } return loaders; }//MultiModelLoaderFactory.javasynchronized List > build(@NonNull Class modelClass) { try { List > loaders = new ArrayList<>(); for (Entry entry : entries) { if (alreadyUsedEntries.contains(entry)) { continue; } if (entry.handles(modelClass)) { alreadyUsedEntries.add(entry); loaders.add(this. build(entry)); alreadyUsedEntries.remove(entry); } } return loaders; } catch (Throwable t) { alreadyUsedEntries.clear(); throw t; } }复制代码
路径很深,大家可以直接看MultiModelLoaderFactory.build
方法. 值得注意的是这里有个变量是entries
,我们看看下是什么东西。
private final List> entries = new ArrayList<>();复制代码
我们使用ctrl+鼠标左键
找下引用的地方,看看哪里往里面添加东西了,添加了什么东西。
privatevoid add( @NonNull Class modelClass, @NonNull Class dataClass, @NonNull ModelLoaderFactory factory, boolean append) { Entry entry = new Entry<>(modelClass, dataClass, factory); entries.add(append ? entries.size() : 0, entry); }复制代码
再ctrl+鼠标左键
,看哪里调用了add
,一步步往上找。
//MultiModelLoaderFactory.javasynchronizedvoid append( @NonNull Class modelClass, @NonNull Class dataClass, @NonNull ModelLoaderFactory factory) { add(modelClass, dataClass, factory, /*append=*/ true); }//ModelLoaderRegistry.java public synchronized void append( @NonNull Class modelClass, @NonNull Class dataClass, @NonNull ModelLoaderFactory factory) { multiModelLoaderFactory.append(modelClass, dataClass, factory); cache.clear(); }//Registry.java public Registry append( @NonNull Class modelClass, @NonNull Class dataClass, @NonNull ModelLoaderFactory factory) { modelLoaderRegistry.append(modelClass, dataClass, factory); return this; }复制代码
最终找到,调用的地方是Glide
的构造方法。
由于方法实在是太长了,这里就直接贴出图片。
我们再来看下方法,注意下参数名。
publicRegistry append(Class modelClass, Class dataClass, ModelLoaderFactory factory) { modelLoaderRegistry.append(modelClass, dataClass, factory); return this; }复制代码
modelClass->传入的数据class,这里我们传入的是http字符串,也就是String.class
dataClass->处理后得到的数据class,前面文章介绍了第一次加载图片得到的其实是InputStream
的子类
factory->创建处理传入数据是modelClass这种类型,得到数据是dataClass这种类型的处理器的工厂
我们继续回过头看下
synchronizedList > build(@NonNull Class modelClass) { try { List > loaders = new ArrayList<>(); for (Entry entry : entries) { ... if (alreadyUsedEntries.contains(entry)) { continue; } if (entry.handles(modelClass)) { alreadyUsedEntries.add(entry); loaders.add(this. build(entry)); alreadyUsedEntries.remove(entry); } } return loaders; } catch (Throwable t) { alreadyUsedEntries.clear(); throw t; } }复制代码
这里就看起来清晰多了,先调用entry.handles(modelClass)
看看这个entry
能不能处理modelClass
这个类型的请求,如果可以就调用build
方法构建一个ModelLoader
我们一步步来,先看handles
方法
public boolean handles(@NonNull Class modelClass) { return this.modelClass.isAssignableFrom(modelClass); }复制代码
非常简单,就看modelClass是不是之前注册的modelClass
的子类。
那么继续看build
方法。
privateModelLoader build(@NonNull Entry entry) { return (ModelLoader ) Preconditions.checkNotNull(entry.factory.build(this)); }复制代码
其实就是调用entry.factory.build(this)
。前面已经介绍过了,factory其实就是前面注册的第三个参数。那么我们就可以看看之前,modelClass为String的对应的几个ModelLoaderFactory
根据前面的介绍是在Glide
构造函数中添加的。我们就看下具体是哪几个。
.append(String.class, InputStream.class, new DataUrlLoader.StreamFactory()).append(String.class, InputStream.class, new StringLoader.StreamFactory()).append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory()).append(String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())复制代码
所以得到的loaders
就是4个上面的factory
调用build
之后所创建的ModelLoader
。这里就以StringLoader.StreamFactory.build
方法为例子介绍下.
public ModelLoaderbuild( @NonNull MultiModelLoaderFactory multiFactory) { return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class)); }复制代码
这里就有个比较巧妙的地方,继续调用了multiFactory
也就是MultiModelLoaderFactory
的build
方法,但是这次和前面不一样,这里传了2个参数,Uri.class
对应modelClass
,InputStream
对应dataClass
,举一反三。前面已经介绍了传了一个参数,就是找到前面注册的modelClass为String.class的factory
。这里也是一样,只不过这里要找到modelClass为Uri.classs,dataClass为InputStream的factory
。然后使用factory构建出对应的ModelLoader
。
这里我们只需要知道,前面创建的StringLoader,内部有一个参数是uriLoader
,而这个uriLoader
就是处理modelClass为Uri,dataClass为InputStream的ModelLoader
。那么我们可以在Glide
的构造方法内找一下对应的factory。
.append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory()).append(Uri.class, InputStream.class, new HttpUriLoader.Factory()).append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets())).append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context)).append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context)).append(Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver)) ...复制代码
有点多,我就不一一写出了。
接下来就往前面看。
//ModelLoaderRegistry.javapublic List> getModelLoaders(@NonNull A model) { //这里获取到了4个 List > modelLoaders = getModelLoadersForClass(getClass(model)); int size = modelLoaders.size(); boolean isEmpty = true; List > filteredLoaders = Collections.emptyList(); for (int i = 0; i < size; i++) { ModelLoader loader = modelLoaders.get(i); //这里过滤掉了一个 if (loader.handles(model)) { if (isEmpty) { filteredLoaders = new ArrayList<>(size - i); isEmpty = false; } filteredLoaders.add(loader); } } return filteredLoaders; }复制代码
大家可以注意下上面的注释,就是loader.handlers
过滤了一个ModelLoader
,这里就直接说了,过滤了DataUrlLoader
public boolean handles(@NonNull Model model) { return model.toString().startsWith(DATA_SCHEME_IMAGE); }复制代码
显然我们传入的字符串不是以这个为开头,所以为false。 所以最后传回去的是3个ModelLoader。继续往前看。
List> getLoadData() { if (!isLoadDataSet) { isLoadDataSet = true; loadData.clear(); //这边就是前面得到的3个ModelLoader List > modelLoaders = glideContext.getRegistry().getModelLoaders(model); for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader modelLoader = modelLoaders.get(i); LoadData current = modelLoader.buildLoadData(model, width, height, options); if (current != null) { loadData.add(current); } } } return loadData; }复制代码
前面其实已经介绍过了,返回的其实是3个StringLoader
,只不过是里面的uriLoader
不太一样罢了。
public LoadData buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) { Uri uri = parseUri(model); if (uri == null || !uriLoader.handles(uri)) { return null; } return uriLoader.buildLoadData(uri, width, height, options); }...//MultiModelLoader.javapublic boolean handles(@NonNull Model model) { for (ModelLoadermodelLoader : modelLoaders) { if (modelLoader.handles(model)) { return true; } } return false; }复制代码
uriLoader
是一个MultiModelLoader
,其实也是遍历一下,看看MultiModelLoader
内部的ModelLoader
能不能处理。
//HttpUriLoader
private static final SetSCHEMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http", "https")));public boolean handles(@NonNull Uri model) { return SCHEMES.contains(model.getScheme()); }复制代码
找到HttpUriLoader
能够处理。然后调用HttpUriLoader.buildLoaderData
public LoadDatabuildLoadData(@NonNull Uri model, int width, int height, @NonNull Options options) { return urlLoader.buildLoadData(new GlideUrl(model.toString()), width, height, options); }复制代码
这里很奇怪,这里又来了一个urlLoader
是什么东西。 创建HttpUriLoader
的时候是根据一个factory
创建的,
public static class Factory implements ModelLoaderFactory{... public ModelLoader build(MultiModelLoaderFactory multiFactory) { return new HttpUriLoader(multiFactory.build(GlideUrl.class, InputStream.class)); }... }复制代码
这段代码感觉又非常熟悉,跟前面很像,只不过这里的modelClass
是GlideUrl
,dataClass
是InputStream
,我们在Glide
构造方法里面找一下
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())复制代码
就这一个。 所以刚才urlLoader
其实就是由HttpGlideUrlLoader.Factory()
构建的HttpGlideUrlLoader
。那么我们来看下HttpGlideUrlLoader.buildLoadData
public LoadDatabuildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {GlideUrl url = model; ... return new LoadData<>(url, new HttpUrlFetcher(url, timeout)); }复制代码
HttpUrlFetcher
其实就是真正发起http
请求获取数据的fetcher
。这里就不在深入了。
这里我们重新再看下这行代码
urlLoader.buildLoadData(new GlideUrl(model.toString()), width, height, options)复制代码
我们可以看下GlideUrl
public class GlideUrl implements Key复制代码
它实现了Key
接口。所以前面获取DiskCacheKey
传入的参数其实就是GlideUrl
。那么我们就重新再回到最前面。DataCacheGenerator
public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader()) { ... Key sourceId = cacheKeys.get(sourceIdIndex); ... Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); cacheFile = helper.getDiskCache().get(originalKey); if (cacheFile != null) { this.sourceKey = sourceId; modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } } loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { ModelLoadermodelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions()); if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }复制代码
经过前面介绍如何获取到ModelLoader
已经Key
是什么之后,再来看下这段代码,就会发现有不太一样的认识。
cacheFile
这里已经不为null。 然后继续往下,从helper.getModelLoaders(cacheFile),其实就是找到modelClass
为 File
的factory
。
.append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory()).append(File.class, InputStream.class, new FileLoader.StreamFactory()).append(File.class, File.class, new FileDecoder()).append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory()).append(File.class, File.class, UnitModelLoader.Factory.getInstance())复制代码
然后反正就根据我刚才那样一步步往下走就好了,就会找到对应的ModelLoader
然后生成对应的LoadData
,这里就直接不再跟入了, 这里LoadData
其实是ByteBufferFileLoader
private static final class ByteBufferFetcher implements DataFetcher{... @Override public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { ByteBuffer result; ... result = ByteBufferUtil.fromFile(file); ... callback.onDataReady(result); }复制代码
里面的fetcher
就是ByteBufferFetcher
,然后调用loadData
方法读取到数据。
总结
这篇文章主要是对磁盘缓存数据还有获取数据的分析,以及ModelLoader
的分析。后续还会继续深入分析Glide