产品中心 Android源码进阶之Glide缓存机制原理详解

本文转载自微信公多号「Android开发编程」,作者Android开发编程。转载本文请有关Android开发编程公多号。

序言

Android缓存机制:倘若异国缓存,在大量的网络乞求从长途获取图片时会造成网络流量的铺张,添载速度较慢,用户体验不好;

【环球网报道 记者 侯佳欣】据“今日俄罗斯”(RT)30日报道,科罗拉多州报告美国首例感染变异新冠病毒病例一天之后,加利福尼亚州也出现了感染变异新冠病毒病例。

【环球时报记者瑞飞 青木辛斌 倪浩魏辉】2020年即将结束之际,中欧之间传来重大的好消息。12月30日晚,国家主席习近平在北京同德国总理默克尔、法国总统马克龙、欧洲理事会主席米歇尔、欧盟委员会主席冯德莱恩举行视频会晤。中欧领导人共同宣布如期完成中欧投资协定谈判。历经长达7年的谈判,这一“历史性”协定来之不易,对中欧双方都意义重大。习近平强调,中欧投资协定展现了中方推进高水平对外开放的决心和信心,将为中欧相互投资提供更大的市场准入、更高水平的营商环境、更有力的制度保障、更光明的合作前景。在当前某些国家大搞保护主义、世界经济遭受新冠肺炎疫情严重冲击的背景下,中欧投资协定完成谈判尤其显得难能可贵。彭博社称,这标志着中欧双方在政治和经济上都取得了“重大胜利”,同时凸显中国和欧盟的经济纽带正进一步深化。复旦大学欧洲问题研究中心主任丁纯对《环球时报》记者表示,中欧完成高水平投资协定谈判,对各自的战略态势都有好处,也维护了多边主义和自由贸易,不仅有利于中欧双方,也有利于世界。

【环球时报记者木子西青木 王传军 甄翔】“即使疫情结束,澳大利亚经济增长可能也不会恢复到疫情暴发前的水平。”美国CNBC网站29日引述经济咨询公司凯投宏观最新研究称,与中国之间的贸易紧张局势不断升级,使澳大利亚经济遭受严重打击。

【环球时报综合报道】2020年,这个世界到底怎么了?《环球时报》年度评选的十大新闻带你回顾。新冠肺炎疫情是百年来全球发生的最为严重的传染病大流行,这场危机正对全人类形成严峻挑战,在全球携手抗疫的同时,也有个别国家忙着“甩锅”,将疫情政治化、污名化;从两岸关系看,年初就遭遇数十年来前所未有的挑战,但大陆方面积极识变应变,让两岸关系仍保持总体稳定;苏莱曼尼被暗杀,美国与伊朗关系再度紧张,但很多人预想的战争被避免;英国终于成为首个“脱欧”的欧盟成员国,双方的“分手”谈判直到圣诞节前才达成,从而避免了“硬脱欧”的尴尬;抗议种族歧视的示威活动在美国引发骚乱;印军屡屡越线滋事,挑起中印边境对峙;香港国安法实施,让香港由乱而治的局面基本形成;2020年美国总统选举也因其史无前例的混乱而载入史册,特别是自1896年以来,出现第一位败选者拒不认输的局面;中欧投资协定(BIT)谈判完成和亚太15国签署《区域全面经济伙伴关系协定》(RCEP) 极大地提振了世界经济信心;多国加入月球和火星探测,而中国圆满完成的一系列航天任务更令全球瞩目……一年来,参与采访和报道这些大事件的《环球时报》记者也见证了这不同寻常的一年。即将到来的2021年会发生什么,可能都与2020年的这些大事有着千丝万缕的联系。

【环球时报记者 倪浩】据路透社30日报道,全球指数编制公司富时罗素表示,未来可能会把更多的中国企业从其全球指数中剔除。此举缘于美国对中国企业的打压政策。

今天吾们就来聊聊Glide的缓存机制

一、Glide中缓存概念简述

Glide将它分成了两个模块,一个是内存缓存,一个是硬盘缓存;

1、内存缓存

内存缓存又分为两级,优等是LruCache缓存,优等是弱引用缓存

内存缓存的作用:防止行使重复将图片数据读取到内存当中。

LruCache缓存:不在操纵中的图片操纵LruCache来进走缓存。

弱引用缓存:把正在操纵中的图片操纵弱引用来进走缓存,如许的方针珍惜正在操纵的资源不会被LruCache算法回收。

2、硬盘缓存

硬盘缓存的作用:防止行使重复从网络或其他地方重复下载和读取数据;

3、图片乞求步骤

最先一个新的图片乞求之前检查以下多级的缓存:

内存缓存:该图片是否比来被添载过并仍存在于内存中?即LruCache缓存;

运动资源:现在是否有另一个 View 正在展现这张图片?也就是弱引用缓存;

资源类型:该图片是否之前曾被解码、转换并写入过磁盘缓存?

数据来源:构建这个图片的资源是否之前曾被写入过文件缓存?

前两步检查图片是否在内存中,倘若是则直接返回图片。后两步则检查图片是否在磁盘上,以便迅速但异步地返回图片;

