Android(安卓)图片加载框架Glide缓存原理源码分析
上一篇 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主流程源码分析
更多相关文章
- AndroidのBitmap之大图片优化
- android大图片显示
- Android入门第十二篇之Gallery
- Android技术积累:图片缓存管理
- 更换android的初始化图片
- Android(安卓)开发注意事项
- 【转】【Android】获取手机中已安装apk文件信息(PackageInfo、Re
- Android使用ImageLoader异步加载网络图片(二)结合listview
- Android图片放大缩小实现方式(一)