上一篇 Android 图片加载框架Glide主流程源码分析
一个好使的图片加载框架,与它的缓存设计关系密切,这里来通过源码看看Glide是怎么设计它的缓存的吧。
根据加载主流程我们可以知道Glide的使用是要先初始化Glide单例,进入到GlideBuilder
public final class GlideBuilder {
...
public Glide build(Context context) {
// 开始初始化glide
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
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);
}
...
}
这个方法挺长的,第10行,初始化操作磁盘缓存的线程池,
第18行,初始化内存大小,接着得到的memoryCache、bitmapPool、arrayPool分别传入Engine、Glide实例中,
跟踪看下18行,看下内存缓存大小怎么计算的
public final class MemorySizeCalculator {
...
// Package private to avoid PMD warning.
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
this.context = builder.context;
arrayPoolSize =
isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
// 计算APP可申请最大使用内存,再乘以乘数因子,内存过低时乘以0.33,一般情况乘以0.4
int maxSize =
getMaxSize(
builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
// ARGB_8888 ,每个像素占用4个字节内存
// 计算屏幕这么大尺寸的图片占用内存大小
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
// 计算目标位图池内存大小
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
// 计算目标Lrucache内存大小,也就是屏幕尺寸图片大小乘以2
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
// 最终APP可用内存大小
int availableSize = maxSize - arrayPoolSize;
if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
// 如果目标位图内存大小+目标Lurcache内存大小小于APP可用内存大小,则OK
memoryCacheSize = targetMemoryCacheSize;
bitmapPoolSize = targetBitmapPoolSize;
} else {
// 否则用APP可用内存大小等比分别赋值
float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"Calculation complete"
+ ", Calculated memory cache size: "
+ toMb(memoryCacheSize)
+ ", pool size: "
+ toMb(bitmapPoolSize)
+ ", byte array size: "
+ toMb(arrayPoolSize)
+ ", memory class limited? "
+ (targetMemoryCacheSize + targetBitmapPoolSize > maxSize)
+ ", max size: "
+ toMb(maxSize)
+ ", memoryClass: "
+ builder.activityManager.getMemoryClass()
+ ", isLowMemoryDevice: "
+ isLowMemoryDevice(builder.activityManager));
}
}
...
}
注释写得比较清楚了,就不解释了,那么Glide在哪开始使用缓存的呢?
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class> resourceClass,
Class transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map, Transformation>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
// 根据各种参数创建图片key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// 检查内存中弱引用是否有目标图片
EngineResource> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
// 检查内存中Lrucache是否有目标图片
EngineResource> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// 内存中没有图片构建任务往下执行
EngineJob> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
...
}
Engine的load()方法还熟悉吧,很长一串
28-29,创建图片URL、宽、高等一系列参数创建key
31-39,从内存缓存弱引用中根据Key获取图片资源,有就返回,没有往下执行
41-49,从内存缓存Lrucache中根绝key获取图片资源,有就返回,没有往下执行
通过以上代码可以看出Glide内存缓存采用了2级,第一级是弱引用,第二级才是Lrucache,如果软引用中没有
对应的图片缓存,就从Lrucache中获取,如果还是没有才去检查磁盘缓存,如果还是没有最后才去网络下载
这里来看下获取内存缓存图片资源的方法loadFromActiveResources()和loadFromCache(),
首先跟踪loadFromActiveResources()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Nullable
private EngineResource> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
...
}
final class ActiveResources {
...
@Nullable
EngineResource> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
...
}
这里是从内存缓存的弱引用中获取的相应图片资源
第7行,判断缓存是否开启,如果可用才继续进行,否则返回null,默认是开启的,我们也可以使用的时候关闭,如下
ImgurGlide.with(vh.imageView)
.load(image.link)
.skipMemoryCache(true)
.into(vh.imageView);
第10行,根据图片key获取软引用图片资源active,如果不为null,则调用active.acquire(),返回相应资源
这里要注意2个方法acquire()和release()
class EngineResource implements Resource {
...
void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call acquire on the main thread");
}
++acquired;
}
/**
* Decrements the number of consumers using the wrapped resource. Must be called on the main
* thread.
*
* This must only be called when a consumer that called the {@link #acquire()} method is now
* done with the resource. Generally external users should never call this method, the framework
* will take care of this for you.
*/
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
...
}
第10行和第28行,可以看到当调用acquire(),acquired这个成员变量会自增1,当调用release(),acquired这个
成员变量会自减1,当acquired数量大于0,说明当前EngineResource实例被使用中
全局搜索可以知道当前资源加载结束后,会调用release(),看下29行,一个回调,找到实现的类Engine
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource> resource) {
Util.assertMainThread();
// 从内存弱引用中移除图片
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
// 内存Lrucache中添加图片
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
第9行,从内存弱引用中移除图片
第12行,内存Lrucache中添加此图片
接下来看下loadFromCache()方法
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
private EngineResource> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
@SuppressWarnings("unchecked")
private EngineResource> getEngineResourceFromCache(Key key) {
//cache 是LruResourceCache实例
Resource> cached = cache.remove(key);
final EngineResource> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
...
}
第6行,判断是否开启内存缓存,默认是开启的
10-15行,从内存Lrucache缓存中获取key对应的图片资源并返回
第12行,调用acquire(),同理当前资源调用数+1
第13行,跟踪进入ActiveResources类
final class ActiveResources {
...
void activate(Key key, EngineResource> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
...
}
这里把Lrucache缓存中的资源存入弱引用缓存
到这里其实应该比较清晰了,Glide加载的内存缓存的图片资源始终是软引用的,当引用中有则返回资源,且资源存入
Lrucache中,如果没有,从Lrucache中返回存入弱引用中,如果Lrucache中也没有呢?
以上就是Glide内存缓存的源码分析,接下来开始磁盘缓存的分析
public abstract class DiskCacheStrategy {
...
public static final DiskCacheStrategy ALL = new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
@Override
public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
EncodeStrategy encodeStrategy) {
return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
...
}
磁盘缓存策略,DiskCacheStrategy这个类定义了几种策略类型
DiskCacheStrategy.ALL :
表示既缓存原始图片,也缓存转换过后的图片。对于远程图片,缓存DATA和RESOURCE。对于本地图片,只缓存RESOURCE。
DiskCacheStrategy.AUTOMATIC (默认策略):
它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据(DATA),因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图(RESOURCE),因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。
DiskCacheStrategy.DATA:
表示只缓存未被处理的文件。我的理解就是我们获得的stream。它是不会被展示出来的,需要经过装载decode,对图片进行压缩和转换,等等操作,得到最终的图片才能被展示。
DiskCacheStrategy.NONE:
表示不缓存任何内容。
DiskCacheStrategy.RESOURCE:
表示只缓存转换过后的图片。(也就是经过decode,转化裁剪的图片)
默认的策略为DiskCacheStrategy.AUTOMATIC,改变策略也很简单, 比如
ImgurGlide.with(vh.imageView)
.load(image.link)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(vh.imageView);
我们这里就用默认磁盘缓存策略分析
通过上一篇的加载流程,我们可以直接找到DecodeJob类,获取磁盘缓存的步骤就在里面
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable>,
Poolable {
...
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 检查磁盘缓存策略,是否解码缓存的转换图片
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
// 检查磁盘缓存策略,是否解码缓存的原始数据,这里理解为流
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
...
}
这3个方法,非常绕,
30-58行,这段代码的逻辑就是根据磁盘缓存策略把检查步骤往下推,第一步检查转换过的图片是否有缓存,
第二步检查未被转化过的图片资源缓存,第三步检查数据源,比如本地相册原始图片,网络URL原始图片。
如果目标图片已经加载过缓存在磁盘了,但是内存缓存中还没有,那么其实在第一步检查中就会获取到,然后加载显示。
这也是大多数的情况加载磁盘缓存过的图片资源,这里以这种情况为例继续分析
class ResourceCacheGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback
直接跟踪类ResourceCacheGenerator
25-34,根据图片URL生成一个磁盘缓存ResourceCacheKey
第35行,根据这个key获取磁盘缓存图片File
第38行,根据这个File获取初始化Glide时注册的ModelLoaders,猜也猜得到大概是哪几个了,必定是其中一个
.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())
// Compilation with Gradle requires the type to be specified for UnitModelLoader here.
.append(File.class, File.class, UnitModelLoader.Factory.getInstance())
这里公布下答案,ByteBufferFileLoader,
接着第52行,根据这个ByteBufferFileLoader去加载磁盘缓存图片文件然后一层一层回调回去显示
其实到这里Glide磁盘缓存就差不多了,接下来继续分析缓存图片在哪存入和获取的呢
第35行,这里就是根据ResourceCacheKey获取磁盘缓存图片File,
在哪存储的呢?
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback
第10行,当从网络下载后会执行到这里,这里就是存储到磁盘的起点
跟踪进入,会发现获取磁盘文件和存储都会调用helper.getDiskCache(),接着就先弄清楚它
final class DecodeHelper {
...
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
...
}
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable>,
Poolable {
...
interface DiskCacheProvider {
DiskCache getDiskCache();
}
...
}
这里首先要知道diskCacheProvider是哪个类的实例,从上面的代码可知是DiskcacheProvider接口的一个实现类,
通过全局搜索可以知道只有一个实现类LazyDiskCacheProvider,那么diskCacheProvider肯定就是
LazyDiskCacheProvider的实例了
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
@Override
...
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build();
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
...
}
第12行,可以知道从外面传了一个factory创建了一个DiskCache的实例,DiskCache是一个接口,全局搜索可以知道DiskCache
有2个实现类DiskLruCacheWrapper、DiskCacheAdapter,从上面的代码分析肯定不是DiskCacheAdapter的实例,如果
是就不会有15行的创建DiskCacheAdapter实例,因此,可以推断第12行创建的是DiskLruCacheWrapper实例,这里可以透露下
其实磁盘缓存的读写都是这个实例里面实现的,不过先不看这个类,我们先要知道这个factory从哪传入的
public final class GlideBuilder {
...
public Glide build(Context context) {
// 开始初始化glide
...
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
...
}
...
}
还记得初始Glide实例那里吗,第8行,可以看到默认情况下初始化磁盘存储,然后InternalCacheDiskCacheFactory实例一级一级
往下传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) {
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);
}
}
第4-5 行,这里的2个参数默认是磁盘内部存储缓存目录、磁盘缓存大小为250M,构造方法最后调用了super构造方法
16-25行,重写了getCacheDirectory(),这个方法的作用就是创建磁盘内部缓存目录,接着跟踪super
public class DiskLruCacheFactory implements DiskCache.Factory {
...
public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long
diskCacheSize) {
this.diskCacheSize = diskCacheSize;
this.cacheDirectoryGetter = cacheDirectoryGetter;
}
@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);
}
...
}
第10-21行,有个build()方法,用于创建DiskLruCacheWrapper实例,与之前的呼应了吧,跟踪第21行,
可以知道创建DiskLruCacheWrapper实例
public class DiskLruCacheWrapper implements DiskCache {
...
public static DiskCache create(File directory, long maxSize) {
return new DiskLruCacheWrapper(directory, maxSize);
}
...
}
到这里应该已经清楚了当存储或者获取磁盘缓存图片资源时用到的DiskCache就是DiskLruCacheWrapper实例,
接下来分析DiskLruCacheWrapper类
public class DiskLruCacheWrapper implements DiskCache {
...
protected DiskLruCacheWrapper(File directory, long maxSize) {
this.directory = directory;
this.maxSize = maxSize;
this.safeKeyGenerator = new SafeKeyGenerator();
}
...
}
这是DiskLruCacheWrapper的构造方法,把缓存目录和大小传入作为成员变量
第6行,构造SafeKeyGenerator实例,跟踪
public class SafeKeyGenerator {
...
private final LruCache loadIdToSafeHash = new LruCache<>(1000);
private final Pools.Pool digestPool =
FactoryPools.threadSafe(10,
new FactoryPools.Factory() {
@Override
public PoolableDigestContainer create() {
try {
return new PoolableDigestContainer(MessageDigest.getInstance("SHA-256"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
});
...
}
SafeKeyGenerator是一个使用LruCache算法保存磁盘缓存图片加密名称的一个工具,这里就不深入分析它了,
接下来看DiskLruCacheWrapper的put()和get(),先跟踪get()
public class DiskLruCacheWrapper implements DiskCache {
...
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key);
}
File result = null;
try {
// It is possible that the there will be a put in between these two gets. If so that shouldn't
// be a problem because we will always put the same value at the same key so our input streams
// will still represent the same data.
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to get from disk cache", e);
}
}
return result;
}
...
}
第5行,加密key,比如,httpxxx 加密后为 32fgdg44r2xx,
第14行,获取磁盘缓存图片文件,接着返回,这里又是难点的开始了,跟踪进去
public class DiskLruCacheWrapper implements DiskCache {
...
private synchronized DiskLruCache getDiskCache() throws IOException {
if (diskLruCache == null) {
diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
}
return diskLruCache;
}
...
}
public final class DiskLruCache implements Closeable {
...
public static DiskLruCache open(File directory, int appVersion, int valueCount, long
maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
...
}
第5行,DiskLruCache.open()构建DiskLruCache实例
24-34行,创建日志文件和备份,
第37行,创建DiskLruCache实例
第40行,如果日志文件存在,开始读取日志文件内容,跟踪
public final class DiskLruCache implements Closeable {
...
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
...
}
6-10行,从日志文件中读取介绍信息,版本号啊神马的
20-28行,循环读取剩下的日志信息,当读取到末尾退出循环,读取到的信息放入lruEntries中
44-60行,读取日志信息,从读取到的一行信息截取空格间的字符串,也就是加密key
62-65行,lruEntries开始存Entry对象,Entry对象又包含上面读取出来的加密key,lruEntries很多地方都会用到
第64行,跟踪
public final class DiskLruCache implements Closeable {
...
private final class Entry {
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
cleanFiles = new File[valueCount];
dirtyFiles = new File[valueCount];
// The names are repetitive so re-use the same builder to avoid allocations.
StringBuilder fileBuilder = new StringBuilder(key).append('.');
int truncateTo = fileBuilder.length();
for (int i = 0; i < valueCount; i++) {
fileBuilder.append(i);
cleanFiles[i] = new File(directory, fileBuilder.toString());
fileBuilder.append(".tmp");
dirtyFiles[i] = new File(directory, fileBuilder.toString());
fileBuilder.setLength(truncateTo);
}
}
}
...
}
可以知道构造Entry实体的时候,根据读取日志中的加密key,其实也就是磁盘缓存图片文件名,生成file对象作为Entry实体
成员变量
接着看DiskLruCache的get()方法
public final class DiskLruCache implements Closeable {
...
public synchronized Value get(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
for (File file : entry.cleanFiles) {
// A file must have been deleted manually!
if (!file.exists()) {
return null;
}
}
redundantOpCount++;
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
...
}
第5行,从lruEntries中根据加密key获取Entry对象,如果有,往下执行
14-19行,从Entry对象获取缓存文件,如果存在往下执行
第30行,如果缓存文件存在构建一个Value实体对象,包含缓存文件返回
到这里获取磁盘缓存图片就差不多了,接下来看下DiskLruCacheWrapper.put(),存储磁盘缓存图片文件
public class DiskLruCacheWrapper implements DiskCache {
...
@Override
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,采用Lrucache算法
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);
}
}
...
}
17-21行,跟get()差不多,先获取DiskLruCache实例,然后get(key)获取缓存文件,如果存在,就返回null,表示不需要存储,
如果没有就继续执行
第23行,很重要,先跟踪,一会再回到这里
public final class DiskLruCache implements Closeable {
...
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Value is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.append(DIRTY);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
journalWriter.flush();
return editor;
}
...
}
13-23行,lruEntrie熟悉了吧,又来了哦,判断存储有加密key没有,没有就构建Entry实体实例把加密key存入,跟前面
get()逻辑一样的
25-34行,根据加密key构建一串特定字符串,然后写入磁盘日志文件中,这个日志用于记录缓存图片文件名称的,下次获取
缓存文件就从这个日志读取名称,前面get()部分分析过了
接着回到DiskLruCacheWrapper.put()
public class DiskLruCacheWrapper implements DiskCache {
...
@Override
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,采用Lrucache算法
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);
}
}
...
}
27-32行,根据之前返回的DiskLruCache.Editor实例,获取需要磁盘缓存的图片文件名称,
然后写入磁盘好了,到这里磁盘的存和取都分析了一遍,相信也比较清楚了,
最后几句话总结下原理
根据传入的URL和相关参数组成一个key,然后从内存缓存的弱引用中查找是否相应的图片资源,如果有
就返回,没有就从内存缓存的Lrucache缓存中查找,如果有就返回并存入弱引用缓存,如果没有就去查看
磁盘缓存,磁盘缓存有几种策略,常用的策略还是缓存转换后的图片,先加密key,然后去磁盘日志文件中
查找有没有记录,如果有就根据记录的文件名称获取图片文件并返回,如果没有记录,就从网络获取图片
资源流,根据加密key创建一份记录写入日志文件中,并把图片资源流写入磁盘缓存中欢迎大家指教,其中还是有很多不是很明白,好了,到这里又可以愉快的玩耍了
Android 图片加载框架Glide主流程源码分析