倘若四个步骤都未能找到图片,则Glide会返回到原首资源以取回数据(原首文件,Uri, Url等);

图片存的挨次是:弱引用、内存、磁盘;

图片取的挨次是:内存、弱引用、磁盘。

4、Glide中Bitmap复用机制

Bitmap复用机制:将已经不必要操纵的数据空间重新拿来操纵,缩短内存抖动(指在短时间内有大量的对象被创建或者被回收的形象);

BitmapFactory.Options.inMutable是Glide能够复用Bitmap的基石,是BitmapFactory挑供的一个参数,外示该Bitmap是可变的,声援复用的。BitmapFactory.Options中挑供了两个属性:inMutable、inBitmap。当进走Bitmap复用时,必要竖立inMutable为true,inBitmap竖立被复用的已经存在的Bitmap。Bitmap复用池操纵LRU算法实现。

二、缓存源码流程

memory cache和disk cache在Glide创建的时候也被创建了,Glide创建的代码在GlideBuilder.build(Context)手段。

@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,             ...);   }   return new Glide(       ...       memoryCache,       ...); } 
1、内存缓存-memoryCache

经过代码能够望到 memoryCache 被放入 Engine 和 Glide 实例中。在Engine中行使memoryCache进走存取操作,Glide 实例中的memoryCache是用来在内存主要的时候,告诉memoryCache开释内存。Glide实现了ComponentCallbacks2接口,在Glide创建完善后,经过applicationContext.registerComponentCallbacks(glide)似的 Glide 实例能够监听内存主要的信号。

// Glide @Override public void onTrimMemory(int level) {   trimMemory(level); } public void trimMemory(int level) {   // Engine asserts this anyway when removing resources, fail faster and consistently   Util.assertMainThread();   // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.   memoryCache.trimMemory(level);   bitmapPool.trimMemory(level);   arrayPool.trimMemory(level); } 

memoryCache是一个操纵LRU(least recently used)算法实现的内存缓存类LruResourceCache,继承至LruCache类,并实现了MemoryCache接口。LruCache定义了LRU算法实现有关的操作,而MemoryCache定义的是内存缓存有关的操作。

LruCache 的实现是行使了 LinkedHashMap 的这栽数据组织的一个特性( accessOrder=true 基于访问挨次 )再添上对 LinkedHashMap 的数据操作上锁实现的缓存策略。

当调用 put()手段时,就会在荟萃中增补元素,并调用

trimToSize()判定缓存是否已满,倘若满了就用 LinkedHashMap 的迭代器删除队尾元素,即近期最少访问的元素。

当调用 get()手段访问缓存对象时,就会调用 LinkedHashMap 的 get()手段获得对答荟萃元素,同时会更新该元素到队头。

2、磁盘缓存

diskCacheFactory是创建DiskCache的Factory,DiskCache接口定义。

public interface DiskCache {   interface Factory {     /** 250 MB of cache. */     int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;     String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";     @Nullable     DiskCache build();   }   interface Writer {     boolean write(@NonNull File file);   }   @Nullable   File get(Key key);   void put(Key key, Writer writer);   @SuppressWarnings("unused")   void delete(Key key);   void clear(); } 

接着再来望下DiskCache.Factory的默认实现: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);   } } 

由以上代码能够望出:默认会创建一个250M的缓存现在录产品中心,其路径为/data/data/{package}/cache/image_manager_disk_cache/。

不息望其父类DiskLruCacheFactory的代码:

public class DiskLruCacheFactory implements DiskCache.Factory {   private final long diskCacheSize;   private final CacheDirectoryGetter cacheDirectoryGetter;   public interface CacheDirectoryGetter {     File getCacheDirectory();   }   ...   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);   } } 

DiskLruCacheFactory.build()手段会返回一个DiskLruCacheWrapper类的实例,望下DiskLruCacheWrapper的实现。

public class DiskLruCacheWrapper implements DiskCache {   private static final String TAG = "DiskLruCacheWrapper";   private static final int APP_VERSION = 1;   private static final int VALUE_COUNT = 1;   private static DiskLruCacheWrapper wrapper;   private final SafeKeyGenerator safeKeyGenerator;   private final File directory;   private final long maxSize;   private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();   private DiskLruCache diskLruCache;   @SuppressWarnings("deprecation")   public static DiskCache create(File directory, long maxSize) {     return new DiskLruCacheWrapper(directory, maxSize);   }   @Deprecated   @SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})   protected DiskLruCacheWrapper(File directory, long maxSize) {     this.directory = directory;     this.maxSize = maxSize;     this.safeKeyGenerator = new SafeKeyGenerator();   }   private synchronized DiskLruCache getDiskCache() throws IOException {     if (diskLruCache == null) {       diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);     }     return diskLruCache;   }   @Override   public File get(Key key) {     String safeKey = safeKeyGenerator.getSafeKey(key);     File result = null;     try {       final DiskLruCache.Value value = getDiskCache().get(safeKey);       if (value != null) {         result = value.getFile(0);       }     } catch (IOException e) {       ...     }     return result;   }   @Override   public void put(Key key, Writer writer) {     String safeKey = safeKeyGenerator.getSafeKey(key);     writeLocker.acquire(safeKey);     try {       try {         DiskLruCache diskCache = getDiskCache();         Value current = diskCache.get(safeKey);         ...         DiskLruCache.Editor editor = diskCache.edit(safeKey);         ...         try {           File file = editor.getFile(0);           if (writer.write(file)) {             editor.commit();           }         } finally {           editor.abortUnlessCommitted();         }       } catch (IOException e) {         ...       }     } finally {       writeLocker.release(safeKey);     }   }   ... } 

