这篇文章基于Glide4,只分析Glide4的缓存机制,如果大家还不了解Glide4源码,可以去看一下下面这两篇做一下铺垫:
好了,直接进入正题~~
这里以加载一张网络图片来讲解缓存过程,从前面的文章中可以知道,当第一次加载一张新的网络图片时,本地是没有这张网络图片的缓存的。
所以会进入到SourceGenerator类的startNext()方法中
public boolean startNext() {
//1.判断是否有缓存,如果有,直接加载缓存(这里第一次加载,所以dataToCache为null)
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
//2.没有缓存,从远程加载
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//3.获取数据加载器
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
因为是首次加载,所以我们直接看从远程加载图片的情况。
获取到数据加载器loadData后,会调用loadData.fetcher.loadData(helper.getPriority(),this)
void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);
第二个参数是个回调,数据加载器在获取到数据后,会回调callback的onDataReady方法,并传入获取到的数据。
这里数据加载器有很多种,感兴趣的可以去看上面的文章。由于这里是从远程加载图片,所以最终会得到HttpGlideUrlLoader这个加载器,我们围绕这个数据加载器来展开,
HttpGlideUrlLoader继承自ModelLoader,HttpGlideUrlLoader类的buildLoadData方法如下
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
// spent parsing urls.
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
返回一个LoadData,第二个参数fetcher传入一个HttpUrlFetcher对象,之后会调用HttpUrlFetcher的loadData方法:
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//获取资源的InputStream
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//调用回调函数
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
可以看到,这个方法中调用loadDataWithRedirects方法获取输入流result,然后调用callback回调函数onDataReady。
我们知道这里的callback就是SourceGenerator对象,
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//将data赋值给dataToCache
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
//重新执行此次加载,即重新调用startNext方法
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
这里将data赋值给dataToCache,然后重新执行此次任务,最终重新调用startNext方法,不过这次dataToCache不为空了
public boolean startNext() {
//1.判断是否有缓存,如果有,直接加载缓存(这里第一次加载,所以dataToCache为null)
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
//2.没有缓存,从远程加载
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//3.获取数据加载器
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
dataToCache不为空,所以这次执行cacheData方法,这里面就是做缓存了,我们一起来看一下
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
//将dataToCache封装为一个DataCacheWriter对象
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
//生成key
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//缓存
helper.getDiskCache().put(originalKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
将dataToCache封装为一个DataCacheWriter对象,然后生成key,即DataCacheKey对象,然后调用helper.getDiskCache().put()进行缓存。
接下来我们来看一下helper.getDiskCache()得到了什么?
这里helper即DecodeHelper,它的getDiskCache方法如下
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
这里的diskCacheProvider是从哪里来的呢?
其实如果你一直追到源头,你会发现这个!在GlideBuilder类的build方法中
@NonNull
Glide build(@NonNull Context context) {
......
//构造内存缓存对象
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
//构造磁盘缓存对象
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions);
}
其实是在构造Glide对象的时候生成的,一个是内存缓存LruResourceCache,一个是磁盘缓存InternalCacheDiskCacheFactory。
这里以磁盘缓存为例,内存缓存也是大同小异~
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {
public InternalCacheDiskCacheFactory(Context context) {
this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,
DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);
}
public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) {
this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);
}
public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,
long diskCacheSize) {
//调用父构造函数,返回DiskLruCacheFactory对象
super(new CacheDirectoryGetter() {
@Override
public File getCacheDirectory() {
File cacheDirectory = context.getCacheDir();
if (cacheDirectory == null) {
return null;
}
if (diskCacheName != null) {
return new File(cacheDirectory, diskCacheName);
}
return cacheDirectory;
}
}, diskCacheSize);
}
}
可以看到,其构造函数调用了父构造函数,因为其继承了DiskLruCacheFactory,所以得到的是DiskLruCacheFactory对象。
我们再看DiskLruCacheFactory的build方法
@Override
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);
}
返回DiskLruCacheWrapper对象,helper.getDiskCache()方法得到的也是DiskLruCacheWrapper对象。
DiskLruCacheWrapper里面就是缓存的逻辑所在了~~
我们看一下它的put方法
public void put(Key key, Writer writer) {
// We want to make sure that puts block so that data is available when put completes. We may
// actually not write any data if we find that data is written by the time we acquire the lock.
//生成key
String safeKey = safeKeyGenerator.getSafeKey(key);
//获取锁
writeLocker.acquire(safeKey);
try {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);
}
try {
// We assume we only need to put once, so if data was written while we were trying to get
// the lock, we can simply abort.
DiskLruCache diskCache = getDiskCache();
Value current = diskCache.get(safeKey);
if (current != null) {
return;
}
DiskLruCache.Editor editor = diskCache.edit(safeKey);
if (editor == null) {
throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
}
try {
File file = editor.getFile(0);
//写操作
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to put to disk cache", e);
}
}
} finally {
//释放锁
writeLocker.release(safeKey);
}
}
这里在参数key的基础上再调用了safeKeyGenerator.getSafeKey()生成safeKey,然后采用了Lock机制,获取锁,进行缓存操作,在finally中释放锁。
问题是这里缓存是如何做的?这个Writer是什么?
我们的data是通过HttpUrlFetcher得到的,我们再看一下这个类的loadData方法
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
返回的是一个InputStream对象,也就是说这个data就是一个InputStream。
我们回头看一下cacheData方法
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
//将dataToCache封装为一个DataCacheWriter对象
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
//生成key
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//缓存
helper.getDiskCache().put(originalKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
已经知道dataToCache是个InputStream,其中调用helper.getSourceEncoder(dataToCache)获取Encoder对象,
//DecodeHelper类
<X> Encoder<X> getSourceEncoder(X data) throws Registry.NoSourceEncoderAvailableException {
return glideContext.getRegistry().getSourceEncoder(data);
}
最终会从Registry类中获取Encoder,
//Registry类
public <X> Encoder<X> getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException {
Encoder<X> encoder = encoderRegistry.getEncoder((Class<X>) data.getClass());
if (encoder != null) {
return encoder;
}
throw new NoSourceEncoderAvailableException(data.getClass());
}
又从encoderRegistry中获取。encoderRegistry里的Encoder是在Glide初始化过程中添加的,在Glide的构造函数中有这么一段
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))
/* Bitmaps */
.append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
.append(
Registry.BUCKET_BITMAP,
ParcelFileDescriptor.class,
Bitmap.class,
parcelFileDescriptorVideoDecoder)
.append(
Registry.BUCKET_BITMAP,
AssetFileDescriptor.class,
Bitmap.class,
VideoDecoder.asset(bitmapPool))
......
其中前面两个就是Encoder,ByteBufferEncoder和StreamEncoder。
那选哪个呢?
我们从encoderRegistry中获取Encoder的时候会做判断
//EncoderRegistry.java
public synchronized <T> Encoder<T> getEncoder(@NonNull Class<T> dataClass) {
for (Entry<?> entry : encoders) {
if (entry.handles(dataClass)) {
return (Encoder<T>) entry.encoder;
}
}
return null;
}
只有Encoder的handles方法返回true时才采用该Encoder。上面的for循环从encoders中逐个取出Entry实例,
private static final class Entry<T> {
private final Class<T> dataClass;
@Synthetic @SuppressWarnings("WeakerAccess") final Encoder<T> encoder;
Entry(@NonNull Class<T> dataClass, @NonNull Encoder<T> encoder) {
this.dataClass = dataClass;
this.encoder = encoder;
}
boolean handles(@NonNull Class<?> dataClass) {
return this.dataClass.isAssignableFrom(dataClass);
}
}
Entry的handles决定了是否能处理该任务,内部属性dataClass是能处理的数据类型,encoder对象就是解码器。
由于我们的data是InputStream类型的,所以只有StreamEncoder才能处理这个类型
我们回到SourceGenerator的cacheData方法里~
得到Encoder解码器后,包装为一个DataCacheWriter,最后调用put方法进行缓存。
class DataCacheWriter<DataType> implements DiskCache.Writer {
private final Encoder<DataType> encoder;
private final DataType data;
private final Options options;
DataCacheWriter(Encoder<DataType> encoder, DataType data, Options options) {
this.encoder = encoder;
this.data = data;
this.options = options;
}
@Override
public boolean write(@NonNull File file) {
return encoder.encode(data, file, options);
}
}
我们看到DiskLruCacheWrapper的put方法
public void put(Key key, Writer writer) {
String safeKey = safeKeyGenerator.getSafeKey(key);
writeLocker.acquire(safeKey);
try {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);
}
try {
...
DiskLruCache.Editor editor = diskCache.edit(safeKey);
try {
File file = editor.getFile(0);
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
}
...
} finally {
writeLocker.release(safeKey);
}
}
里面调用了writer的write方法,write方法里再调用解码器encoder的encode方法,这里的encoder就是StreamEncoder
//StreamEncoder.class
public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
boolean success = false;
OutputStream os = null;
try {
os = new FileOutputStream(file);
int read;
while ((read = data.read(buffer)) != -1) {
os.write(buffer, 0, read);
}
os.close();
success = true;
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to encode data onto the OutputStream", e);
}
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// Do nothing.
}
}
byteArrayPool.put(buffer);
}
return success;
}
很简单,就是文件的读写而已~~
至此,磁盘缓存的操作就完成了~~
喜欢的点个赞~~~