上篇文章讲了图片网络的请求步骤,这一章说说缓存机制。Glide的缓存分为三大步:内存缓存、磁盘缓存以及服务器存储(或 drawable、Asset等),服务器也就是上一篇说的网络请求,这里就不说了;内存缓存是 软引用+LruCache缓存,磁盘缓存分为 原始图片缓存+转换后的图片缓存;内存缓存是通过 skipMemoryCache() 来控制是否开启,默认是开启状态,磁盘缓存则是通过 diskCacheStrategy() 方法,默认是 GenericRequestBuilder 中的 DiskCacheStrategy.RESULT,即不保存原始图片,只缓存转换后的图片。
我们先分析磁盘缓存,然后是内存缓存。 图片缓存是网络获取后才存入本地,记得上一篇中的 EngineRunnable run()是执行异步获取图片的逻辑,假如没有缓存,执行 DecodeJob 中的 decodeFromSource() 方法
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
long startTime = LogTime.getLogTime();
final A data = fetcher.loadData(priority);
...
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}
这里的 A data 是封装了网络返回IO流的对象,decodeFromSourceData() 方法则是解码,把流转换为图片,看看它的代码
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
} else {
long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
...
}
return decoded;
}
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
...
SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
...
Resource<T> result = loadFromCache(resultKey.getOriginalKey());
...
return result;
}
先不说细节,单看这两个方法,decodeFromSourceData() 中显示判断磁盘的缓存方式,如果不允许保存原始图片,执行else的代码,意思是根据 width 和 height对图片进行相应的缩放,这两个参数是view的大小或是我们通过 Glide加载图片时的 override() 方法设置的数据;如果允许保留原始图片,则执行 cacheAndDecodeSourceData() 方法,这个方法名字起得很好,见名知意。SourceWriter 是个包装类,diskCacheProvider.getDiskCache() 就是 DiskLruCacheWrapper,它里面包裹了 DiskLruCache类,put(resultKey.getOriginalKey(), writer) 就把数据给缓存到磁盘中了,注意使用的key是 esultKey.getOriginalKey(),它里面只有两个属性,确切说只有一个 id,另外一个 signature 在 GenericRequestBuilder 中创建,是个固定值;数据保存到DiskLruCache 后,loadFromCache() 意思是从缓存中把它重新读出来,这么做个人感觉是为了判断是否缓存成功,
private Resource<T> loadFromCache(Key key) throws IOException {
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}
Resource<T> result = null;
try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
}
如果缓存的数据失败了,则会把它从 DiskLruCache 中删除。主流程上逻辑很简单,那么接下来就细致分析下,看上面的这三个方法,会发现使用的 loadProvider 的几个方法 loadProvider.getSourceDecoder().decode()、loadProvider.getSourceEncoder()。encode()、loadProvider.getCacheDecoder().decode(),loadProvider 上一篇分析过,这里就不多说了,简化点,这里几个方法对应的还是 Glide 构造方法中初始化的 dataLoadProviderRegistry 中的值,主流程还可以画一条线 ImageVideoGifDrawableLoadProvider --》 ImageVideoDataLoadProvider --》 StreamBitmapDataLoadProvider 在这条线里,对象还会被封装,创建新的类型,但最终调用的是 StreamBitmapDataLoadProvider 的 getXXX() 方法,我们以 getSourceDecoder() 为例子分析一下 loadProvider 是 ChildLoadProvider 类型对象,调用方法后,它里面中转给 FixedLoadProvider,接着调用的就是 ImageVideoGifDrawableLoadProvider 了,看下代码
private final ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> sourceDecoder;
public ImageVideoGifDrawableLoadProvider(DataLoadProvider<ImageVideoWrapper, Bitmap> bitmapProvider,
DataLoadProvider<InputStream, GifDrawable> gifProvider, BitmapPool bitmapPool) {
final GifBitmapWrapperResourceDecoder decoder = new GifBitmapWrapperResourceDecoder(
bitmapProvider.getSourceDecoder(),
gifProvider.getSourceDecoder(),
bitmapPool
);
sourceDecoder = decoder;
...
}
@Override
public ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> getSourceDecoder() {
return sourceDecoder;
}
loadProvider.getSourceDecoder() 返回的就是 sourceDecoder 对象,它是在构造方法中创建的 GifBitmapWrapperResourceDecoder,封装了构成方法的参数,它的 decode(data, width, height) 方法被调用后,则执行 GifBitmapWrapperResourceDecoder 中代码
public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
...
GifBitmapWrapper wrapper = null;
try {
wrapper = decode(source, width, height, tempBytes);
} finally {
pool.releaseBytes(tempBytes);
}
return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
}
private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
final GifBitmapWrapper result;
if (source.getStream() != null) {
result = decodeStream(source, width, height, bytes);
} ...
return result;
}
private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
throws IOException {
InputStream bis = streamFactory.build(source.getStream(), bytes);
...
if (result == null) {
ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
result = decodeBitmapWrapper(forBitmapDecoder, width, height);
}
return result;
}
private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
GifBitmapWrapper result = null;
Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
if (bitmapResource != null) {
result = new GifBitmapWrapper(bitmapResource, null);
}
return result;
}
最终执行到 decodeBitmapWrapper() 方法,这个方法中,调用了 bitmapDecoder 的 decode(toDecode, width, height) 方法,bitmapDecoder 是通过构造方法传递进来的对象,它是什么呢?重新看 ImageVideoGifDrawableLoadProvider ,原来是 bitmapProvider.getSourceDecoder() 返回的对象,顺着上面的线,或者 Glide 的构造方法中 dataLoadProviderRegistry 添加数据的顺序,发现 bitmapProvider 是 ImageVideoDataLoadProvider,那么对应的 getSourceDecoder()
private final ImageVideoBitmapDecoder sourceDecoder;
public ImageVideoDataLoadProvider(DataLoadProvider<InputStream, Bitmap> streamBitmapProvider,
DataLoadProvider<ParcelFileDescriptor, Bitmap> fileDescriptorBitmapProvider) {
...
sourceDecoder = new ImageVideoBitmapDecoder(streamBitmapProvider.getSourceDecoder(),
fileDescriptorBitmapProvider.getSourceDecoder());
}
@Override
public ResourceDecoder<ImageVideoWrapper, Bitmap> getSourceDecoder() {
return sourceDecoder;
}
注意了,返回的是 sourceDecoder,它对应的是 ImageVideoBitmapDecoder 类型,它封装了 streamBitmapProvider.getSourceDecoder(),到此为止找到了正主,可以看它的 decode(toDecode, width, height) 方法了
@Override
public Resource<Bitmap> decode(ImageVideoWrapper source, int width, int height) throws IOException {
Resource<Bitmap> result = null;
InputStream is = source.getStream();
if (is != null) {
try {
result = streamDecoder.decode(is, width, height);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Failed to load image from stream, trying FileDescriptor", e);
}
}
}
if (result == null) {
ParcelFileDescriptor fileDescriptor = source.getFileDescriptor();
if (fileDescriptor != null) {
result = fileDescriptorDecoder.decode(fileDescriptor, width, height);
}
}
return result;
}
看来,又中转了,它方法中调用了 streamDecoder.decode(is, width, height), streamDecoder 则是 ImageVideoBitmapDecoder 中的 streamBitmapProvider.getSourceDecoder(), streamBitmapProvider 是 ImageVideoBitmapDecoder构造方法传进来的,可以找到它是 StreamBitmapDataLoadProvider,那么它
@Override
public ResourceDecoder<InputStream, Bitmap> getSourceDecoder() {
return decoder;
}
public StreamBitmapDataLoadProvider(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
decoder = new StreamBitmapDecoder(bitmapPool, decodeFormat);
...
}
找到正主了, StreamBitmapDecoder 的 decode() 方法,
@Override
public Resource<Bitmap> decode(InputStream source, int width, int height) {
Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
return BitmapResource.obtain(bitmap, bitmapPool);
}
最终由 Downsampler 来进行数据的转换,有兴趣的可以看看这个类,图片的缩放就是在这个里面执行的。简单的一行代码,居然嵌套了这么多层,这里面比较绕,但只要流程明白了,就很简单。其他的几个方法可以类似的看,或者直接看 StreamBitmapDataLoadProvider 的代码,这里是一条线的尾部。
继续回到 DecodeJob 类中的 decodeFromSource() 方法,获取到图片后,会调用 transformEncodeAndTranscode() 方法
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
...
writeTransformedToCache(transformed);
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
...
return result;
}
transform(decoded) 的方法比较简单,它执行的是我们在 Glide 加载图片设置的 bitmapTransform() 方法,设置类似 模糊图片、圆角图片 等我们需要的功能,默认这是个空方法; 然后是 writeTransformedToCache(transformed) 方法
private void writeTransformedToCache(Resource<T> transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
long startTime = LogTime.getLogTime();
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
diskCacheProvider.getDiskCache().put(resultKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Wrote transformed from source to cache", startTime);
}
}
这里就是存储转换后的图片,它也有个判断,diskCacheStrategy.cacheResult() 这里是个开关,再往下又是缓存数据,这里分析可以参考上面的逻辑;缓存转换后的图片,key 是 resultKey,它里面信息比较多,包括 id、width、height 等等。 缓存完图片后,执行了 transcode() 方法
又是类型转换,transcoder 其实就是 loadProvider.getTranscoder(),源头是 Glide 构造方法 transcoderRegistry 中的对象,
transcoderRegistry.register(GifBitmapWrapper.class, GlideDrawable.class,
new GifBitmapWrapperDrawableTranscoder(new GlideBitmapDrawableTranscoder(context.getResources(), bitmapPool)));
这里面的代码比较简单,也就是转换封装对象,然后返回。在 EngineJob 中切回主线程,handleResultOnMainThread() 中 listener.onEngineJobComplete(key, engineResource) 方法,执行的是 Engine 中的
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
jobs.remove(key);
}
在这里把转换后的数据,存在内存中一份,这里用的是软引用,关于内存缓存,下篇再讲,这里先提一下。 本篇是以 EngineRunnable 中 run() 方法开始的,它里面先调用 decode()
private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
return decodeFromCache();
} else {
return decodeFromSource();
}
}
如果第一次图片缓存成功了,杀死进程,重新进来,仍是先执行 decodeFromCache() 方法,
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
try {
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
}
if (result == null) {
result = decodeJob.decodeSourceFromCache();
}
return result;
}
看它的代码,知道先从缓存中获取转换之后的图片,如果为空,则找原始图片,原始图片找到后,仍然是转换下图片,缓存起来。此时有图片,则直接通过 EngineJob 切回主线程,后续逻辑和上面一样。