上一篇 Android 图片加载框架Glide主流程源码分析


一个好使的图片加载框架,与它的缓存设计关系密切,这里来通过源码看看Glide是怎么设计它的缓存的吧。


根据加载主流程我们可以知道Glide的使用是要先初始化Glide单例,进入到GlideBuilder

public final class GlideBuilder {  ...  public Glide build(Context context) {    // 开始初始化glide    if (sourceExecutor == null) {      sourceExecutor = GlideExecutor.newSourceExecutor();    }    if (diskCacheExecutor == null) {      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();    }    if (animationExecutor == null) {      animationExecutor = GlideExecutor.newAnimationExecutor();    }    if (memorySizeCalculator == null) {      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();    }    if (connectivityMonitorFactory == null) {      connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();    }    if (bitmapPool == null) {      int size = memorySizeCalculator.getBitmapPoolSize();      if (size > 0) {        bitmapPool = new LruBitmapPool(size);      } else {        bitmapPool = new BitmapPoolAdapter();      }    }    if (arrayPool == null) {      arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());    }    if (memoryCache == null) {      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());    }    if (diskCacheFactory == null) {      diskCacheFactory = new InternalCacheDiskCacheFactory(context);    }    if (engine == null) {      engine =          new Engine(              memoryCache,              diskCacheFactory,              diskCacheExecutor,              sourceExecutor,              GlideExecutor.newUnlimitedSourceExecutor(),              GlideExecutor.newAnimationExecutor(),              isActiveResourceRetentionAllowed);    }    RequestManagerRetriever requestManagerRetriever =        new RequestManagerRetriever(requestManagerFactory);    return new Glide(        context,        engine,        memoryCache,        bitmapPool,        arrayPool,        requestManagerRetriever,        connectivityMonitorFactory,        logLevel,        defaultRequestOptions.lock(),        defaultTransitionOptions);  }    ...}

这个方法挺长的,第10行,初始化操作磁盘缓存的线程池,

第18行,初始化内存大小,接着得到的memoryCache、bitmapPool、arrayPool分别传入Engine、Glide实例中,

跟踪看下18行,看下内存缓存大小怎么计算的

public final class MemorySizeCalculator {  ... // Package private to avoid PMD warning.  MemorySizeCalculator(MemorySizeCalculator.Builder builder) {    this.context = builder.context;    arrayPoolSize =        isLowMemoryDevice(builder.activityManager)            ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR            : builder.arrayPoolSizeBytes;    // 计算APP可申请最大使用内存,再乘以乘数因子,内存过低时乘以0.33,一般情况乘以0.4    int maxSize =        getMaxSize(            builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);    int widthPixels = builder.screenDimensions.getWidthPixels();    int heightPixels = builder.screenDimensions.getHeightPixels();    // ARGB_8888 ,每个像素占用4个字节内存    // 计算屏幕这么大尺寸的图片占用内存大小    int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;    // 计算目标位图池内存大小    int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);    // 计算目标Lrucache内存大小,也就是屏幕尺寸图片大小乘以2    int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);    // 最终APP可用内存大小    int availableSize = maxSize - arrayPoolSize;    if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {      // 如果目标位图内存大小+目标Lurcache内存大小小于APP可用内存大小,则OK      memoryCacheSize = targetMemoryCacheSize;      bitmapPoolSize = targetBitmapPoolSize;    } else {      // 否则用APP可用内存大小等比分别赋值      float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);      memoryCacheSize = Math.round(part * builder.memoryCacheScreens);      bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);    }    if (Log.isLoggable(TAG, Log.DEBUG)) {      Log.d(          TAG,          "Calculation complete"              + ", Calculated memory cache size: "              + toMb(memoryCacheSize)              + ", pool size: "              + toMb(bitmapPoolSize)              + ", byte array size: "              + toMb(arrayPoolSize)              + ", memory class limited? "              + (targetMemoryCacheSize + targetBitmapPoolSize > maxSize)              + ", max size: "              + toMb(maxSize)              + ", memoryClass: "              + builder.activityManager.getMemoryClass()              + ", isLowMemoryDevice: "              + isLowMemoryDevice(builder.activityManager));    }  }   ...}


注释写得比较清楚了,就不解释了,那么Glide在哪开始使用缓存的呢?


public class Engine implements EngineJobListener,    MemoryCache.ResourceRemovedListener,    EngineResource.ResourceListener {  ...   public  LoadStatus load(      GlideContext glideContext,      Object model,      Key signature,      int width,      int height,      Class<?> resourceClass,      Class transcodeClass,      Priority priority,      DiskCacheStrategy diskCacheStrategy,      Map, Transformation<?>> transformations,      boolean isTransformationRequired,      boolean isScaleOnlyOrNoTransform,      Options options,      boolean isMemoryCacheable,      boolean useUnlimitedSourceExecutorPool,      boolean useAnimationPool,      boolean onlyRetrieveFromCache,      ResourceCallback cb) {    Util.assertMainThread();    long startTime = LogTime.getLogTime();    // 根据各种参数创建图片key    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,        resourceClass, transcodeClass, options);    // 检查内存中弱引用是否有目标图片    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);    if (active != null) {      cb.onResourceReady(active, DataSource.MEMORY_CACHE);      if (Log.isLoggable(TAG, Log.VERBOSE)) {        logWithTimeAndKey("Loaded resource from active resources", startTime, key);      }      return null;    }    // 检查内存中Lrucache是否有目标图片    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);    if (cached != null) {      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);      if (Log.isLoggable(TAG, Log.VERBOSE)) {        logWithTimeAndKey("Loaded resource from cache", startTime, key);      }      return null;    }    // 内存中没有图片构建任务往下执行    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);    if (current != null) {      current.addCallback(cb);      if (Log.isLoggable(TAG, Log.VERBOSE)) {        logWithTimeAndKey("Added to existing load", startTime, key);      }      return new LoadStatus(cb, current);    }    EngineJob engineJob =        engineJobFactory.build(            key,            isMemoryCacheable,            useUnlimitedSourceExecutorPool,            useAnimationPool,            onlyRetrieveFromCache);    DecodeJob decodeJob =        decodeJobFactory.build(            glideContext,            model,            key,            signature,            width,            height,            resourceClass,            transcodeClass,            priority,            diskCacheStrategy,            transformations,            isTransformationRequired,            isScaleOnlyOrNoTransform,            onlyRetrieveFromCache,            options,            engineJob);    jobs.put(key, engineJob);    engineJob.addCallback(cb);    engineJob.start(decodeJob);    if (Log.isLoggable(TAG, Log.VERBOSE)) {      logWithTimeAndKey("Started new load", startTime, key);    }    return new LoadStatus(cb, engineJob);  }  ...}

Engine的load()方法还熟悉吧,很长一串

28-29,创建图片URL、宽、高等一系列参数创建key

31-39,从内存缓存弱引用中根据Key获取图片资源,有就返回,没有往下执行

41-49,从内存缓存Lrucache中根绝key获取图片资源,有就返回,没有往下执行

通过以上代码可以看出Glide内存缓存采用了2级,第一级是弱引用,第二级才是Lrucache,如果软引用中没有

对应的图片缓存,就从Lrucache中获取,如果还是没有才去检查磁盘缓存,如果还是没有最后才去网络下载


这里来看下获取内存缓存图片资源的方法loadFromActiveResources()和loadFromCache(),

首先跟踪loadFromActiveResources()

public class Engine implements EngineJobListener,    MemoryCache.ResourceRemovedListener,    EngineResource.ResourceListener {  ...  @Nullable  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {    if (!isMemoryCacheable) {      return null;    }    EngineResource<?> active = activeResources.get(key);    if (active != null) {      active.acquire();    }    return active;  }   ...}final class ActiveResources {  ...  @Nullable  EngineResource<?> get(Key key) {    ResourceWeakReference activeRef = activeEngineResources.get(key);    if (activeRef == null) {      return null;    }    EngineResource<?> active = activeRef.get();    if (active == null) {      cleanupActiveReference(activeRef);    }    return active;  }  ...}

这里是从内存缓存的弱引用中获取的相应图片资源

第7行,判断缓存是否开启,如果可用才继续进行,否则返回null,默认是开启的,我们也可以使用的时候关闭,如下