内里包装了一个DiskLruCache,该类主要是为DiskLruCache挑供了一个按照Key生成safeKey的SafeKeyGenerator以及写锁DiskCacheWriteLocker。

回到GlideBuilder.build(Context)中,diskCacheFactory会被传进Engine中,在Engine的组织手段中会被包装成为一个LazyDiskCacheProvider,在被必要的时候调用getDiskCache()手段,如许就会调用factory的build()手段返回一个DiskCache。代码如下:

private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {     private final DiskCache.Factory factory;     private volatile DiskCache diskCache;     LazyDiskCacheProvider(DiskCache.Factory factory) {       this.factory = factory;     }     ...     @Override     public DiskCache getDiskCache() {       if (diskCache == null) {         synchronized (this) {           if (diskCache == null) {             diskCache = factory.build();           }           if (diskCache == null) {             diskCache = new DiskCacheAdapter();           }         }       }       return diskCache;     }   } 

LazyDiskCacheProvider会在Engine后面的初首化流程中行为入参传到DecodeJobFactory的组织器。在DecodeJobFactory创建DecodeJob时也会行为入参会传进往,DecodeJob中会以全局变量保存此LazyDiskCacheProvider,在资源添载完毕并展现后,会进走缓存的存储。同时,DecodeJob也会在DecodeHelper初首化时,将此DiskCacheProvider竖立进往,供ResourceCacheGenerator、DataCacheGenerator读取缓存,供SourceGenerator写入缓存。

3、 ActiveResources

ActiveResources在Engine的组织器中被创建,在ActiveResources的组织器中会启动一个后台优先级级别(THREAD_PRIORITY_BACKGROUND)的线程,在该线程中会调用cleanReferenceQueue()手段不息循环消弭ReferenceQueue中的将要被GC的Resource。

