傻瓜式的带你深入了解glide的内存机制

真正的傻瓜式,人人都看得懂

glide一共有几级缓存

三级缓存,activeResource,内存缓存,硬盘缓存
有代码为证:class Engine

     //先从activeResource加载
    EngineResource active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      //有结果就直接返回
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

     //从内存缓存中加载
    EngineResource cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

   //都没有,到异步线程去执行逻辑,包括硬盘缓存跟请求网络
    EngineJob current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

activeResource

比如如下的页面,两个imageview加载同样的一张图片,在内存中,其实只有一份bitmap,这个就是activeResource的功劳


image.png
image.png

通过glide加载的图片资源,多个地方加载同个资源,都是返回同一份bitmap,避免bitmap重复(当页面销毁,资源也会从activeResource中移除)

ActiveResources代码参考

//加入图片的引用,内部是一个hashmap的弱引用在管理
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();
    }
  }

//查询是否有某个图片的引用
  @Nullable
  synchronized 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;
  }

所以,内部的资源加载(各种R.drawable),图片加载,尽量使用glide来加载,是最好的选择

那有个问题来了,如果加载两张一样的图片,但是imageview的宽高不一样,还是内存中只有一份bitmap吗?
我们直接验证看下


image.png

image.png

可以看到,内存有两张bitmap,并且两张bitmap的尺寸不一样

这里就涉及到上面保存引用的key的逻辑了,可以直接看代码

class EngineKeyFactory {

  @SuppressWarnings("rawtypes")
  EngineKey buildKey(Object model, Key signature, int width, int height,
      Map, Transformation> transformations, Class resourceClass,
      Class transcodeClass, Options options) {
    return new EngineKey(model, signature, width, height, transformations, resourceClass,
        transcodeClass, options);
  }
}

可以看到,key跟很多参数相关
model:就是加载的url
signature:额外的签名
width,height:imageview的宽高
transformations:就是转换,比如centerCrop,roundCorner等

由于我们这里,只是宽高不一样,所以为了保证同一个key,保证宽高是一样的就好,可以使用override方式
Glide.with(this).load(url).override(Target.SIZE_ORIGINAL).into(imageView)

这样key是一样,在内存中也就只有一份bitmap了,提升了App性能

内存缓存

内存缓存的大小
有一套复杂的计算规则,不过大多数手机,都是屏幕宽高*8,常规的手机就是17M左右
详细的计算规则可以查看类MemorySizeCalculator
对于大多数App,这个内存是偏小的,通过直接放大1.5倍,代码如下
Glide.get(context).setMemoryCategory(MemoryCategory.HIGH);
如果还是不够的话,需要通过AppGlideModule来实现

内存缓存内部是采用LinkedHashMap来实现的,最新加的,放在最后面,缓存总大小到了上限值,越早加入缓存的,越先被清空,可以看下构造函数

/**
     * Constructs an empty LinkedHashMap instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - true for
     *         access-order, false for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

关键是这个accessOrder,需要设置为true,这样就会按照范围的顺序排序,最近被访问或者使用的,就越不会被回收

获取一个内存缓存,很简单,就是从hashmap的get方法就可以

/**
   * Returns the item in the cache for the given key or null if no such item exists.
   *
   * @param key The key to check.
   */
  @Nullable
  public synchronized Y get(@NonNull T key) {
    return cache.get(key);
  }

放入一个图片到缓存中

/**
   * Adds the given item to the cache with the given key and returns any previous entry for the
   * given key that may have already been in the cache.
   *
   * 

If the size of the item is larger than the total cache size, the item will not be added to * the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with * the given key and item. * * @param key The key to add the item at. * @param item The item to add. */ @Nullable public synchronized Y put(@NonNull T key, @Nullable Y item) { final int itemSize = getSize(item); if (itemSize >= maxSize) { onItemEvicted(key, item); return null; } if (item != null) { currentSize += itemSize; } @Nullable final Y old = cache.put(key, item); if (old != null) { currentSize -= getSize(old); if (!old.equals(item)) { onItemEvicted(key, old); } } evict(); return old; }

放入内存缓存的时机是什么时候??
页面销毁的时候,证据如下


image.png

逻辑是这样
加载到一张图片,显示在view上,同时放入activeResource缓存,单view销毁了,从activeResource缓存中挪到memoryCache中
下次再打开页面,加载同一种图片,从memoryCache中,remove掉该图片,加载到view里面,同时放入activeResource中

所以放入内存缓存中的图片,都是不在页面显示的图片

硬盘缓存

硬盘缓存的大小和默认缓存路径

大小:250M
路径:/data/user/0/com.example.myapplication/cache/image_manager_disk_cache

interface Factory {
    /** 250 MB of cache. */
    int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
    String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
    /** Returns a new disk cache, or {@code null} if no disk cache could be created. */
    @Nullable
    DiskCache build();
  }

问题:正写入一个缓存文件到硬盘,App崩溃了,怎么办?
答案:日志文件
一个日志文件,内容大概这样

     *     libcore.io.DiskLruCache
     *     1
     *     100
     *     1
     *
     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
     *     READ 335c4c6028171cfddfbaae1a9c313c52
     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

写入一个缓存的逻辑是这样
现在日志文件,写一行DIRTY的日志
写入真实的缓存的图片
在日志文件,写入一行CLEAN的日志

这样,当写入的图片失败,或者App崩溃,日志里面,就只有DIRTH记录,没有CLEAN记录,就可以把这次DIRTH的缓存给删掉

加载一个缓存文件
通过日志文件,会生成一个硬盘缓存的hashmap,通过key索引,可以加载到这个拿到这个hashmap里面的值,可以实际拿到文件的缓存路径

/**
   * Returns a snapshot of the entry named {@code key}, or null if it doesn't
   * exist is not currently readable. If a value is returned, it is moved to
   * the head of the LRU queue.
   */
  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);
  }

删除一个缓存
先删除文件,然后往日志里面写入REMOVE的标记

public synchronized boolean remove(String key) throws IOException {
    checkNotClosed();
    Entry entry = lruEntries.get(key);
    if (entry == null || entry.currentEditor != null) {
      return false;
    }

    for (int i = 0; i < valueCount; i++) {
      File file = entry.getCleanFile(i);
      if (file.exists() && !file.delete()) {
        throw new IOException("failed to delete " + file);
      }
      size -= entry.lengths[i];
      entry.lengths[i] = 0;
    }

    redundantOpCount++;
    journalWriter.append(REMOVE);
    journalWriter.append(' ');
    journalWriter.append(key);
    journalWriter.append('\n');

    lruEntries.remove(key);

    if (journalRebuildRequired()) {
      executorService.submit(cleanupCallable);
    }

    return true;
  }

删除所有的硬盘缓存

public void delete() throws IOException {
    close();
    Util.deleteContents(directory);
  }

日志文件路径:
/data/user/0/com.example.myapplication/cache/image_manager_disk_cache/journal
缓存文件路径:
/data/user/0/com.example.myapplication/cache/image_manager_disk_cache/6da652fb74c5ae6035ec3f7cab315b6f5dedb11515c173b2097d02a14c7974b9.0

你可能感兴趣的:(傻瓜式的带你深入了解glide的内存机制)