      ImgurGlide.with(vh.imageView)
          .load(image.link)
          .skipMemoryCache(true)

          .into(vh.imageView);

第10行,根据图片key获取软引用图片资源active,如果不为null,则调用active.acquire(),返回相应资源

这里要注意2个方法acquire()和release()

class EngineResource implements Resource {  ...  void acquire() {    if (isRecycled) {      throw new IllegalStateException("Cannot acquire a recycled resource");    }    if (!Looper.getMainLooper().equals(Looper.myLooper())) {      throw new IllegalThreadStateException("Must call acquire on the main thread");    }    ++acquired;  }  /**   * Decrements the number of consumers using the wrapped resource. Must be called on the main   * thread.   *   * 

This must only be called when a consumer that called the {@link #acquire()} method is now * done with the resource. Generally external users should never call this method, the framework * will take care of this for you. */ void release() { if (acquired <= 0) { throw new IllegalStateException("Cannot release a recycled or not yet acquired resource"); } if (!Looper.getMainLooper().equals(Looper.myLooper())) { throw new IllegalThreadStateException("Must call release on the main thread"); } if (--acquired == 0) { listener.onResourceReleased(key, this); } } ...}

第10行和第28行,可以看到当调用acquire(),acquired这个成员变量会自增1,当调用release(),acquired这个

成员变量会自减1,当acquired数量大于0,说明当前EngineResource实例被使用中


全局搜索可以知道当前资源加载结束后,会调用release(),看下29行,一个回调,找到实现的类Engine

public class Engine implements EngineJobListener,    MemoryCache.ResourceRemovedListener,    EngineResource.ResourceListener {  ...  @Override  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {    Util.assertMainThread();    // 从内存弱引用中移除图片    activeResources.deactivate(cacheKey);    if (resource.isCacheable()) {      // 内存Lrucache中添加图片      cache.put(cacheKey, resource);    } else {      resourceRecycler.recycle(resource);    }  }  ...}

第9行,从内存弱引用中移除图片

第12行,内存Lrucache中添加此图片


接下来看下loadFromCache()方法

public class Engine implements EngineJobListener,    MemoryCache.ResourceRemovedListener,    EngineResource.ResourceListener {  ...  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {    if (!isMemoryCacheable) {      return null;    }    EngineResource<?> cached = getEngineResourceFromCache(key);    if (cached != null) {      cached.acquire();      activeResources.activate(key, cached);    }    return cached;  }  @SuppressWarnings("unchecked")  private EngineResource<?> getEngineResourceFromCache(Key key) {    //cache 是LruResourceCache实例    Resource<?> cached = cache.remove(key);    final EngineResource<?> result;    if (cached == null) {      result = null;    } else if (cached instanceof EngineResource) {      // Save an object allocation if we've cached an EngineResource (the typical case).      result = (EngineResource<?>) cached;    } else {      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);    }    return result;  }  ...}

第6行,判断是否开启内存缓存,默认是开启的

10-15行,从内存Lrucache缓存中获取key对应的图片资源并返回

第12行,调用acquire(),同理当前资源调用数+1

第13行,跟踪进入ActiveResources类

final class ActiveResources {  ...  void activate(Key key, EngineResource<?> resource) {    ResourceWeakReference toPut =        new ResourceWeakReference(            key,            resource,            getReferenceQueue(),            isActiveResourceRetentionAllowed);    ResourceWeakReference removed = activeEngineResources.put(key, toPut);    if (removed != null) {      removed.reset();    }  }  ...}

这里把Lrucache缓存中的资源存入弱引用缓存

到这里其实应该比较清晰了,Glide加载的内存缓存的图片资源始终是软引用的,当引用中有则返回资源,且资源存入

Lrucache中,如果没有,从Lrucache中返回存入弱引用中,如果Lrucache中也没有呢?


以上就是Glide内存缓存的源码分析,接下来开始磁盘缓存的分析


public abstract class DiskCacheStrategy {  ...  public static final DiskCacheStrategy ALL = new DiskCacheStrategy() {    @Override    public boolean isDataCacheable(DataSource dataSource) {      return dataSource == DataSource.REMOTE;    }    @Override    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,        EncodeStrategy encodeStrategy) {      return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;    }    @Override    public boolean decodeCachedResource() {      return true;    }    @Override    public boolean decodeCachedData() {      return true;    }  };  ...}

磁盘缓存策略,DiskCacheStrategy这个类定义了几种策略类型

DiskCacheStrategy.ALL : 

表示既缓存原始图片,也缓存转换过后的图片。对于远程图片,缓存DATA和RESOURCE。对于本地图片,只缓存RESOURCE。


DiskCacheStrategy.AUTOMATIC (默认策略):

它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据(DATA),因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图(RESOURCE),因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。


DiskCacheStrategy.DATA:

表示只缓存未被处理的文件。我的理解就是我们获得的stream。它是不会被展示出来的,需要经过装载decode,对图片进行压缩和转换,等等操作,得到最终的图片才能被展示。


DiskCacheStrategy.NONE: 

表示不缓存任何内容。


DiskCacheStrategy.RESOURCE:

表示只缓存转换过后的图片。(也就是经过decode,转化裁剪的图片)


默认的策略为DiskCacheStrategy.AUTOMATIC,改变策略也很简单, 比如