final class ActiveResources {   private final boolean isActiveResourceRetentionAllowed;   private final Executor monitorClearedResourcesExecutor;   @VisibleForTesting   final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();   private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();   private volatile boolean isShutdown;   ActiveResources(boolean isActiveResourceRetentionAllowed) {     this(         isActiveResourceRetentionAllowed,         java.util.concurrent.Executors.newSingleThreadExecutor(             new ThreadFactory() {               @Override               public Thread newThread(@NonNull final Runnable r) {                 return new Thread(                     new Runnable() {                       @Override                       public void run() {                         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);                         r.run();                       }                     },                     "glide-active-resources");               }             }));   }   @VisibleForTesting   ActiveResources(       boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {     this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;     this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;     monitorClearedResourcesExecutor.execute(         new Runnable() {           @Override           public void run() {             cleanReferenceQueue();           }         });   }   @SuppressWarnings("WeakerAccess")   @Synthetic void cleanReferenceQueue() {     while (!isShutdown) {       try {         ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();         cleanupActiveReference(ref);         // This section for testing only.         DequeuedResourceCallback current = cb;         if (current != null) {           current.onResourceDequeued();         }         // End for testing only.       } catch (InterruptedException e) {         Thread.currentThread().interrupt();       }     }   } } 

先来望望ActiveResources的activate手段(保存)、deactivate手段(删除)的手段。

synchronized void activate(Key key, EngineResource<?> resource) {     ResourceWeakReference toPut =         new ResourceWeakReference(             key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);     ResourceWeakReference removed = activeEngineResources.put(key, toPut);     if (removed != null) {       removed.reset();     }   }   synchronized void deactivate(Key key) {     ResourceWeakReference removed = activeEngineResources.remove(key);     if (removed != null) {       removed.reset();     }   } 

activate手段会将参数封装成为一个ResourceWeakReference,然后放入map中,倘若对答的key之前有值,那么调用之前值的reset手段进走消弭。deactivate手段先在map中移除,然后调用resource的reset手段进走消弭。ResourceWeakReference继承WeakReference,内部只是保存了Resource的一些属性。

static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {   @SuppressWarnings("WeakerAccess") @Synthetic final Key key;   @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;   @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;   @Synthetic   @SuppressWarnings("WeakerAccess")   ResourceWeakReference(       @NonNull Key key,       @NonNull EngineResource<?> referent,       @NonNull ReferenceQueue<? super EngineResource<?>> queue,       boolean isActiveResourceRetentionAllowed) {     super(referent, queue);     this.key = Preconditions.checkNotNull(key);     this.resource =         referent.isCacheable() && isActiveResourceRetentionAllowed             ? Preconditions.checkNotNull(referent.getResource()) : null;     isCacheable = referent.isCacheable();   } } 

组织手段中调用了super(referent, queue),如许做能够让将要被GC的对象放入到ReferenceQueue中。而ActiveResources.cleanReferenceQueue()手段会不息尝试从queue中获取将要被GC的resource,然后调用cleanupActiveReference手段将resource从activeEngineResources中移除。cleanupActiveReference源码如下:

void cleanupActiveReference(@NonNull ResourceWeakReference ref) {     synchronized (listener) {       synchronized (this) {         // 移除active资源         activeEngineResources.remove(ref.key);         if (!ref.isCacheable || ref.resource == null) {           return;         }         // 组织新的 Resource         EngineResource<?> newResource =             new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);         newResource.setResourceListener(ref.key, listener);         // 回调Engine的onResourceReleased手段         // 这会导致此资源从active变成memory cache状态         listener.onResourceReleased(ref.key, newResource);       }     }   } 

Engine实现了EngineResource.ResourceListener,此处的listener就是Engine,最后会回调Engine.onResourceReleased。

@Override   public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {     activeResources.deactivate(cacheKey);     if (resource.isCacheable()) {       cache.put(cacheKey, resource);     } else {       resourceRecycler.recycle(resource);     }   } 

倘若资源能够被缓存,则缓存到 memory cache,否则对资源进走回收。

4、磁盘缓存读取

吾们分析下缓存的存取代码。吾们望下:

public synchronized <R> LoadStatus load(...) {   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);     return null;   }   EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);   if (cached != null) {     cb.onResourceReady(cached, DataSource.MEMORY_CACHE);     return null;   }   EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);   if (current != null) {     current.addCallback(cb, callbackExecutor);     return new LoadStatus(cb, current);   }   EngineJob<R> engineJob =       engineJobFactory.build(...);   DecodeJob<R> decodeJob =       decodeJobFactory.build(...);   jobs.put(key, engineJob);   engineJob.addCallback(cb, callbackExecutor);   engineJob.start(decodeJob);   return new LoadStatus(cb, engineJob); } 

缓存必要按照EngineKey往存取,先望下EngineKey的组织手段。

EngineKey(       Object model,       Key signature,       int width       int height,       Map<Class<?>, Transformation<?>> transformations,       Class<?> resourceClass,       Class<?> transcodeClass,       Options options) 

model:load手段传的参数;

signature:BaseRequestOptions的成员变量,默认会是EmptySignature.obtain()

在添载本地resource资源时会变成ApplicationVersionSignature.obtain(context);

width、height:倘若异国指定override(int size),那么将得到view的size;

transformations:默认会基于ImageView的scaleType竖立对答的四个Transformation;

倘若指定了transform,那么就基于该值进走竖立;

resourceClass:解码后的资源,倘若异国asBitmap、asGif,清淡会是Object;

transcodeClass:最后要转换成的数据类型,按照as手段确定,添载本地res或者网络URL,都会调用asDrawable,因而为Drawable

options:倘若异国竖立过transform,此处会按照ImageView的scaleType默认指定一个option;

因而,在多次添载联相符个model的过程中,只要上述任何一个参数有转折,都不会认为是联相符个key;

回到Engine.load手段,从缓存添载成功后的回调cb.onResourceReady(cached, DataSource.MEMORY_CACHE);能够望到:active状态的资源和memory cache状态的资源都是DataSource.MEMORY_CACHE,并且添载的资源都是 EngineResource 对象,该对象内部采用了引用计数往判定资源是否被开释,倘若引用计数为0,那么会调用listener.onResourceReleased(key, this)手段告诉外界此资源已经开释了。这边的listener是ResourceListener类型的接口,只有一个onResourceReleased(Key key, EngineResource resource)手段,Engine实现了该接口,此处的listener就是Engine。在Engine.onResourceReleased手段中会判定资源是否可缓存,可缓存则将此资源放入memory cache中,否则回收失踪该资源,代码如下:

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {     // 从activeResources中移除     activeResources.deactivate(cacheKey);     if (resource.isCacheable()) {       // 存入 MemoryCache       cache.put(cacheKey, resource);     } else {       resourceRecycler.recycle(resource);     }   } 

不息回到Engine.load手段,先来望下active资源获取的手段。

@Nullable   private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {     // 竖立skipMemoryCache(true),则isMemoryCacheable为false,跳过ActiveResources     if (!isMemoryCacheable) {       return null;     }     EngineResource<?> active = activeResources.get(key);     if (active != null) {       // 命中缓存,引用计数+1       active.acquire();     }     return active;   } 

不息分析cached资源获取的手段,倘若从active资源中异国获取到缓存,则不息从内存缓存中查找。

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {     // 竖立skipMemoryCache(true),则isMemoryCacheable为false,跳过ActiveResources     if (!isMemoryCacheable) {       return null;     }     EngineResource<?> cached = getEngineResourceFromCache(key);     if (cached != null) {       // 命中缓存,引用计数+1       cached.acquire();       // 将此资源从memoryCache中移到activeResources中       activeResources.activate(key, cached);     }     return cached;   } 

倘若从memoryCache中获取到资源则将此资源从memoryCache中移到activeResources中。第一次添载的时候activeResources和memoryCache中都异国缓存的,后面不息经过DecodeJob和EngineJob往添载资源。DecoceJob实现了Runnable接口,然后会被EngineJob.start手段挑交到对答的线程池中往实走。在DecoceJob的run手段中,会挨次从ResourceCacheGenerator和DataCacheGenerator中往取缓存数据,当这两者都取不到的情况下,会交给SourceGenerator添载网络图片或者本地资源。resource资源和data资源都是磁盘缓存中的资源。

先望下 ResourceCacheGenerator.startNext。

@Override   public boolean startNext() {     // list内里只有一个GlideUrl对象     List<Key> sourceIds = helper.getCacheKeys();     if (sourceIds.isEmpty()) {       return false;     }     // 获得了三个能够到达的registeredResourceClasses     // GifDrawable、Bitmap、BitmapDrawable     List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();     if (resourceClasses.isEmpty()) {       if (File.class.equals(helper.getTranscodeClass())) {         return false;       }       throw new IllegalStateException(          "Failed to find any load path from " + helper.getModelClass() + " to "              + helper.getTranscodeClass());     }     // 遍历sourceIds中的每一个key、resourceClasses中每一个class,以及其他的一些值构成key     // 尝试在磁盘缓存中以key找到缓存文件     while (modelLoaders == null || !hasNextModelLoader()) {       resourceClassIndex++;       if (resourceClassIndex >= resourceClasses.size()) {         sourceIdIndex++;         if (sourceIdIndex >= sourceIds.size()) {           return false;         }         resourceClassIndex = 0;       }       Key sourceId = sourceIds.get(sourceIdIndex);       Class<?> resourceClass = resourceClasses.get(resourceClassIndex);       Transformation<?> transformation = helper.getTransformation(resourceClass);       // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,       // we only run until the first one succeeds, the loop runs for only a limited       // number of iterations on the order of 10-20 in the worst case.       // 组织key       currentKey =           new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops               helper.getArrayPool(),               sourceId,               helper.getSignature(),               helper.getWidth(),               helper.getHeight(),               transformation,               resourceClass,               helper.getOptions());       // 查找缓存文件       cacheFile = helper.getDiskCache().get(currentKey);       // 倘若找到了缓存文件,循环条件则会为false,退出循环       if (cacheFile != null) {         sourceKey = sourceId;         // 1. 找出注时兴以File.class为modelClass的注入代码         // 2. 调用一切注入的factory.build手段得到ModelLoader         // 3 .过滤失踪不能够处理model的ModelLoader         // 此时的modelLoaders值为:         // [ByteBufferFileLoader, FileLoader, FileLoader, UnitModelLoader]         modelLoaders = helper.getModelLoaders(cacheFile);         modelLoaderIndex = 0;       }     }     // 倘若找到了缓存文件,hasNextModelLoader()手段则会为true,能够实走循环     // 异国找到缓存文件,则不会进入循环,会直接返回false     loadData = null;     boolean started = false;     while (!started && hasNextModelLoader()) {       ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);       // 在循环中会挨次判定某个ModelLoader能不克添载此文件       loadData = modelLoader.buildLoadData(cacheFile,           helper.getWidth(), helper.getHeight(), helper.getOptions());       if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {         started = true;         // 倘若某个ModelLoader能够,那么就调用其fetcher进走添载数据        
// 添载成功或战败会告诉自身         loadData.fetcher.loadData(helper.getPriority(), this);       }     }     return started;   } 

该手段的有关注解代码里都有标明。找缓存时key的类型为ResourceCacheKey,吾们先来望下ResourceCacheKey的构成

currentKey =           new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops               helper.getArrayPool(),               sourceId,               helper.getSignature(),               helper.getWidth(),               helper.getHeight(),               transformation,               resourceClass,               helper.getOptions()); ResourceCacheKey(       ArrayPool arrayPool,       Key sourceKey,       Key signature,       int width,       int height,       Transformation<?> appliedTransformation,       Class<?> decodedResourceClass,       Options options) 

arrayPool:默认值是LruArrayPool,不参与key的equals手段;

sourceKey:倘若乞求的是URL,此处就是GlideUrl(GlideUrl implements Key);

signature:BaseRequestOptions的成员变量,默认会是EmptySignature.obtain(),

在添载本地resource资源时会变成ApplicationVersionSignature.obtain(context);

width、height:倘若异国指定override(int size),那么将得到view的size;

appliedTransformation:默认会按照ImageView的scaleType竖立对答的BitmapTransformation;

倘若指定了transform,那么就会是指定的值;

decodedResourceClass:能够被编码成的资源类型,如BitmapDrawable等;

options:倘若异国竖立过transform,此处会按照ImageView的scaleType默认指定一个option;

在ResourceCacheKey中,arrayPool并异国参与equals手段;

生成ResourceCacheKey之后会按照key往磁盘缓存中查找cacheFile = helper.getDiskCache().get(currentKey);

helper.getDiskCache()返回DiskCache接口,它的实现类是DiskLruCacheWrapper,望下DiskLruCacheWrapper.get手段。

@Override   public File get(Key key) {     String safeKey = safeKeyGenerator.getSafeKey(key);     ...     File result = null;     try {       final DiskLruCache.Value value = getDiskCache().get(safeKey);       if (value != null) {         result = value.getFile(0);       }     } catch (IOException e) {       ...     }     return result;   } 

这边调用SafeKeyGenerator生成了一个String类型的SafeKey,实际上就是对原首key中每个字段都操纵SHA-256添密,然后将得到的字节数组转换为16进制的字符串。生成SafeKey后,接着按照SafeKey往DiskCache内里找对答的缓存文件,然后返回文件。

回到ResourceCacheGenerator.startNext手段中,倘若找到了缓存会调用loadData.fetcher.loadData(helper.getPriority(), this);这边的 fetcher 是 ByteBufferFetcher,ByteBufferFetcher的loadData手段中最后会实走callback.onDataReady(result)这边callback是ResourceCacheGenerator。

public void onDataReady(Object data) {     cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,         currentKey);   } 

ResourceCacheGenerator的onDataReady手段又会回调DecodeJob的onDataFetcherReady手段进走后续的解码操作。

倘若ResourceCacheGenerator异国找到缓存,就会交给DataCacheGenerator不息查找缓存。该类大体流程和ResourceCacheGenerator相通,有点差别的是,DataCacheGenerator的组织器有两个组织器,其中的DataCacheGenerator(List, DecodeHelper, FetcherReadyCallback)组织器是给SourceGenerator准备的。由于倘若异国磁盘缓存,那么从源头添载后,一定必要进走磁盘缓存操作的。因而,SourceGenerator会将添载后的资源保存到磁盘中,然后转交给DataCacheGenerator从磁盘中掏出交给ImageView展现。

望下DataCacheGenerator.startNext:

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);       ...     while (!started && hasNextModelLoader()) {       ModelLoader<File, ?> modelLoader = 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;   } 

这边的originalKey是DataCacheKey类型的,DataCacheKey组织手段如下:

DataCacheKey(Key sourceKey, Key signature)

这边的sourceKey和signature与ResourceCacheKey中的两个变量相反,从这边就能够望出:DataCache缓存的是原首的数据,ResourceCache缓存的是是被解码、转换后的数据。

倘若DataCacheGenerator异国取到缓存,那么会交给SourceGenerator从源头添载。望下SourceGenerator的startNext手段。

@Override   public boolean startNext() {     // 首次运走dataToCache为null     if (dataToCache != null) {       Object data = dataToCache;       dataToCache = null;       cacheData(data);     }     // 首次运走sourceCacheGenerator为null     if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {       return true;     }     sourceCacheGenerator = null;     loadData = null;     boolean started = false;     while (!started && hasNextModelLoader()) {       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;   } 

添载成功后,照样会回调SourceGenerator的onDataReady手段。

@Override   public void onDataReady(Object data) {     DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();     if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {       dataToCache = data;       // cb 为 DecodeJob       cb.reschedule();     } else {       // cb 为 DecodeJob       cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,           loadData.fetcher.getDataSource(), originalKey);     }   } 

先判定获取到的数据是否必要进走磁盘缓存,倘若必要磁盘缓存,则经过DecodeJob、EngineJob的调度,重新调用SourceGenerator.startNext手段,此时dataToCache已经被赋值,则会调用cacheData(data);进走磁盘缓存的写入,并转交给DataCacheGenerator完善后续的处理;否则就告诉DecodeJob已经添载成功。

先望下SourceGenerator的startNext手段中调用的SourceGenerator.cacheData(data)。

private void cacheData(Object dataToCache) {     long startTime = LogTime.getLogTime();     try {       Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);       DataCacheWriter<Object> writer =           new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());       originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());       helper.getDiskCache().put(originalKey, writer);       ...     } finally {       loadData.fetcher.cleanup();     }     sourceCacheGenerator =         new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);   } 

cacheData手段先构建了一个DataCacheKey将data写入了磁盘,然后new了一个DataCacheGenerator赋值给sourceCacheGenerator。回到startNext不息向下实走,此时sourceCacheGenerator不为空,就调用其startNext()手段从磁盘中添载刚写入磁盘的数据,并返回true让DecodeJob停留尝试获取数据。此时,从磁盘缓存中读取数据的逻辑已经完善,接下来是写磁盘缓存。

倘若SourceGenerator的onDataReady手段中的磁盘缓存策略不走用,则会回调DecodeJob.onDataFetcherReady手段。

// DecodeJob   @Override   public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,       DataSource dataSource, Key attemptedKey) {     this.currentSourceKey = sourceKey;     this.currentData = data;     this.currentFetcher = fetcher;     this.currentDataSource = dataSource;     this.currentAttemptingKey = attemptedKey;     if (Thread.currentThread() != currentThread) {       runReason = RunReason.DECODE_DATA;       callback.reschedule(this);     } else {       GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");       try {         decodeFromRetrievedData();       } finally {         GlideTrace.endSection();       }     }   }   private void decodeFromRetrievedData() {     ...     Resource<R> resource = null;     try {       resource = decodeFromData(currentFetcher, currentData, currentDataSource);     } catch (GlideException e) {       e.setLoggingDetails(currentAttemptingKey, currentDataSource);       throwables.add(e);     }     if (resource != null) {       notifyEncodeAndRelease(resource, currentDataSource);     } else {       runGenerators();     }   } 

decodeFromRetrievedData();后续的手段调用链在之前的文章平分析过,主要做的事情就是:将原首的data数据变化为能够供ImageView表现的resource数据并将其表现在ImageView上。

将原首的data数据变化为resource数据后,会调用DecodeJob.onResourceDecoded(dataSource, decoded)。

@Synthetic   @NonNull   <Z> Resource<Z> onResourceDecoded(DataSource dataSource,       @NonNull Resource<Z> decoded) {     @SuppressWarnings("unchecked")     Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();     Transformation<Z> appliedTransformation = null;     Resource<Z> transformed = decoded;     // 不是 resource cache时要transform     if (dataSource != DataSource.RESOURCE_DISK_CACHE) {       appliedTransformation = decodeHelper.getTransformation(resourceSubClass);       transformed = appliedTransformation.transform(glideContext, decoded, width, height);     }     // TODO: Make this the responsibility of the Transformation.     if (!decoded.equals(transformed)) {       decoded.recycle();     }     final EncodeStrategy encodeStrategy;     final ResourceEncoder<Z> encoder;     if (decodeHelper.isResourceEncoderAvailable(transformed)) {       encoder = decodeHelper.getResultEncoder(transformed);       encodeStrategy = encoder.getEncodeStrategy(options);     } else {       encoder = null;       encodeStrategy = EncodeStrategy.NONE;     }     Resource<Z> result = transformed;     boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);     if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,         encodeStrategy)) {       if (encoder == null) {         throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());       }       final Key key;       switch (encodeStrategy) {         case SOURCE:           key = new DataCacheKey(currentSourceKey, signature);           break;         case TRANSFORMED:           key =               new ResourceCacheKey(                   decodeHelper.getArrayPool(),                   currentSourceKey,                   signature,                   width,                   height,                   appliedTransformation,                   resourceSubClass,                   options);           break;         default:           throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);       }       LockedResource<Z> lockedResult = LockedResource.obtain(transformed);       deferredEncodeManager.init(key, encoder, lockedResult);       result = lockedResult;     }     return result;   } 

然后是此过程中的磁盘缓存过程,影响的因素有encodeStrategy、DiskCacheStrategy.isResourceCacheable。encodeStrategy按照resource数据的类型来判定,倘若是Bitmap或BitmapDrawable,那么就是TRANSFORMED;倘若是GifDrawable,那么就是SOURCE。磁盘缓存策略默认是DiskCacheStrategy.AUTOMATIC。源码如下:

public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {         public boolean isDataCacheable(DataSource dataSource) {             return dataSource == DataSource.REMOTE;         }         public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {             return (isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE || dataSource == DataSource.LOCAL) && encodeStrategy == EncodeStrategy.TRANSFORMED;         }         public boolean decodeCachedResource() {             return true;         }         public boolean decodeCachedData() {             return true;         }     }; 

只有dataSource为DataSource.LOCAL且encodeStrategy为EncodeStrategy.TRANSFORMED时,才批准缓存。也就是只有本地的resource数据为Bitmap或BitmapDrawable的资源才能够缓存。

在DecodeJob.onResourceDecoded中会调用deferredEncodeManager.init(key, encoder, lockedResult);往初首化deferredEncodeManager。

在DecodeJob的decodeFromRetrievedData();中拿到resource数据后会调用notifyEncodeAndRelease(resource, currentDataSource)行使deferredEncodeManager对象进走磁盘缓存的写入;

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {     ...     // 告诉回调,资源已经停当     notifyComplete(result, dataSource);     stage = Stage.ENCODE;     try {       if (deferredEncodeManager.hasResourceToEncode()) {         deferredEncodeManager.encode(diskCacheProvider, options);       }     } finally {       if (lockedResource != null) {         lockedResource.unlock();       }     }     onEncodeComplete();   } 

deferredEncodeManager.encode走磁盘缓存的写入。

// DecodeJob private static class DeferredEncodeManager<Z> {   private Key key;   private ResourceEncoder<Z> encoder;   private LockedResource<Z> toEncode;   @Synthetic   DeferredEncodeManager() { }   // We just need the encoder and resource type to match, which this will enforce.   @SuppressWarnings("unchecked")   <X> void init(Key key, ResourceEncoder<X> encoder, LockedResource<X> toEncode) {     this.key = key;     this.encoder = (ResourceEncoder<Z>) encoder;     this.toEncode = (LockedResource<Z>) toEncode;   }   void encode(DiskCacheProvider diskCacheProvider, Options options) {     GlideTrace.beginSection("DecodeJob.encode");     try {       // 存入磁盘缓存       diskCacheProvider.getDiskCache().put(key,           new DataCacheWriter<>(encoder, toEncode, options));     } finally {       toEncode.unlock();       GlideTrace.endSection();     }   }   boolean hasResourceToEncode() {     return toEncode != null;   }   void clear() {     key = null;     encoder = null;     toEncode = null;   } } 

diskCacheProvider.getDiskCache()获取到DiskLruCacheWrapper,并调用DiskLruCacheWrapper的put写入。DiskLruCacheWrapper在写入的时候会操纵到写锁DiskCacheWriteLocker,锁对象由对象池WriteLockPool创建,写锁WriteLock实现是一个不公平锁ReentrantLock。

在缓存写入前,会判定key对答的value存不存在,若存在则不写入。缓存的真实写入会由DataCacheWriter交给ByteBufferEncoder和StreamEncoder两个详细类来写入,前者负责将ByteBuffer写入到文件,后者负责将InputStream写入到文件。

现在为止,磁盘缓存的读写流程都已分析完善。

5、内存缓存:ActiveResource与MemoryCache读取

回到DecodeJob.notifyEncodeAndRelease手段中,经过notifyComplete、EngineJob.onResourceReady、notifyCallbacksOfResult手段中。

在该手段中一方面会将原首的resource包装成一个EngineResource,然后经过回调传给Engine.onEngineJobComplete。

@Override   public synchronized void onEngineJobComplete(       EngineJob<?> engineJob, Key key, EngineResource<?> resource) {     // 竖立资源的回调为本身,如许在资源开释时会告诉本身的回调手段     if (resource != null) {       resource.setResourceListener(key, this);       // 将资源放入activeResources中,资源变为active状态       if (resource.isCacheable()) {         activeResources.activate(key, resource);       }     }     // 将engineJob从Jobs中移除     jobs.removeIfCurrent(key, engineJob);   } 

在这边会将资源放入activeResources中,资源变为active状态。后面会操纵Executors.mainThreadExecutor()调用SingleRequest.onResourceReady回调进走资源的表现。在触发回调前后各有一个地方会对engineResource进走acquire()和release()操作,这两个操作别离发生在notifyCallbacksOfResult()手段的incrementPendingCallbacks、decrementPendingCallbacks()调用中。

@Synthetic void notifyCallbacksOfResult() {   ResourceCallbacksAndExecutors copy;   Key localKey;   EngineResource<?> localResource;   synchronized (this) {     ...     engineResource = engineResourceFactory.build(resource, isCacheable);     ...     hasResource = true;     copy = cbs.copy();     incrementPendingCallbacks(copy.size() + 1);     localKey = key;     localResource = engineResource;   }   listener.onEngineJobComplete(this, localKey, localResource);   for (final ResourceCallbackAndExecutor entry : copy) {     entry.executor.execute(new CallResourceReady(entry.cb));   }   decrementPendingCallbacks(); } synchronized void incrementPendingCallbacks(int count) {   ...   if (pendingCallbacks.getAndAdd(count) == 0 && engineResource != null) {     engineResource.acquire();   } } synchronized void decrementPendingCallbacks() {   ...   int decremented = pendingCallbacks.decrementAndGet();   if (decremented == 0) {     if (engineResource != null) {       engineResource.release();     }     release();   } } private class CallResourceReady implements Runnable {   private final ResourceCallback cb;   CallResourceReady(ResourceCallback cb) {     this.cb = cb;   }   @Override   public void run() {     synchronized (EngineJob.this) {       if (cbs.contains(cb)) {         // Acquire for this particular callback.         engineResource.acquire();         callCallbackOnResourceReady(cb);         removeCallback(cb);       }       decrementPendingCallbacks();     }   } } 

CallResourceReady的run手段中也会调用engineResource.acquire(),上面的代码调用终结后,engineResource的引用计数为1。engineResource的引用计数会在RequestManager.onDestory手段中最后调用SingleRequest.clear()手段,SingleRequest.clear()内部调用releaseResource()、Engine.release 进走开释,如许引用计数就变为0。引用计数就变为0后会告诉Engine将此资源从active状态变成memory cache状态。倘若吾们再次添载资源时能够从memory cache中添载,那么资源又会从memory cache状态变成active状态。也就是说,在资源第一次表现后,吾们关闭页面,资源会由active变成memory cache;然后吾们再次进入页面,添载时会命中memory cache,从而又变成active状态。

总结

读取内存缓存时,先从LruCache算法机制的内存缓存读取,再从弱引用机制的内存缓存读取;

写入内存缓存时,先写入 弱引用机制 的内存缓存,等到图片不再被操纵时,再写入到 LruCache算法机制的内存缓存;

读取磁盘缓存时,先读取转换后图片的缓存,再读取原首图片的缓存。

【编辑保举】产品中心

2幼时学习Spring整相符Freemarker项现在实战 基于Strview.js项现在脚手架StrviewApp是怎么搭建首来的? 想高效在线办公,提出你“如流”一下:项现在管理、配相符文档都来了 为了让初学者有项现在可入门,吾清理了这23个开源项现在…… 如何将你的开源项现在变成安详的收好来源

posted on 2021-09-08  作者:admin  阅读量:

栏目导航

Powered by 英超联赛下注 @2018 RSS地图 HTML地图