美文网首页android学习
Glide源码分析-缓存与复用机制

Glide源码分析-缓存与复用机制

作者: Joker_Wan | 来源:发表于2021-02-18 14:58 被阅读0次

    1 Glide缓存与复用机制简介

    1.1 Glide的资源状态可以分为四种

    1. Active Resources:有其他View正在展示这张图片
    2. Memory cache:该图片被存进内存中
    3. Resource:经过decode、transformed后的缓存
    4. Data:原始的没有修改过的数据缓存

    Glide读取缓存也是依次从上面四种状态的缓存中读取,如果都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)

    1.2 Glide中Bitmap复用机制

    复用:

    将已经不需要使用的数据空间重新拿来使用,减少内存抖动(指在短时间内有大量的对象被创建或者被回收的现象)

    原理:

    inMutable是Glide能够复用Bitmap的基石,是BitmapFactory提供的一个参数,表示该Bitmap是可变的,支持复用的。BitmapFactory.Options中提供了两个属性:inMutable、inBitmap。当进行Bitmap复用时,需要设置inMutable为true,inBitmap设置被复用的已经存在的Bitmap。Bitmap复用池使用LRU算法实现。

    Bitmap复用使用条件:
    • 在Android 4.4之前,仅支持相同大小的Bitmap,inSampleSize必须为1,而且必须采用jpeg或png格式。
    • 在Android 4.4之后只有一个限制,就是被复用的Bitmap尺寸要大于 新的bitmap,简单来说就是大图可以给小图复用。

    2 缓存源码流程

    Glide源码分析-网络图片加载主流程分析一文中,我们已经知道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,
          ...);
    }
    

    2.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.2 diskCacheFactory

    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写入缓存。

    2.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,否则对资源进行回收。

    2.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<Key>, 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交给ByteBufferEncoderStreamEncoder两个具体类来写入,前者负责将ByteBuffer写入到文件,后者负责将InputStream写入到文件。

    到目前为止,磁盘缓存的读写流程都已分析完成。

    2.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状态。

    2.6 总结

    四种缓存状态
    1. memory cache和disk cache在Glide创建的时候也被创建。

    2. disk cache默认会创建一个250M的缓存目录(/data/data/{package}/cache/image_manager_disk_cache/)。

    3. ActiveResources在Engine的构造器中被创建,内部维护了一个 Map<Key, ResourceWeakReference>类型的activeEngineResources用来存储包裹EngineResource的ResourceWeakReference,ResourceWeakReference构造器中会传入一个ReferenceQueue,在ActiveResources的构造器中会启动一个后台线程,在该线程中会循环从activeEngineResources清除ReferenceQueue中的将要被GC的Resource。

    4. ActiveResources被引用后,其内部的引用计数会+1,当被释放后,其内部的引用计数会-1,当引用计数为0,则表示该ActiveResources不再被引用,会将资源放入LruResourceCache中。

    5. 首先从ActiveResources中获取缓存资源,获取不到再从LruResourceCache中查找。第一次没有缓存,会从网络下载图片成功后会存入磁盘缓存,然后再从磁盘缓存获取资源存入ActiveResources中并将其交给ImageView展示。

    6. 缓存查找中涉及的三个类:ResourceCacheGenerator、DataCacheGenerator、SourceGenerator。按照先后关系依次调用他们的startNext()方法查找缓存,ResourceCacheGenerator获取downsample、transform后的资源文件的缓存文件;DataCacheGenerator获取原始的没有修改过的资源文件的缓存文件;SourceGenerator获取原始源数据。

    7. ActiveResources引用计数就变为0后会通知Engine将此资源从active状态变成memory cache状态。如果我们再次加载资源时可以从memory cache中加载,那么资源又会从memory cache状态变成active状态。也就是说,在资源第一次显示后,我们关闭页面,资源会由active变成memory cache;然后我们再次进入页面,加载时会命中memory cache,从而又变成active状态。

    8. 之所以需要ActiveResources,因为它用弱引用包装资源,随时可能被回收,memory的强引用频繁读写可能造成内存激增频繁GC,而造成内存抖动。资源在使用过程中保存在ActiveResources中,而ActiveResources是弱引用,随时被系统回收,不会造成内存过多使用和泄漏。

    3 Bitmap复用池复用源码流程

    Glide源码分析-网络图片加载主流程分析文章中分析过Glide加载图片主要流程源码,在ByteBufferBitmapDecoder.decode方法中,先将ByteBuffer转换成InputStream,然后在调用Downsampler.decode方法进行解码,代码如下:

      // ByteBufferBitmapDecoder
      @Override
      public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
          @NonNull Options options)
          throws IOException {
        InputStream is = ByteBufferUtil.toStream(source);
        return downsampler.decode(is, width, height, options);
      }
    

    继续跟进Downsampler.decode方法

    public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
          Options options, DecodeCallbacks callbacks) throws IOException {
        Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
            + " mark()");
    
        byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
        
        // getDefaultOptions()中将inMutable设置为true
        BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
        
        // inTempStorage是一个bitmap解析的参数,带入一个buffer,创建临时文件,将图片存储的临时缓存空间
        bitmapFactoryOptions.inTempStorage = bytesForOptions;
    
        DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
        DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
        boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
        boolean isHardwareConfigAllowed =
          options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
    
        try {
          Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
              downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
              requestedHeight, fixBitmapToRequestedDimensions, callbacks);
          return BitmapResource.obtain(result, bitmapPool);
        } finally {
          releaseOptions(bitmapFactoryOptions);
          byteArrayPool.put(bytesForOptions);
        }
      }
    

    在getDefaultOptions()方法中会将inMutable设置为true,代码如下:

      private static synchronized BitmapFactory.Options getDefaultOptions() {
        BitmapFactory.Options decodeBitmapOptions;
        synchronized (OPTIONS_QUEUE) {
          decodeBitmapOptions = OPTIONS_QUEUE.poll();
        }
        if (decodeBitmapOptions == null) {
          decodeBitmapOptions = new BitmapFactory.Options();
          resetOptions(decodeBitmapOptions);
        }
    
        return decodeBitmapOptions;
      }
      
        private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
        decodeBitmapOptions.inTempStorage = null;
        decodeBitmapOptions.inDither = false;
        decodeBitmapOptions.inScaled = false;
        decodeBitmapOptions.inSampleSize = 1;
        decodeBitmapOptions.inPreferredConfig = null;
        decodeBitmapOptions.inJustDecodeBounds = false;
        decodeBitmapOptions.inDensity = 0;
        decodeBitmapOptions.inTargetDensity = 0;
        decodeBitmapOptions.outWidth = 0;
        decodeBitmapOptions.outHeight = 0;
        decodeBitmapOptions.outMimeType = null;
        decodeBitmapOptions.inBitmap = null;
        decodeBitmapOptions.inMutable = true;
      }
    

    回到Downsampler.decode方法中,继续调用decodeFromWrappedStreams方法返回Bitmap,跟进decodeFromWrappedStreams

    // DownSampler
     private Bitmap decodeFromWrappedStreams(InputStream is,
          BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
          DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
          int requestedHeight, boolean fixBitmapToRequestedDimensions,
          DecodeCallbacks callbacks) throws IOException {
        long startTime = LogTime.getLogTime();
    
        // 计算原始大小
        int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
        int sourceWidth = sourceDimensions[0];
        int sourceHeight = sourceDimensions[1];
        String sourceMimeType = options.outMimeType;
    
        ...
        
        // 计算方向
        int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
        int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
        boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
    
        // 计算目标大小
        int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
        int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
        // 计算类型
        ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
    
        ...
           
         // 使用复用池
        if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
          int expectedWidth;
          int expectedHeight;
          
          ...
    
          if (expectedWidth > 0 && expectedHeight > 0) {
            setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
          }
        }
        Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
        callbacks.onDecodeComplete(bitmapPool, downsampled);
    
        ...
        
        return rotated;
      }
      
      private static void setInBitmap(
          BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
        @Nullable Bitmap.Config expectedConfig = null;
         ...
    
        options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
      }
    

    setInBitmap方法里面的bitmapPool就是LruBitmapPool,在Glide构造器里面被初始化,LruBitmapPool 就是Glide提供Bitmap复用池,真正的实现类是LruPoolStrategy

    public class LruBitmapPool implements BitmapPool {
    
    private final LruPoolStrategy strategy;
    
    ...
    
    public synchronized void put(Bitmap bitmap) {
        if (bitmap == null) {
          throw new NullPointerException("Bitmap must not be null");
        }
        if (bitmap.isRecycled()) {
          throw new IllegalStateException("Cannot pool recycled bitmap");
        }
        if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize
            || !allowedConfigs.contains(bitmap.getConfig())) {
          bitmap.recycle();
          return;
        }
    
        final int size = strategy.getSize(bitmap);
        strategy.put(bitmap);
        tracker.add(bitmap);
    
        puts++;
        currentSize += size;
        dump();
        evict();
      }
    
      public Bitmap get(int width, int height, Bitmap.Config config) {
        Bitmap result = getDirtyOrNull(width, height, config);
        if (result != null) {
          result.eraseColor(Color.TRANSPARENT);
        } else {
          result = createBitmap(width, height, config);
        }
    
        return result;
        }
    }
    

    看下LruBitmapPool.getDirty方法

    // LruBitmapPool
      public Bitmap getDirty(int width, int height, Bitmap.Config config) {
        // 优先获取
        Bitmap result = getDirtyOrNull(width, height, config);
        if (result == null) { // 若没有,新建一个bitmap
          result = createBitmap(width, height, config);
        }
        return result;
      }
    

    如果可以使用bitmap池,就会调用bitmapPool的getDirty(),最后赋值给inBitmap,总结一下:setInBitmap方法主要就是从LruBitmapPool中获取可以被复用的Bitmap返回,并赋值给BitmapFactory.Options的inBitmap。

    那么可以被复用的Bitmap是什么时候加入Bitmap复用池呢?当Resource资源没有被引用并且不可被缓存的时候,会调用recycle()方法进行回收,在BitmapDrawableResource的recycle()方法被调用的时候,会将当前BitmapDrawableResource的bitmap放入复用池,代码如下:

      // BitmapDrawableResource
      @Override
      public void recycle() {
        bitmapPool.put(drawable.getBitmap());
      }
    

    LruBitmapPool内部利用了Lru算法,每次操作都自动检测是否删除多余的缓存

    // LruBitmapPool
    private void evict() {
        trimToSize(maxSize);
      }
    
      private synchronized void trimToSize(long size) {
        while (currentSize > size) {
          final Bitmap removed = strategy.removeLast();
          // TODO: This shouldn't ever happen, see #331.
          if (removed == null) {
            if (Log.isLoggable(TAG, Log.WARN)) {
              Log.w(TAG, "Size mismatch, resetting");
              dumpUnchecked();
            }
            currentSize = 0;
            return;
          }
          tracker.remove(removed);
          currentSize -= strategy.getSize(removed);
          evictions++;
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
          }
          dump();
          removed.recycle();
        }
      }
    

    再来看看Bitmap复用池对Bitmap具体的存取逻辑的类LruPoolStrategy,在LruBitmapPool的put方法中会调用LruPoolStrategy的put方法,在LruBitmapPool的get方法中会调用getDirtyOrNull方法进而调用LruPoolStrategy的get方法,LruPoolStrategy时一个接口,在LruBitmapPool中为LruPoolStrategy类型的全局变量strategy其赋值的地方在LruBitmapPool构造器中

      LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
        this.initialMaxSize = maxSize;
        this.maxSize = maxSize;
        this.strategy = strategy;
        this.allowedConfigs = allowedConfigs;
        this.tracker = new NullBitmapTracker();
      }
      
      public LruBitmapPool(long maxSize) {
        this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
      }
      
      private static LruPoolStrategy getDefaultStrategy() {
        final LruPoolStrategy strategy;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
          strategy = new SizeConfigStrategy();
        } else {
          strategy = new AttributeStrategy();
        }
        return strategy;
      }
    

    当sdk版本在19之上,会创建SizeConfigStrategy实例并赋值给strategy,那么继续看下SizeConfigStrategy的put和get方法相关代码

      public class SizeConfigStrategy implements LruPoolStrategy {
      private static final int MAX_SIZE_MULTIPLE = 8;
      private final KeyPool keyPool = new KeyPool();
      private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
      private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
      
      // 存入
      public void put(Bitmap bitmap) {
        int size = Util.getBitmapByteSize(bitmap);
        Key key = keyPool.get(size, bitmap.getConfig());
    
        groupedMap.put(key, bitmap);
    
        NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
        Integer current = sizes.get(key.size);
        sizes.put(key.size, current == null ? 1 : current + 1);
      }
    
      // 取出
      public Bitmap get(int width, int height, Bitmap.Config config) {
        int size = Util.getBitmapByteSize(width, height, config);
        // 查找出最合适的bitmap
        Key bestKey = findBestKey(size, config);
        // 取出
        Bitmap result = groupedMap.get(bestKey);
        if (result != null) {
          // Decrement must be called before reconfigure.
          decrementBitmapOfSize(bestKey.size, result);
          result.reconfigure(width, height,
              result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
        }
        return result;
      }
      
    }
    

    findBestKey()方法就是通过对size进行匹配,找出最合适size的Bitmap的key,上面有提到过:在Android 4.4之后复用Bitmap有一个限制,就是被复用的Bitmap尺寸要大于新的Bitmap尺寸,findBestKey()方法就是实现这个逻辑

      private Key findBestKey(int size, Bitmap.Config config) {
        Key result = keyPool.get(size, config);
        for (Bitmap.Config possibleConfig : getInConfigs(config)) {
          NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
          
          // 返回大于或等于指定size的最小的符合要求的size
          Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
          if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
            if (possibleSize != size
                || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
              keyPool.offer(result);
              result = keyPool.get(possibleSize, possibleConfig);
            }
            break;
          }
        }
        return result;
      }
    

    总结

    • Glide中使用Bitmap复用池来减少内存抖动
    • Bitmap复用池实现类是LruBitmapPool,通过LRU算法来管理Bitmap复用池
    • 具体的存储和取出符合要求的可被复用的Bitmap的逻辑在SizeConfigStrategy中,主要是通过NavigableMap数据结构的ceilingKey(K key)方法取出大于或等于新的Bitmap的size的最优size对应的key,进而取出最优的待复用的Bitmap

    参考链接
    https://muyangmin.github.io/glide-docs-cn/doc/caching.html

    相关文章

      网友评论

        本文标题:Glide源码分析-缓存与复用机制

        本文链接:https://www.haomeiwen.com/subject/khtnxltx.html