    ImgurGlide.with(vh.imageView)
          .load(image.link)
          .diskCacheStrategy(DiskCacheStrategy.ALL)

          .into(vh.imageView);


我们这里就用默认磁盘缓存策略分析


通过上一篇的加载流程,我们可以直接找到DecodeJob类,获取磁盘缓存的步骤就在里面

class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,    Runnable,    Comparable>,    Poolable {  ...  private void runWrapped() {     switch (runReason) {      case INITIALIZE:        stage = getNextStage(Stage.INITIALIZE);        currentGenerator = getNextGenerator();        runGenerators();        break;      case SWITCH_TO_SOURCE_SERVICE:        runGenerators();        break;      case DECODE_DATA:        decodeFromRetrievedData();        break;      default:        throw new IllegalStateException("Unrecognized run reason: " + runReason);    }  }  private DataFetcherGenerator getNextGenerator() {    switch (stage) {      case RESOURCE_CACHE:        return new ResourceCacheGenerator(decodeHelper, this);      case DATA_CACHE:        return new DataCacheGenerator(decodeHelper, this);      case SOURCE:        return new SourceGenerator(decodeHelper, this);      case FINISHED:        return null;      default:        throw new IllegalStateException("Unrecognized stage: " + stage);    }  } private Stage getNextStage(Stage current) {    switch (current) {      case INITIALIZE:        // 检查磁盘缓存策略,是否解码缓存的转换图片        return diskCacheStrategy.decodeCachedResource()            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);      case RESOURCE_CACHE:        // 检查磁盘缓存策略,是否解码缓存的原始数据,这里理解为流        return diskCacheStrategy.decodeCachedData()            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);      case DATA_CACHE:        // Skip loading from source if the user opted to only retrieve the resource from cache.        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;      case SOURCE:      case FINISHED:        return Stage.FINISHED;      default:        throw new IllegalArgumentException("Unrecognized stage: " + current);    }  }  ...}

这3个方法,非常绕,

30-58行,这段代码的逻辑就是根据磁盘缓存策略把检查步骤往下推,第一步检查转换过的图片是否有缓存,

第二步检查未被转化过的图片资源缓存,第三步检查数据源,比如本地相册原始图片,网络URL原始图片。

如果目标图片已经加载过缓存在磁盘了,但是内存缓存中还没有,那么其实在第一步检查中就会获取到,然后加载显示。

这也是大多数的情况加载磁盘缓存过的图片资源,这里以这种情况为例继续分析


class ResourceCacheGenerator implements DataFetcherGenerator,    DataFetcher.DataCallback {  ...  @Override  public boolean startNext() {    List sourceIds = helper.getCacheKeys();    if (sourceIds.isEmpty()) {      return false;    }    List> resourceClasses = helper.getRegisteredResourceClasses();    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);      currentKey =          new ResourceCacheKey(              helper.getArrayPool(),              sourceId,              helper.getSignature(),              helper.getWidth(),              helper.getHeight(),              transformation,              resourceClass,              helper.getOptions());      cacheFile = helper.getDiskCache().get(currentKey);      if (cacheFile != null) {        this.sourceKey = sourceId;        modelLoaders = helper.getModelLoaders(cacheFile);        modelLoaderIndex = 0;      }    }    loadData = null;    boolean started = false;    while (!started && hasNextModelLoader()) {      ModelLoader 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;  }  ...}   

直接跟踪类ResourceCacheGenerator

25-34,根据图片URL生成一个磁盘缓存ResourceCacheKey

第35行,根据这个key获取磁盘缓存图片File

第38行,根据这个File获取初始化Glide时注册的ModelLoaders,猜也猜得到大概是哪几个了,必定是其中一个

        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())        .append(File.class, InputStream.class, new FileLoader.StreamFactory())        .append(File.class, File.class, new FileDecoder())        .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.        .append(File.class, File.class, UnitModelLoader.Factory.getInstance())

这里公布下答案,ByteBufferFileLoader,

接着第52行,根据这个ByteBufferFileLoader去加载磁盘缓存图片文件然后一层一层回调回去显示


其实到这里Glide磁盘缓存就差不多了,接下来继续分析缓存图片在哪存入和获取的呢

第35行,这里就是根据ResourceCacheKey获取磁盘缓存图片File,


在哪存储的呢?


class SourceGenerator implements DataFetcherGenerator,    DataFetcher.DataCallback,    DataFetcherGenerator.FetcherReadyCallback {  ...  @Override  public boolean startNext() {    if (dataToCache != null) {      Object data = dataToCache;      dataToCache = null;      cacheData(data);    }    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;  }  ...}   

第10行,当从网络下载后会执行到这里,这里就是存储到磁盘的起点

跟踪进入,会发现获取磁盘文件和存储都会调用helper.getDiskCache(),接着就先弄清楚它

final class DecodeHelper {   ...   DiskCache getDiskCache() {      return diskCacheProvider.getDiskCache();   }   ...}
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,    Runnable,    Comparable>,    Poolable {  ...  interface DiskCacheProvider {    DiskCache getDiskCache();  }  ...}

这里首先要知道diskCacheProvider是哪个类的实例,从上面的代码可知是DiskcacheProvider接口的一个实现类,

通过全局搜索可以知道只有一个实现类LazyDiskCacheProvider,那么diskCacheProvider肯定就是

LazyDiskCacheProvider的实例了

public class Engine implements EngineJobListener,    MemoryCache.ResourceRemovedListener,    EngineResource.ResourceListener {  ...  private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {      @Override    ...    public DiskCache getDiskCache() {      if (diskCache == null) {        synchronized (this) {          if (diskCache == null) {            diskCache = factory.build();          }          if (diskCache == null) {            diskCache = new DiskCacheAdapter();          }        }      }      return diskCache;    }  }  ...}

第12行,可以知道从外面传了一个factory创建了一个DiskCache的实例,DiskCache是一个接口,全局搜索可以知道DiskCache

有2个实现类DiskLruCacheWrapper、DiskCacheAdapter,从上面的代码分析肯定不是DiskCacheAdapter的实例,如果

是就不会有15行的创建DiskCacheAdapter实例,因此,可以推断第12行创建的是DiskLruCacheWrapper实例,这里可以透露下

其实磁盘缓存的读写都是这个实例里面实现的,不过先不看这个类,我们先要知道这个factory从哪传入的


public final class GlideBuilder {  ...  public Glide build(Context context) {    // 开始初始化glide    ...    if (diskCacheFactory == null) {      diskCacheFactory = new InternalCacheDiskCacheFactory(context);    }    if (engine == null) {      engine =          new Engine(              memoryCache,              diskCacheFactory,              diskCacheExecutor,              sourceExecutor,              GlideExecutor.newUnlimitedSourceExecutor(),              GlideExecutor.newAnimationExecutor(),              isActiveResourceRetentionAllowed);    }   ...  }  ...}

还记得初始Glide实例那里吗,第8行,可以看到默认情况下初始化磁盘存储,然后InternalCacheDiskCacheFactory实例一级一级

往下传InternalCacheDiskCacheFactory

public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {  public InternalCacheDiskCacheFactory(Context context) {    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,        DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);  }  public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) {    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);  }  public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,                                       long diskCacheSize) {    super(new CacheDirectoryGetter() {      @Override      public File getCacheDirectory() {        File cacheDirectory = context.getCacheDir();        if (cacheDirectory == null) {          return null;        }        if (diskCacheName != null) {          return new File(cacheDirectory, diskCacheName);        }        return cacheDirectory;      }    }, diskCacheSize);  }}

第4-5 行,这里的2个参数默认是磁盘内部存储缓存目录、磁盘缓存大小为250M,构造方法最后调用了super构造方法

16-25行,重写了getCacheDirectory(),这个方法的作用就是创建磁盘内部缓存目录,接着跟踪super

public class DiskLruCacheFactory implements DiskCache.Factory {  ...  public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long            diskCacheSize) {    this.diskCacheSize = diskCacheSize;    this.cacheDirectoryGetter = cacheDirectoryGetter;  }  @Override  public DiskCache build() {    File cacheDir = cacheDirectoryGetter.getCacheDirectory();    if (cacheDir == null) {      return null;    }    if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {      return null;    }    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);  }  ...}

第10-21行,有个build()方法,用于创建DiskLruCacheWrapper实例,与之前的呼应了吧,跟踪第21行,

可以知道创建DiskLruCacheWrapper实例

public class DiskLruCacheWrapper implements DiskCache {  ...  public static DiskCache create(File directory, long maxSize) {    return new DiskLruCacheWrapper(directory, maxSize);  }  ...}

到这里应该已经清楚了当存储或者获取磁盘缓存图片资源时用到的DiskCache就是DiskLruCacheWrapper实例,

接下来分析DiskLruCacheWrapper类

public class DiskLruCacheWrapper implements DiskCache {  ...  protected DiskLruCacheWrapper(File directory, long maxSize) {     this.directory = directory;                                     this.maxSize = maxSize;                                         this.safeKeyGenerator = new SafeKeyGenerator();               }                                                               ...}

这是DiskLruCacheWrapper的构造方法,把缓存目录和大小传入作为成员变量

第6行,构造SafeKeyGenerator实例,跟踪

public class SafeKeyGenerator {  ...  private final LruCache loadIdToSafeHash = new LruCache<>(1000);  private final Pools.Pool digestPool =        FactoryPools.threadSafe(10,      new FactoryPools.Factory() {        @Override        public PoolableDigestContainer create() {          try {            return new PoolableDigestContainer(MessageDigest.getInstance("SHA-256"));          } catch (NoSuchAlgorithmException e) {            throw new RuntimeException(e);          }        }      });                                                            ...}

SafeKeyGenerator是一个使用LruCache算法保存磁盘缓存图片加密名称的一个工具,这里就不深入分析它了,

接下来看DiskLruCacheWrapper的put()和get(),先跟踪get()

public class DiskLruCacheWrapper implements DiskCache {     ...    @Override  public File get(Key key) {    String safeKey = safeKeyGenerator.getSafeKey(key);    if (Log.isLoggable(TAG, Log.VERBOSE)) {      Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key);    }    File result = null;    try {      // It is possible that the there will be a put in between these two gets. If so that shouldn't      // be a problem because we will always put the same value at the same key so our input streams      // will still represent the same data.      final DiskLruCache.Value value = getDiskCache().get(safeKey);      if (value != null) {        result = value.getFile(0);      }    } catch (IOException e) {      if (Log.isLoggable(TAG, Log.WARN)) {        Log.w(TAG, "Unable to get from disk cache", e);      }    }    return result;  }                                                                                                          ...}

第5行,加密key,比如,httpxxx 加密后为 32fgdg44r2xx,

第14行,获取磁盘缓存图片文件,接着返回,这里又是难点的开始了,跟踪进去

public class DiskLruCacheWrapper implements DiskCache {     ...  private synchronized DiskLruCache getDiskCache() throws IOException {                     if (diskLruCache == null) {                                                               diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);       }                                                                                       return diskLruCache;                                                                  }                                                            ...}public final class DiskLruCache implements Closeable {   ...  public static DiskLruCache open(File directory, int appVersion, int valueCount, long       maxSize)      throws IOException {    if (maxSize <= 0) {      throw new IllegalArgumentException("maxSize <= 0");    }    if (valueCount <= 0) {      throw new IllegalArgumentException("valueCount <= 0");    }    // If a bkp file exists, use it instead.    File backupFile = new File(directory, JOURNAL_FILE_BACKUP);    if (backupFile.exists()) {      File journalFile = new File(directory, JOURNAL_FILE);      // If journal file also exists just delete backup file.      if (journalFile.exists()) {        backupFile.delete();      } else {        renameTo(backupFile, journalFile, false);      }    }    // Prefer to pick up where we left off.    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);    if (cache.journalFile.exists()) {      try {        cache.readJournal();        cache.processJournal();        return cache;      } catch (IOException journalIsCorrupt) {        System.out            .println("DiskLruCache "                + directory                + " is corrupt: "                + journalIsCorrupt.getMessage()                + ", removing");        cache.delete();      }    }    // Create a new empty cache.    directory.mkdirs();    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);    cache.rebuildJournal();    return cache;  }                                                                     ...}

第5行,DiskLruCache.open()构建DiskLruCache实例

24-34行,创建日志文件和备份,

第37行,创建DiskLruCache实例

第40行,如果日志文件存在,开始读取日志文件内容,跟踪

public final class DiskLruCache implements Closeable {   ...   private void readJournal() throws IOException {    StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);    try {      String magic = reader.readLine();      String version = reader.readLine();      String appVersionString = reader.readLine();      String valueCountString = reader.readLine();      String blank = reader.readLine();      if (!MAGIC.equals(magic)          || !VERSION_1.equals(version)          || !Integer.toString(appVersion).equals(appVersionString)          || !Integer.toString(valueCount).equals(valueCountString)          || !"".equals(blank)) {        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "            + valueCountString + ", " + blank + "]");      }      int lineCount = 0;      while (true) {        try {          readJournalLine(reader.readLine());          lineCount++;        } catch (EOFException endOfJournal) {          break;        }      }      redundantOpCount = lineCount - lruEntries.size();      // If we ended on a truncated line, rebuild the journal before appending to it.      if (reader.hasUnterminatedLine()) {        rebuildJournal();      } else {        journalWriter = new BufferedWriter(new OutputStreamWriter(            new FileOutputStream(journalFile, true), Util.US_ASCII));      }    } finally {      Util.closeQuietly(reader);    }  }  private void readJournalLine(String line) throws IOException {    int firstSpace = line.indexOf(' ');    if (firstSpace == -1) {      throw new IOException("unexpected journal line: " + line);    }    int keyBegin = firstSpace + 1;    int secondSpace = line.indexOf(' ', keyBegin);    final String key;    if (secondSpace == -1) {      key = line.substring(keyBegin);      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {        lruEntries.remove(key);        return;      }    } else {      key = line.substring(keyBegin, secondSpace);    }    Entry entry = lruEntries.get(key);    if (entry == null) {      entry = new Entry(key);      lruEntries.put(key, entry);    }    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {      String[] parts = line.substring(secondSpace + 1).split(" ");      entry.readable = true;      entry.currentEditor = null;      entry.setLengths(parts);    } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {      entry.currentEditor = new Editor(entry);    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {      // This work was already done by calling lruEntries.get().    } else {      throw new IOException("unexpected journal line: " + line);    }  }                                       ...}

6-10行,从日志文件中读取介绍信息,版本号啊神马的

20-28行,循环读取剩下的日志信息,当读取到末尾退出循环,读取到的信息放入lruEntries中

44-60行,读取日志信息,从读取到的一行信息截取空格间的字符串,也就是加密key

62-65行,lruEntries开始存Entry对象,Entry对象又包含上面读取出来的加密key,lruEntries很多地方都会用到

第64行,跟踪

public final class DiskLruCache implements Closeable {   ...  private final class Entry {    private Entry(String key) {      this.key = key;      this.lengths = new long[valueCount];      cleanFiles = new File[valueCount];      dirtyFiles = new File[valueCount];      // The names are repetitive so re-use the same builder to avoid allocations.      StringBuilder fileBuilder = new StringBuilder(key).append('.');      int truncateTo = fileBuilder.length();      for (int i = 0; i < valueCount; i++) {          fileBuilder.append(i);          cleanFiles[i] = new File(directory, fileBuilder.toString());          fileBuilder.append(".tmp");          dirtyFiles[i] = new File(directory, fileBuilder.toString());          fileBuilder.setLength(truncateTo);      }    }     }                     ...}

可以知道构造Entry实体的时候,根据读取日志中的加密key,其实也就是磁盘缓存图片文件名,生成file对象作为Entry实体

成员变量

接着看DiskLruCache的get()方法

public final class DiskLruCache implements Closeable {   ... public synchronized Value get(String key) throws IOException {    checkNotClosed();    Entry entry = lruEntries.get(key);    if (entry == null) {      return null;    }    if (!entry.readable) {      return null;    }    for (File file : entry.cleanFiles) {        // A file must have been deleted manually!        if (!file.exists()) {            return null;        }    }    redundantOpCount++;    journalWriter.append(READ);    journalWriter.append(' ');    journalWriter.append(key);    journalWriter.append('\n');    if (journalRebuildRequired()) {      executorService.submit(cleanupCallable);    }    return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);  }                              ...}

第5行,从lruEntries中根据加密key获取Entry对象,如果有,往下执行

14-19行,从Entry对象获取缓存文件,如果存在往下执行

第30行,如果缓存文件存在构建一个Value实体对象,包含缓存文件返回

到这里获取磁盘缓存图片就差不多了,接下来看下DiskLruCacheWrapper.put(),存储磁盘缓存图片文件

public class DiskLruCacheWrapper implements DiskCache {  ...  @Override  public void put(Key key, Writer writer) {    // We want to make sure that puts block so that data is available when put completes. We may    // actually not write any data if we find that data is written by the time we acquire the lock.    // 返回加密后的key,采用Lrucache算法    String safeKey = safeKeyGenerator.getSafeKey(key);    writeLocker.acquire(safeKey);    try {      if (Log.isLoggable(TAG, Log.VERBOSE)) {        Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);      }      try {        // We assume we only need to put once, so if data was written while we were trying to get        // the lock, we can simply abort.        DiskLruCache diskCache = getDiskCache();        Value current = diskCache.get(safeKey);        if (current != null) {          return;        }        DiskLruCache.Editor editor = diskCache.edit(safeKey);        if (editor == null) {          throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);        }        try {          File file = editor.getFile(0);          if (writer.write(file)) {            editor.commit();          }        } finally {          editor.abortUnlessCommitted();        }      } catch (IOException e) {        if (Log.isLoggable(TAG, Log.WARN)) {          Log.w(TAG, "Unable to put to disk cache", e);        }      }    } finally {      writeLocker.release(safeKey);    }  }                                                                                                                                 ...}

17-21行,跟get()差不多,先获取DiskLruCache实例,然后get(key)获取缓存文件,如果存在,就返回null,表示不需要存储,

