美文网首页
傻瓜式的带你深入了解glide的内存机制

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

作者: 韦东锏 | 来源:发表于2019-08-13 13:48 被阅读0次

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

    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<Class<?>, 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 <tt>LinkedHashMap</tt> 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 - <tt>true</tt> for
         *         access-order, <tt>false</tt> 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.
       *
       * <p>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的内存机制

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