精华内容
下载资源
问答
  • 2021-01-14 01:18:11

    使用到的技术:

    rxjava:可以用新开线程代替即可。我的主要目的是方便切换线程

    glide:图片下载

    使用场景:我的表情包是从接口获取的,但是在发送大表情的时候,融云需要接口参数中必须提供本地文件的Uri,因此需要将该图的缓存路径找出来,并转为Uri。

    注意事项:

    Flowable的.subscribeOn(Schedulers.newThread())和observeOn(AndroidSchedulers.mainThread())方法,observeOn观察者是在主线程,请求网络下载是在子线程。

    Disposable disposable = Flowable.create(new FlowableOnSubscribe() {

    @Override

    public void subscribe(FlowableEmitter emitter) throws Exception {

    try {

    FutureTarget target = Glide.with(context.getApplicationContext())

    .downloadOnly()

    .load(emoji.getUrl())

    .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);

    File imageFile = target.get();

    emitter.onNext(imageFile);

    } catch (ExecutionException e) {

    e.printStackTrace();

    emitter.onComplete();

    } catch (InterruptedException e) {

    e.printStackTrace();

    emitter.onComplete();

    }

    }

    }, BackpressureStrategy.BUFFER)

    .subscribeOn(Schedulers.newThread())//指定在子线程中执行下载/取缓存图片路径

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(new Consumer() {

    @Override

    public void accept(File file) throws Exception {

    Uri remoteUri = Uri.parse(emoji.getUrl());

    Uri localUri = Uri.fromFile(file);

    ImageMessage imageMessage = ImageMessage.obtain(remoteUri, localUri);

    RongIMClient.getInstance().sendImageMessage(Conversation.ConversationType.PRIVATE, String.valueOf(mTargetUser.getUserId()),

    imageMessage, "", "", new RongIMClient.SendImageMessageCallback() {

    @Override

    public void onAttached(Message message) {

    }

    @Override

    public void onError(Message message, RongIMClient.ErrorCode errorCode) {

    }

    @Override

    public void onSuccess(Message message) {

    Log.i(TAG, "发送成功,图片为:remoteUri=" + remoteUri + ";\nlocalUri="+localUri);

    }

    @Override

    public void onProgress(Message message, int i) {

    }

    });

    }

    });

    更多相关内容
  • 主要介绍了Android中Glide获取缓存大小并清除缓存图片,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 本篇文章主要介绍了Android中Glide加载图片并实现图片缓存,这里和大家简单的分享一下Glide的使用方法以及缓存 ,有兴趣的可以了解一下。
  • Glide缓存图片流程浅析

    千次阅读 2022-02-16 11:31:37
    如果没有缓存,在大量的网络请求从远程获取图片时会造成网络流量的浪费,尤其是面对高清大图的加载更是如此,为了节省带宽,也为了减少用户等待的时间,合理的缓存方式必不可少,这也是Glide图片框架的强大之处。...

    如果没有缓存,在大量的网络请求从远程获取图片时会造成网络流量的浪费,尤其是面对高清大图的加载更是如此,为了节省带宽,也为了减少用户等待的时间,合理的缓存方式必不可少,这也是Glide图片框架的强大之处。另外Glide的缓存机制可以说是非常高频的问题,Glide有几级缓存?Glide读取缓存的顺序和时机是什么?Glide存放缓存的顺序和时机又是什么?

    1.Glide中缓存概念简述

    a29aa5505376d623a985b14658142521.jpg

    Glide中的缓存分为两部分,内存缓存和硬盘缓存。

    1.1 内存缓存

    内存缓存又分为两级,一级是LruCache缓存,一级是弱引用缓存。

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

    LruCache缓存:不在使用中的图片使用LruCache来进行缓存。

    弱引用缓存:把正在使用中的图片使用弱引用来进行缓存

    为什么设计弱引用缓存?

    因为内存缓存使用LRU算法,当你使用Gilde加载并显示第一张图片时,后面又加载了很多图片,同时你的第一张图片还在用。这个时候内存缓存根据LRU算法可能会删除你正在使用的第一张照片。这样的后果就是你正在使用的照片找不到,后果就是程序崩溃。

    1.2 硬盘缓存

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

    2.缓存源码流程

    memory cache和disk cache在Glide创建的时候也被创建了,并传给了Engine引擎类,Glide创建的代码在GlideBuilder.build(Context)方法。

    @NonNull 
    Glide build(@NonNull Context context) { 
      if (memoryCache == null) { 
        memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); 
      } 
      if (diskCacheFactory == null) { 
        diskCacheFactory = new InternalCacheDiskCacheFactory(context); 
      } 
      if (engine == null) { 
        engine = 
            new Engine( 
                memoryCache, 
                diskCacheFactory, 
                ...); 
      } 
      return new Glide( 
          ... 
          memoryCache, 
          ...); 
    } 
    

    1. 内存缓存–memoryCache

    通过代码可以看到 memoryCache 被放入 Engine 和 Glide 实例中。在Engine中利用memoryCache进行存取操作,Glide 实例中的memoryCache是用来在内存紧张的时候,通知memoryCache释放内存。

    Glide如何监听内存紧张?

    当应用内存不足的时候,会回调ComponentCallbacks2接口的void onTrimMemory(@TrimMemoryLevel int level);或者ComponentCallbacks接口的void onLowMemory();;ComGlide实现了ComponentCallbacks2接口(ComponentCallback2继承了ComponentCallback),在Glide创建完成后,通过```applicationContext.registerComponentCallbacks(glide)``将其注册进Application,因此Glide 实例可以监听内存紧张的信号。

    @Override
      public void onLowMemory() {
        clearMemory();
      }
    
    public void clearMemory() {
        // Engine asserts this anyway when removing resources, fail faster and consistently
        Util.assertMainThread();
        // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
        memoryCache.clearMemory();
        bitmapPool.clearMemory();
        arrayPool.clearMemory();
      }
    
    // 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(); 
    for (RequestManager manager : managers) {
          manager.onTrimMemory(level);
        }
      // 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); 
    } 
    

    在onlowMemory 中Glide会将一些缓存的内存进行清除,方便进行内存回收,当onTrimMemory被调用的时候,如果level是系统资源紧张,Glide会将Lru缓存和BitMap重用池相关的内容进行回收。如果是其他的原因调用onTrimMemory,Glide会将缓存的内容减小到配置缓存最大内容的1/2。

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

    LruCache源码分析
        public class LruCache<K, V> {
    	// 数据最终存在 LinkedHashMap 中
        private final LinkedHashMap<K, V> map;
    	...
    	public LruCache(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
            this.maxSize = maxSize;
    		// 创建一个LinkedHashMap,accessOrder 传true
            this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
        }
        ...
    

    LruCache 构造方法里创建一个LinkedHashMap,accessOrder 参数传true,表示按照访问顺序排序,数据存储基于LinkedHashMap。

    LinkedHashMap在HashMap的数据结构基础上,添加了双向链表的数据结构,再加上对 LinkedHashMap 的数据操作上锁,就实现了LruCache的缓存策略,即最少最近访问。

    当调用 put()方法时,就会在集合中添加元素,并调用
    trimToSize()判断缓存是否已满,如果满了就用 LinkedHashMap 的迭代器删除队尾元素,即近期最少访问的元素。

    当调用 get()方法访问缓存对象时,就会调用 LinkedHashMap 的 get()方法获得对应集合元素,同时会更新该元素到队头。

    2.磁盘缓存

    diskCacheFactory是创建DiskCache的Factory,DiskCache接口定义。
    DiskCache.Factory的默认实现为InternalCacheDiskCacheFactory。在InternalCacheDiskCacheFactory中会默认会创建一个250M的缓存目录,其路径

    /data/data/{package}/cache/image_manager_disk_cache/。

    继续看其父类DiskLruCacheFactory的代码:

    public class DiskLruCacheFactory implements DiskCache.Factory { 
      // 省略部分代码
      @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。代码如下:

    # Engine.java
    Engine(
          MemoryCache cache,
          DiskCache.Factory diskCacheFactory,
          GlideExecutor diskCacheExecutor,
          GlideExecutor sourceExecutor,
          GlideExecutor sourceUnlimitedExecutor,
          GlideExecutor animationExecutor,
          Jobs jobs,
          EngineKeyFactory keyFactory,
          ActiveResources activeResources,
          EngineJobFactory engineJobFactory,
          DecodeJobFactory decodeJobFactory,
          ResourceRecycler resourceRecycler,
          boolean isActiveResourceRetentionAllowed) {
        this.cache = cache;
        this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);
        ...
    // 省略部分代码
    }
    
    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; 
        } 
      } 
    

    LazyDiskCacheProvider会在Engine后面的初始化流程中作为入参传到DecodeJobFactory的构造器。在DecodeJobFactory创建DecodeJob时也会作为入参会传进去,DecodeJob中会以全局变量保存此LazyDiskCacheProvider,在资源加载完毕并展示后,会进行缓存的存储。同时,DecodeJob也会在DecodeHelper初始化时,将此DiskCacheProvider设置进去,供ResourceCacheGenerator、DataCacheGenerator读取缓存,供SourceGenerator写入缓存。

    3. ActiveResources

    ActiveResources在Engine的构造器中被创建,在ActiveResources的构造器中会启动一个后台优先级级别(THREAD_PRIORITY_BACKGROUND)的线程,在该线程中会调用cleanReferenceQueue()方法一直循环清除ReferenceQueue中的将要被GC的Resource。

    ActiveResources(
          boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
        this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
        this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
    
        monitorClearedResourcesExecutor.execute(
            new Runnable() {
              @Override
              public void run() {
                cleanReferenceQueue();
              }
            });
      }
    

    先来看看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的一些属性。

    图片资源是什么时候从ActiveResource中缓存至内存的?

    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) { 
       // focus
        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方法代码如下:

    #ActiveResource.java
    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。

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

    也就是说当前图片资源未在使用中时,会先从ActiveResource的弱引用缓存中将其移除,然后将该资源添加到内存缓存中。

    缓存的存取过程

    内存缓存

    现在我们来分析下缓存的存取流程,加载开始入口从Engine.load()开始,先看下对这个方法的注释:

    • 会先检查(Active Resources),如果有就直接返回,Active Resources没有被引用的资源会放入Memory Cache,如果Active Resources没有,会往下走。
    • 检查Memory Cache中是否有需要的资源,如果有就返回,Memory Cache中没有就继续往下走。
    • 检查当前在运行中的job中是否有该资源的下载,有就在现有的job中直接添加callback返回,不重复下载,当然前提是计算得到的key是一致的,如果还是没有,就会构造一个新的job开始新的工作。
    public synchronized <R> LoadStatus load(...) { 
      EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, 
          resourceClass, transcodeClass, options); 
    // focus1
      EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); 
      if (active != null) { 
        cb.onResourceReady(active, DataSource.MEMORY_CACHE); 
        return null; 
      } 
    // focus2
      EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); 
      if (cached != null) { 
        cb.onResourceReady(cached, DataSource.MEMORY_CACHE); 
        return null; 
      } 
    // focus3
      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); 
    // focus 4
      engineJob.start(decodeJob); 
      return new LoadStatus(cb, engineJob); 
    } 
    

    先看到 focus 1,这一步会从 ActiveResources 中加载资源,首先判断是否使用内存缓存,否的话返回null;否则到 ActiveResources 中取数据:

    
    // Engine.java
     @Nullable
     private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
       if (!isMemoryCacheable) {
         return null;
       }
       EngineResource<?> active = activeResources.get(key);
       if (active != null) {
         active.acquire();
       }
     
       return active;
     }
    

    接着回到Engine.load()中继续看到focus 2,如果在cache中找到就是remove掉,然后返回EngineResource,其中需要EngineResource进行acquire一下,这个后面再看,然后会把资源移到ActiveResources中,也就是上面提到的缓存:

    // Engine.java
      private final MemoryCache cache;
     
      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;
      }
     
      private EngineResource<?> getEngineResourceFromCache(Key key) {
        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;
      }
    

    其中cache是MemoryCache接口的实现,如果没设置,默认在build的时候是LruResourceCache, 也就是熟悉的LRU Cache:

    
    // GlideBuilder.java
    if (memoryCache == null) {
       memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    

    关于LruCache,前面已经分析过了。

    回到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()的流程中,第一次加载的时候activeResources和memoryCache中都没有缓存的,判断当前是否有EngineJob在运行,,如果job已经在运行了,那么直接添加一个回调后返回LoadStatus,这个可以允许用户取消任务:

    // Engine.java
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
       current.addCallback(cb);
       if (VERBOSE_IS_LOGGABLE) {
          logWithTimeAndKey("Added to existing load", startTime, key);
        }
       return new LoadStatus(cb, current);
    }
     
    // LoadStatus
      public static class LoadStatus {
        private final EngineJob<?> engineJob;
        private final ResourceCallback cb;
     
        LoadStatus(ResourceCallback cb, EngineJob<?> engineJob) {
          this.cb = cb;
          this.engineJob = engineJob;
        }
     
        public void cancel() {
          engineJob.removeCallback(cb);
        }
      }
    

    接着往下看到focus 4, 到这里就需要创建后台任务去拉取磁盘文件或者发起网络请求。具体就是通过DecodeJob和EngineJob去加载资源。

    DecoceJob实现了Runnable接口,然后会被EngineJob.start方法提交到对应的线程池中去执行。在DecoceJob的run方法中,会依次从ResourceCacheGeneratorDataCacheGenerator中去取缓存数据,当这两者都取不到的情况下,会交给```SourceGenerator````加载网络图片或者本地资源。resource资源和data资源都是磁盘缓存中的资源。

    ResourceCacheGenerator 、DataCacheGenerator和SourceGenerator

    这里ResourceCacheGeneratorDataCacheGeneratorSourceGenerator均实现了DataFetcherGenerator接口。

    • ResourceCacheGenerator:从已经完成剪裁或者转换的资源缓存文件中生成DataFetchers;
    • DataCacheGenerator:从未修改的原始源数据的缓存文件中生成DataFetchers;
    • SourceGenerator:从原始数据中生成DataFetchers;

    这三个类中的前两个就是Glide三级缓存中的第二级缓存,文件缓存,前者ResourceCacheGenerator存储的是经过剪裁或转换的图片数据,后者DataCacheGenerator存储的就是未经任何修改的原始数据。正好对应着磁盘缓存策略中的RESOURCEDATA

    什么是DataFetcher?

    就是数据加载器,他有个loadData方法用来从资源加载数据,这个方法必须运行在子线程中,如果缓存中没有数据,就会通过这个方法去加载原始数据。
    举个例子,比如要通过一个网络url图片地址加载图片,那么需要通过SourceGenerator生成相应的DataFetcher通过网络获取数据;从网络上获取到数据流后,不进行任何改变会被存储到本地文件缓存中(这个本地文件默认的目录是context.getCacheDir()方法获取到的目录,文件大小250M),这个存储之前先修改runReason=SWITCH_TO_SOURCE_SERVICE,然后重启一个线程,通过SourceGenerator对象去做存储操作,而DataCacheGenerator就表示获取存储的数据,然后经过解码然后将数据显示到imageview。操作完成后会往内存中再存一份数据,那么ResourceCacheGenerator就表示获取这些已经经过解码剪裁的缓存文件。

    磁盘缓存

    先构造两个job,一个是EngineJob,另外一个DecodeJob,其中DecodeJob会根据需要解码的资源来源分成下面几个阶段:

    private enum Stage {
        /** The initial stage. */
        INITIALIZE,
        /** Decode from a cached resource. */
        RESOURCE_CACHE,
        /** Decode from cached source data. */
        DATA_CACHE,
        /** Decode from retrieved source. */
        SOURCE,
        /** Encoding transformed resources after a successful load. */
        ENCODE,
        /** No more viable stages. */
        FINISHED,
      }
    

    在构造DecodeJob时会把状态置为INITIALIZE。

    构造完两个 Job 后会调用 EngineJob.start

    (DecodeJob),首先会调用getNextStage来确定下一个阶段,这里面跟DiskCacheStrategy这个传入的磁盘缓存策略有关。

    磁盘策略有下面几种:

    • ALL: 缓存原始数据和转换后的数据
    • NONE: 不缓存
    • DATA: 原始数据,未经过解码或者转换
    • RESOURCE: 缓存经过解码的数据
    • AUTOMATIC(默认): 根据EncodeStrategyDataSource等条件自动选择合适的缓存方

    默认的AUTOMATIC方式是允许解码缓存的RESOURCE:

    public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
     // 省略部分代码
        @Override
        public boolean decodeCachedResource() {
          return true;
        }
     
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };
    

    所以在 getNextStage 会先返回

    Stage.RESOURCE_CACHE,然后在start中会返回diskCacheExecutor,然后开始执行DecodeJob:

    // EngineJob.java
    public void start(DecodeJob<R> decodeJob) {
        this.decodeJob = decodeJob;
        GlideExecutor executor = decodeJob.willDecodeFromCache()
            ? diskCacheExecutor
            : getActiveSourceExecutor();
     //到这里之前一直都在主线程,下面这句代码就会切换到子线程
        executor.execute(decodeJob);
    }
     
    // DecodeJob.java
      boolean willDecodeFromCache() {
        Stage firstStage = getNextStage(Stage.INITIALIZE);
        return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
      }
     
      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);
        }
      }
    

    DecodeJob会回调run()开始执行, run()中调用runWrapped执行工作,这里runReason还是RunReason.INITIALIZE ,根据前面的分析指导这里会获得一个ResourceCacheGenerator,然后调用runGenerators:

    
    // DecodeJob.java 
    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);
        }
      }
    

    在 runGenerators 中,会调用 startNext,目前

    currentGenerator是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之后会根据key去磁盘缓存中查找cacheFile = helper.getDiskCache().get(currentKey);

    helper.getDiskCache()返回DiskCache接口,它的实现类是DiskLruCacheWrapper,看下DiskLruCacheWrapper.get方法。

    # DiskLruCacheWrapper.java
    @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 是 ByteBufferFetcherByteBufferFetcher的loadData方法中最终会执行callback.onDataReady(result)这里callback是ResourceCacheGenerator

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

    ResourceCacheGeneratoronDataReady方法又会回调DecodeJobonDataFetcherReady方法进行后续的解码操作。

    如果ResourceCacheGenerator没有找到缓存,就会交给DataCacheGenerator继续查找缓存。该类大体流程和ResourceCacheGenerator一样,有点不同的是,DataCacheGenerator的构造器有两个构造器,其中的DataCacheGenerator(List, DecodeHelper, FetcherReadyCallback)构造器是给SourceGenerator准备的。因为如果没有磁盘缓存,那么从源头加载后,肯定需要进行磁盘缓存操作的。所以,SourceGenerator会将加载后的资源保存到磁盘中,然后转交给DataCacheGenerator从磁盘中取出交给ImageView展示。

    看下DataCacheGenerator.startNext:

    public boolean startNext() { 
        while (modelLoaders == null || !hasNextModelLoader()) { 
          sourceIdIndex++; 
          if (sourceIdIndex >= cacheKeys.size()) { 
            return false; 
          } 
          Key sourceId = cacheKeys.get(sourceIdIndex); 
          ... 
          Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); 
          cacheFile = helper.getDiskCache().get(originalKey); 
          ... 
        while (!started && hasNextModelLoader()) { 
          ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); 
          loadData = 
              modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), 
                  helper.getOptions()); 
          if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { 
            started = true; 
            loadData.fetcher.loadData(helper.getPriority(), this); 
          } 
        } 
        return started; 
      } 
    
    

    这里的originalKey是DataCacheKey类型的,DataCacheKey构造方法如下:

    DataCacheKey(Key sourceKey, Key signature)
    

    这里的sourceKey和signature与ResourceCacheKey中的两个变量一致,从这里就可以看出:DataCache缓存的是原始的数据,ResourceCache缓存的是是被解码、转换后的数据。

    如果DataCacheGenerator没有取到缓存,那么会交给SourceGenerator从源头加载。

    #####总结一下:

    • 首先在ResourceCacheGenerator没有取到缓存,那么它的startNext就会返回false,在runGenerators中就会进入循环体内:
    • 接着会重复上面执行getNextStage,由于现在Stage已经是RESOURCE_CACHE,所以接下来会返回DataCacheGenerator,执行逻辑和上面的ResourceCacheGenerator是一样的,如果还是没有找到需要的,进入循环体内。
    • 此时getNextStage会根据用于是否设置只从磁盘中获取资源,如果是就会通知失败,回调onLoadFailed;如果不是就设置当前Stage为Stage.SOURCE,接着往下走。
    • 状态就会进入循环内部的if条件逻辑里面,调用reschedule。
    • 在reschedule把runReason设置成SWITCH_TO_SOURCE_SERVICE,然后通过callback回调。
    #DecodeJob.runGenerators()
     if (stage == Stage.SOURCE) {
            reschedule();
            return;
          }
    
     public void reschedule() {
        runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
        callback.reschedule(this);
      }
    
    • DecodeJob中的callback是EngineJob传递过来的,所以现在返回到EngineJob。
      -在EngineJob中通过
      getActiveSourceExecutor切换到网络线程池中,注意在此之前执行文件缓存的线程池一直都是diskCacheExecutor,执行DecodeJob,下面就准备开始发起网络请求。
    #EngineJob.java
    @Override
      public void reschedule(DecodeJob<?> job) {
        // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
        // up.
        getActiveSourceExecutor().execute(job);
      }
    

    看下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; 
      } 
    

    这里的DataFetcher其实是HttpUrlFetcher,由它来发起网络请求。

    
    // HttpUrlFetcher.java
    @Override
      public void loadData(@NonNull Priority priority,
          @NonNull DataCallback<? super InputStream> callback) {
        long startTime = LogTime.getLogTime();
        try {
          InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
          callback.onDataReady(result);
        } catch (IOException e) {
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Failed to load data for url", e);
          }
          callback.onLoadFailed(e);
        } finally {
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
          }
        }
      }
    

    加载成功后,依然会回调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已经加载成功。

    DataCacheGenerator前面已经分析过了,就是用来加载本地原始数据的。

    先看下SourceGenerator的startNext方法中调用的SourceGenerator.cacheData(data)。

    # SourceGenerator.java
    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()); 
    //根据data,key,signature 创建缓存key。这个key是DataCacheKey类型的,存储是原始数据
          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停止尝试获取数据。此时,从磁盘缓存中读取数据的逻辑已经完成,接下来是写磁盘缓存。

    结论

    Glide加载网络数据后, 在返回之前,会先缓存一份原始数据 。

    磁盘缓存与内存缓存的关联

    磁盘缓存结束后,sourceCacheGenerator被赋值,然后返回,回到startNext方法,接着往下走,会执行sourceCacheGenerator的startNext方法,
    此方法最终调用loadData.fetcher.loadData(helper.getPriority(), this);这句话。由于data是流的类型,所以看ByteBufferFetcher

    package com.bumptech.glide.load.model.ByteBufferFileLoader.ByteBufferFetcher
    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
      ByteBuffer result;
      try {
      //读取文件获取结果 是ByteBuffer类型
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        callback.onLoadFailed(e);
        return;
      }
      //回调 
      callback.onDataReady(result);
    }
    

    最终会调用到下面的方法

    @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;
        //如果不是同一个线程,最终会修改runReason为RunReason.DECODE_DATA;并从起一个线程执行decodeFromRetrievedData方法
        if (Thread.currentThread() != currentThread) {
          runReason = RunReason.DECODE_DATA;
          callback.reschedule(this);
        } else {
          try {
          //如果是同一个线程 直接执行decodeFromRetrievedData方法
            decodeFromRetrievedData();
          } finally {
          }
        }
      }
    

    decodeFromRetrievedData中主要调用了notifyEncodeAndRelease方法,在后一个方法中会执行到notifyComplete方法,notifyComplete最终会调用到notifyCallbacksOfResult方法,该方法调用下面的回调方法:

    @Override
    public synchronized void onEngineJobComplete(
        EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
      // A null resource indicates that the load failed, usually due to an exception.
      if (resource != null && resource.isMemoryCacheable()) {
      //将资源缓存到activeResource中,这里就跟前面的”先从内存的二级缓存加载数据“的逻辑对应上了
        activeResources.activate(key, resource);
      }
    
      jobs.removeIfCurrent(key, engineJob);
    }
    

    在这里会将资源放入activeResources中,资源变为active状态。后面会使用Executors.mainThreadExecutor()调用SingleRequest.onResourceReady回调进行资源的显示。在触发回调前后各有一个地方会对engineResource进行acquire()和release()操作,这两个操作分别发生在notifyCallbacksOfResult()方法的incrementPendingCallbacks、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状态。

    小结一下

    **Glide的三级缓存:**第一级缓存内存缓存,内存缓存又分为两种存储形式,弱引用缓存和LRU策略的缓存方式;第二级缓存是本地文件缓存,缓存的文件大小默认是250M,本地缓存会分为存储未经修改的源数据和经过剪裁后的数据;第三级缓存就是网络/数据源缓存。
    **关于缓存及展示数据:**数据加载出来之后,会先在缓存到本地文件中,然后再去本地缓存文件中读取数据进行编码,将编码后的数据再进行一次缓存,接着再内存中进行缓存,最后将数据编码后的图片文件发往主线程设置给target。

    回答以下几个问题:

    Glide网络请求回来后数据直接返回给用户还是先存再返回

    不是直接返回给用户,会在SourceGenerator中构造一个DataCacheGenerator来取数据。简单来说就是先缓存到磁盘,再从磁盘里取数据返回给ImageView添加图片

    Glide本地文件IO和网络请求是一个线程吗?

    明显不是,本地IO通过diskCacheExecutor,而网络IO通过ActiveSourceExecutor

    总结

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

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

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

    参考:
    Android源码进阶之Glide缓存机制原理详解
    Glide缓存流程

    展开全文
  • 继承重写SafeKeyGenerator类,做到替换默认加密算法从SHA256到MD5,从而可以通过图片url的md5去查寻已经下载到本地的缓存文件
  • GlideCatchSimpleSimpleDemo请看githubGlide缓存Simple缓存路径的指定缓存大小的获取磁盘缓存清除(两种方法)内存缓存清除可clone之后查看使用SimpleGlide cache Simple.The cache path specifiedThe cache sizeThe ...

    GlideCatchSimple

    SimpleDemo请看github

    Glide缓存Simple

    缓存路径的指定

    缓存大小的获取

    磁盘缓存清除(两种方法)

    内存缓存清除

    可clone之后查看使用Simple

    Glide cache Simple.

    The cache path specified

    The cache size

    The disk cache (two ways)

    Memory cache to clearMay

    use Simple clone after check

    GlideCatchUtil

    获取Glide磁盘缓存大小

    1

    2

    3

    4

    5

    6

    7

    8

    1

    2

    3

    4

    5

    6

    7

    8public String getCacheSize() {

    try {

    return getFormatSize(getFolderSize(new File(Application.getInstance().getCacheDir() + "/" + GlideCatchConfig.GLIDE_CARCH_DIR)));

    } catch (Exception e) {

    e.printStackTrace();

    return "获取失败";

    }

    }

    清除Glide磁盘缓存

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22public boolean cleanCatchDisk() {

    return deleteFolderFile(Application.getInstance().getCacheDir() + "/" + GlideCatchConfig.GLIDE_CARCH_DIR, true);

    }

    public boolean clearCacheDiskSelf() {

    try {

    if (Looper.myLooper() == Looper.getMainLooper()) {

    new Thread(new Runnable() {

    @Override

    public void run() {

    Glide.get(Application.getInstance()).clearDiskCache();

    }

    }).start();

    } else {

    Glide.get(Application.getInstance()).clearDiskCache();

    }

    return true;

    } catch (Exception e) {

    e.printStackTrace();

    return false;

    }

    }

    AndroidMainfest.xml and GlideConfiguration.class

    1

    2

    3

    1

    2

    3

    android:name="com.yaphetzhao.glidecatchsimple.glide.GlideConfiguration"

    android:value="GlideModule" />

    Application.class

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19public class Application extends android.app.Application {

    public static Application instance;

    public static Application getInstance() {

    return instance;

    }

    @Override

    public void onCreate() {

    super.onCreate();

    instance = this;

    }

    }

    android:name=".Application"

    more...

    About Me

    YaphetZhao  Email:yaphetzhao@gmail.com  Email_CN:yaphetzhao@foxmail.com  GitHub:http://github.com/YaphetZhao/  QQ:11613371  CSDN_Blog:http://blog.csdn.net/yaphetzhao

    完整工具类代码

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154package com.yaphetzhao.glidecatchsimple.glide;

    import android.os.Looper;

    import android.util.Log;

    import com.bumptech.glide.Glide;

    import com.yaphetzhao.glidecatchsimple.Application;

    import com.yaphetzhao.glidecatchsimple.glide.config.GlideCatchConfig;

    import java.io.File;

    import java.math.BigDecimal;

    /** * Created by YaphetZhao * on 2016/12/19. *

    * QQ:11613371 * GitHub:https://github.com/YaphetZhao * Email:yaphetzhao@foxmail.com * Email_EN:yaphetzhao@gmail.com *

    * Glide缓存工具类 */

    @SuppressWarnings("ResultOfMethodCallIgnored")

    public class GlideCatchUtil {

    private static GlideCatchUtil instance;

    public static GlideCatchUtil getInstance() {

    if (null == instance) {

    instance = new GlideCatchUtil();

    }

    return instance;

    }

    // 获取Glide磁盘缓存大小

    public String getCacheSize() {

    try {

    return getFormatSize(getFolderSize(new File(Application.getInstance().getCacheDir() + "/" + GlideCatchConfig.GLIDE_CARCH_DIR)));

    } catch (Exception e) {

    e.printStackTrace();

    return "获取失败";

    }

    }

    // 清除Glide磁盘缓存,自己获取缓存文件夹并删除方法

    public boolean cleanCatchDisk() {

    return deleteFolderFile(Application.getInstance().getCacheDir() + "/" + GlideCatchConfig.GLIDE_CARCH_DIR, true);

    }

    // 清除图片磁盘缓存,调用Glide自带方法

    public boolean clearCacheDiskSelf() {

    try {

    if (Looper.myLooper() == Looper.getMainLooper()) {

    new Thread(new Runnable() {

    @Override

    public void run() {

    Glide.get(Application.getInstance()).clearDiskCache();

    }

    }).start();

    } else {

    Glide.get(Application.getInstance()).clearDiskCache();

    }

    return true;

    } catch (Exception e) {

    e.printStackTrace();

    return false;

    }

    }

    // 清除Glide内存缓存

    public boolean clearCacheMemory() {

    try {

    if (Looper.myLooper() == Looper.getMainLooper()) { //只能在主线程执行

    Glide.get(Application.getInstance()).clearMemory();

    return true;

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    return false;

    }

    // 获取指定文件夹内所有文件大小的和

    private long getFolderSize(File file) throws Exception {

    long size = 0;

    try {

    File[] fileList = file.listFiles();

    for (File aFileList : fileList) {

    if (aFileList.isDirectory()) {

    size = size + getFolderSize(aFileList);

    } else {

    size = size + aFileList.length();

    }

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    return size;

    }

    // 格式化单位

    private static String getFormatSize(double size) {

    double kiloByte = size / 1024;

    if (kiloByte < 1) {

    return size + "Byte";

    }

    double megaByte = kiloByte / 1024;

    if (megaByte < 1) {

    BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));

    return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB";

    }

    double gigaByte = megaByte / 1024;

    if (gigaByte < 1) {

    BigDecimal result2 = new BigDecimal(Double.toString(megaByte));

    return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB";

    }

    double teraBytes = gigaByte / 1024;

    if (teraBytes < 1) {

    BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));

    return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";

    }

    BigDecimal result4 = new BigDecimal(teraBytes);

    return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";

    }

    // 按目录删除文件夹文件方法

    private boolean deleteFolderFile(String filePath, boolean deleteThisPath) {

    try {

    File file = new File(filePath);

    if (file.isDirectory()) {

    File files[] = file.listFiles();

    for (File file1 : files) {

    deleteFolderFile(file1.getAbsolutePath(), true);

    }

    }

    if (deleteThisPath) {

    if (!file.isDirectory()) {

    file.delete();

    } else {

    if (file.listFiles().length == 0) {

    file.delete();

    }

    }

    }

    return true;

    } catch (Exception e) {

    e.printStackTrace();

    return false;

    }

    }

    }

    完整代码请看git

    我的更多git项目

    欢迎fork,感谢star

    展开全文
  • * Glide缓存工具类 */ public class ImageCatchUtil { private static ImageCatchUtil inst; private final String ImageExternalCatchDir = Application.getInstance().getExternalCacheDir() + "/image_cache"; ...

    不多说了,还是直接上代码吧

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154

    155

    156

    157

    158

    159

    160

    161

    162

    163

    164

    165

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154

    155

    156

    157

    158

    159

    160

    161

    162

    163

    164

    165/** * Created by zhaoyong on 2016/6/21. * Glide缓存工具类 */

    public class ImageCatchUtil {

    private static ImageCatchUtil inst;

    private final String ImageExternalCatchDir = Application.getInstance().getExternalCacheDir() + "/image_cache";

    public static ImageCatchUtil getInstance() {

    if (inst == null) {

    inst = new ImageCatchUtil();

    }

    return inst;

    }

    /** * 清除图片磁盘缓存 */

    public void clearImageDiskCache() {

    try {

    if (Looper.myLooper() == Looper.getMainLooper()) {

    new Thread(new Runnable() {

    @Override

    public void run() {

    Glide.get(Application.getInstance()).clearDiskCache();

    }

    }).start();

    } else {

    Glide.get(Application.getInstance()).clearDiskCache();

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    /** * 清除图片内存缓存 */

    public void clearImageMemoryCache() {

    try {

    if (Looper.myLooper() == Looper.getMainLooper()) { //只能在主线程执行

    Glide.get(Application.getInstance()).clearMemory();

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    /** * 清除图片所有缓存 */

    public void clearImageAllCache() {

    clearImageDiskCache();

    clearImageMemoryCache();

    deleteFolderFile(ImageExternalCatchDir, true);

    }

    /** * 获取Glide造成的缓存大小 * *@return CacheSize */

    public String getCacheSize() {

    try {

    return getFormatSize(getFolderSize(new File(Application.getInstance().getCacheDir() + "/image_cache")));

    } catch (Exception e) {

    e.printStackTrace();

    }

    return "";

    }

    /** * 获取指定文件夹内所有文件大小的和 * *@param file file *@return size *@throws Exception */

    public long getFolderSize(File file) throws Exception {

    long size = 0;

    try {

    File[] fileList = file.listFiles();

    for (File aFileList : fileList) {

    if (aFileList.isDirectory()) {

    size = size + getFolderSize(aFileList);

    } else {

    size = size + aFileList.length();

    }

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    return size;

    }

    /** * 删除指定目录下的文件,这里用于缓存的删除 * *@param filePath filePath *@param deleteThisPath deleteThisPath */

    public void deleteFolderFile(String filePath, boolean deleteThisPath) {

    if (!TextUtils.isEmpty(filePath)) {

    try {

    File file = new File(filePath);

    if (file.isDirectory()) {

    File files[] = file.listFiles();

    for (File file1 : files) {

    deleteFolderFile(file1.getAbsolutePath(), true);

    }

    }

    if (deleteThisPath) {

    if (!file.isDirectory()) {

    file.delete();

    } else {

    if (file.listFiles().length == 0) {

    file.delete();

    }

    }

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    }

    /** * 格式化单位 * *@param size size *@return size */

    public static String getFormatSize(double size) {

    double kiloByte = size / 1024;

    if (kiloByte < 1) {

    return size + "Byte";

    }

    double megaByte = kiloByte / 1024;

    if (megaByte < 1) {

    BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));

    return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB";

    }

    double gigaByte = megaByte / 1024;

    if (gigaByte < 1) {

    BigDecimal result2 = new BigDecimal(Double.toString(megaByte));

    return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB";

    }

    double teraBytes = gigaByte / 1024;

    if (teraBytes < 1) {

    BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));

    return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";

    }

    BigDecimal result4 = new BigDecimal(teraBytes);

    return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";

    }

    }

    GlideConfiguration配置文件,指定缓存目录等方法都在这里面

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    0818b9ca8b590ca3270a3433284dd417.png

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29import android.content.Context;

    import com.bumptech.glide.Glide;

    import com.bumptech.glide.GlideBuilder;

    import com.bumptech.glide.load.DecodeFormat;

    import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;

    import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;

    import com.bumptech.glide.load.engine.cache.LruResourceCache;

    import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;

    import com.bumptech.glide.module.GlideModule;

    /** * Created by zhaoyong on 2016/6/16. * Glide配置文件 */

    public class GlideConfiguration implements GlideModule {

    @Override

    public void applyOptions(Context context, GlideBuilder builder) {

    //自定义缓存目录,磁盘缓存给150M

    builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "image_catch", 150 * 1024 * 1024));

    }

    @Override

    public void registerComponents(Context context, Glide glide) {

    }

    }

    Manifest.xml

    1

    2

    1

    2

    展开全文
  • glide获取缓存图片的方法

    千次阅读 2019-02-14 15:38:07
    首先使用glide加载网络图片,然后有硬盘缓存,从缓存获取图片。设置diskCacheStrategy方法的缓存策略为DiskCacheStrategy.ALL或者DiskCacheStrategy.SOURCE 1、Glide.with(mContext).load(url).asBitmap().into...
  • 1)在图片下载缓存好之后获取 Glide.with(mContext).load(url).asBitmap().into(new SimpleTarget() { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> gl
  • android – Glide获取缓存文件位置

    千次阅读 2020-12-23 15:41:03
    现在我想知道Glide存储从网址下载的缓存图像的位置.我使用下面的代码来显示图像.Glide.with(mContext).load(mData.get(position).getImage()).centerCrop().override(300, 300).placeholder(R.drawable.default_...
  • Glide获取图片缓存文件名Key

    千次阅读 2020-01-10 18:30:12
    今天同事碰到一个问题,跟了3天了,他的由于项目中用了Glide图片加载框架,而我们的图片Url又有进行CDN裁剪,他的项目中有小图,中图,大图,三种分辨率的图片,发现当有了大图后,小图去加载时,由于url中带了尺寸...
  • Glide图片硬盘缓存使用详解

    千次阅读 2021-01-16 20:30:32
    Glide缓存工具类*/@SuppressWarnings("ResultOfMethodCallIgnored")public class GlideCacheUtil {private static GlideCacheUtil instance;public static GlideCacheUtil getInstance() {if (null == instance) {...
  • 缓存的过程首先是在内存中缓存,然后将加载的图片资源缓存到硬盘,这样就可以在随后的再次加载中使用缓存了,Glide使用缓存时候首先要检查内存这一层级是否缓存了相应的缓存,如果有,则直接使用,如果没有,则深入...
  • android glide 获取缓存图片

    万次阅读 2017-03-08 15:02:19
    Glide是Google推荐用的图片加载框架,很简单就可以加载出图片了,如下: Glide.with(context).load(ImgUrl).into(ImageView); 也可以加一些常用的功能,如placeHolder图片等等,如下: Glide.with(context).load...
  • Glide 缓存流程 详解

    2022-02-26 21:32:01
    内存缓存的主要作用时防止应用重新加载图片数据到内存中;而硬盘缓存的主要作用是防止应用重复从网络或其他地方下载和读取数据; 缓存执行的顺序是: 取顺序:弱引用,LRUCache,磁盘 存顺序:磁盘,弱引用,...
  • Glide获取某个url对应的缓存图片

    万次阅读 热门讨论 2017-10-26 18:37:05
    Glide这么久了,我一直有个疑问,Glide该如何获取到指定的缓存图片?原生Glide是没有提供任何Api用来获取缓存图片的,至少我是没找到。翻看Glide源码(3.7),发现其中一个叫:EngineKey的类,Glide通过该类来查找...
  • glide缓存原理

    千次阅读 2020-06-08 23:18:30
    一.glide缓存分为:内存缓存和硬盘缓存 二.在load方法中可以看出先调用内存缓存在加载图片,内存缓存找不到在调用硬盘缓存中加载图片 三.内存缓存 1.缓存key:决定缓存key的参数有十几个(包括url,宽,高,...
  • Android Glide缓存策略

    2021-10-11 10:17:57
    一、glide缓存策略 缓存在请求网络图片时能减少不必要的流量浪费。Glide 缓存分为内存缓存和硬盘缓存,这两个缓存模块的作用各不相同,内存缓存的主要作用是 防止应用重复的将图片数据读取到内存,而硬盘缓存则是...
  • Glide缓存机制

    2022-02-25 11:04:01
    Glide缓存机制
  • Glide 缓存原理实现

    2021-06-10 10:46:47
    Glide 缓存原理实现专注于Android开发,分享经验总结...Glide缓存分为:活动缓存、内存缓存、Bitmap复用池、磁盘缓存、加载外置(网络或者SD卡)、绑定生命周期具体的流程如下在这里插入图片描述资源封装Key -- 对Valu...
  • Glide 缓存流程

    2021-06-08 15:54:53
    Glide提供简洁易用的api,整个框架也方便扩展,比如可以替换网络请求库,同时也提供了完备的缓存机制,应用层不需要自己去管理图片缓存获取,框架会分成内存缓存,文件缓存和远程缓存。本文不会从简单的使用着手...
  • Glide获取缓存大小并清除缓存图片

    万次阅读 2016-10-11 00:32:35
    清除Glide缓存Glide自带清除缓存的功能,分别对应Glide.get(context).clearDiskCache();(清除磁盘缓存)与Glide.get(context).clearMemory();(清除内存缓存) 两个方法.其中clearDiskCache()方法必须运行在子线程,...
  • 随时随地阅读更多技术实战干货,获取项目源码、学习资料,请关注源代码社区公众号(ydmsq666)、博主微信(guyun297890152)、QQ技术交流群(183198395)。 from:...
  • Glide缓存原理

    2022-03-04 22:07:57
    ​ 首先在Glide缓存分为四部分:活动缓存、内存缓存、数据格式磁盘缓存(就是把所有裁剪过,压缩过、转换过的图片也保存起来),原图磁盘缓存。后面两个是自己起的名字,叫什么都行。解释如下: 活动缓存:主要就是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,638
精华内容 3,055
关键字:

获取glide缓存的图片