              如果没有就继续执行

第23行,很重要,先跟踪,一会再回到这里

public final class DiskLruCache implements Closeable {  ...  /**   * Returns an editor for the entry named {@code key}, or null if another   * edit is in progress.   */  public Editor edit(String key) throws IOException {    return edit(key, ANY_SEQUENCE_NUMBER);  }  private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {    checkNotClosed();    Entry entry = lruEntries.get(key);    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null        || entry.sequenceNumber != expectedSequenceNumber)) {      return null; // Value is stale.    }    if (entry == null) {      entry = new Entry(key);      lruEntries.put(key, entry);    } else if (entry.currentEditor != null) {      return null; // Another edit is in progress.    }    Editor editor = new Editor(entry);    entry.currentEditor = editor;    // Flush the journal before creating files to prevent file leaks.    journalWriter.append(DIRTY);    journalWriter.append(' ');    journalWriter.append(key);    journalWriter.append('\n');    journalWriter.flush();    return editor;  }                                                                                                                    ...}

13-23行,lruEntrie熟悉了吧,又来了哦,判断存储有加密key没有,没有就构建Entry实体实例把加密key存入,跟前面

get()逻辑一样的

25-34行,根据加密key构建一串特定字符串,然后写入磁盘日志文件中,这个日志用于记录缓存图片文件名称的,下次获取

缓存文件就从这个日志读取名称,前面get()部分分析过了

接着回到DiskLruCacheWrapper.put()

public class DiskLruCacheWrapper implements DiskCache {  ...  @Override  public void put(Key key, Writer writer) {    // We want to make sure that puts block so that data is available when put completes. We may    // actually not write any data if we find that data is written by the time we acquire the lock.    // 返回加密后的key,采用Lrucache算法    String safeKey = safeKeyGenerator.getSafeKey(key);    writeLocker.acquire(safeKey);    try {      if (Log.isLoggable(TAG, Log.VERBOSE)) {        Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);      }      try {        // We assume we only need to put once, so if data was written while we were trying to get        // the lock, we can simply abort.        DiskLruCache diskCache = getDiskCache();        Value current = diskCache.get(safeKey);        if (current != null) {          return;        }        DiskLruCache.Editor editor = diskCache.edit(safeKey);        if (editor == null) {          throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);        }        try {          File file = editor.getFile(0);          if (writer.write(file)) {            editor.commit();          }        } finally {          editor.abortUnlessCommitted();        }      } catch (IOException e) {        if (Log.isLoggable(TAG, Log.WARN)) {          Log.w(TAG, "Unable to put to disk cache", e);        }      }    } finally {      writeLocker.release(safeKey);    }  }                                                                                                                             ...}

27-32行,根据之前返回的DiskLruCache.Editor实例,获取需要磁盘缓存的图片文件名称,

然后写入磁盘好了,到这里磁盘的存和取都分析了一遍,相信也比较清楚了,

最后几句话总结下原理


根据传入的URL和相关参数组成一个key,然后从内存缓存的弱引用中查找是否相应的图片资源,如果有

就返回,没有就从内存缓存的Lrucache缓存中查找,如果有就返回并存入弱引用缓存,如果没有就去查看

磁盘缓存,磁盘缓存有几种策略,常用的策略还是缓存转换后的图片,先加密key,然后去磁盘日志文件中

查找有没有记录,如果有就根据记录的文件名称获取图片文件并返回,如果没有记录,就从网络获取图片

资源流,根据加密key创建一份记录写入日志文件中,并把图片资源流写入磁盘缓存中


欢迎大家指教,其中还是有很多不是很明白,好了,到这里又可以愉快的玩耍了


Android 图片加载框架Glide主流程源码分析


更多相关文章

  1. AndroidのBitmap之大图片优化
  2. android大图片显示
  3. Android入门第十二篇之Gallery
  4. Android技术积累:图片缓存管理
  5. 更换android的初始化图片
  6. Android(安卓)开发注意事项
  7. 【转】【Android】获取手机中已安装apk文件信息(PackageInfo、Re
  8. Android使用ImageLoader异步加载网络图片(二)结合listview
  9. Android图片放大缩小实现方式(一)

随机推荐

  1. 采用XMPP协议实现Android推送
  2. 系出名门Android(1) - 在 Windows 下搭建
  3. Android应用程序与SurfaceFlinger服务的
  4. androidのEditTex详细使用
  5. Android的系统架构
  6. Android开发屏幕适配
  7. Android联系人数据库全解析(2)
  8. android ndk log
  9. Android绘制优化----系统显示原理
  10. Android学习笔记:Android消息处理机制之Ha