Bitmap的加载和Cache

232 阅读18分钟

“Bitmap,表示位图,由像素点构成。Bitmap的承载容器是jpg、png等格式的文件,是对bitmap的压缩。当jpg、png等文件需要展示在手机上的控件时,就会解析成Bitmap并绘制到view上。通常处理图片时要避免过多的内存使用,毕竟移动设备的内存有限。” “那么加载一张图片需要占用多大内存呢?考虑到效率加载图片时缓存策略是怎样的呢?”

Bitmap的加载

1.1 Bitmap的内存占用

原始计算公式如下: Bitmap的内存 = 分辨率 * 像素点的大小

图片分辨率,可能不是原始图片的分辨率。例如 图片放在Res中不同dpi的文件夹中,分辨率是原始分辨率转换后的。比如放hdpi与放xhdpi,转换后的分辨率是不同的。转换后的分辨率=原始分辨率*(设备的 dpi / 目录对应的 dpi)。其他情况,如放在磁盘、文件、流等均按原分辨率处理。另外,这个逻辑是原生系统BitmapFactory的逻辑,如果是直接使用图片库,内部逻辑可能不会转换分辨率,如glide的不转换Res中图片的分辨率。

像素点的大小,就是ARGB8888(4B)、RGB565(2B)这几个。

1.2 Bitmap的高效加载

Bitmap的加载,可过系统提供的BitmapFactory四个方法:decodeFile、decodeResource、decodeStream、decodeByteArray,对应处理从 文件、资源、流、字节数组 来加载Bitmap。

如何优化加载呢?由公式可见想要减少图片加载成Bitmap时占用的内存,两个方法:

  • 降低像素点的大小:如可以把图片格式ARGB8888 换成RGB565,内存占用就会减少一半。但导致不支持透明度,降低图片质量。开源库一般也支持更换格式。

  • 降低分辨率:通常图片的分辨率远大于控件view的分辨率,加载后view无法显示原始的分辨率,所以降低分辨率也不会影响图片的展示效果。

针对第二点,降低分辨率,BitmapFactory.Options也提供了对应的方法,步骤如下:

把BitmapFactory.Options.inJustDecodeBounds设为true,并加载图片。(只加载原始宽高信息,轻量级操作)

获取原始宽高信息:options.outWidth、options.outHeight

设置采样率options.inSampleSize。采样率根据 原始宽高信息 和 view的大小计算。

把BitmapFactory.Options.inJustDecodeBounds设为false,并加载图片。

代码如下:

 1    private void initView() {
 2        //R.mipmap.blue放在Res的xxh(480dpi)中,测试手机dpi也是480
 3
 4        //1、inJustDecodeBounds设为true,并加载图片
 5        BitmapFactory.Options options = new BitmapFactory.Options();
 6        options.inJustDecodeBounds = true;
 7        BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);
 8
 9        //2、获取原始宽高信息
10        int outWidth = options.outWidth;
11        int outHeight = options.outHeight;
12
13        Log.i(TAG, "initView: outWidth="+outWidth+",outHeight="+outHeight);
14
15        //3、原始宽高信息 和 view的大小 计算并设置采样率
16        ViewGroup.LayoutParams layoutParams = ivBitamp.getLayoutParams();
17        int inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, outWidth, outHeight);
18        options.inSampleSize = inSampleSize;
19
20        Log.i(TAG, "initView: inSampleSize="+options.inSampleSize);
21
22        //4、inJustDecodeBounds设为false,并加载图片
23        options.inJustDecodeBounds = false;
24        Bitmap bitmap= BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);
25
26        Log.i(TAG, "initView: size="+bitmap.getByteCount());
27
28        int density = bitmap.getDensity();
29        Log.i(TAG, "initView: density="+density);
30        Log.i(TAG, "initView: original size="+337*222*4);
31        Log.i(TAG, "initView: calculated size="+ (337/inSampleSize) *(222/inSampleSize)* density/480 *4);
32
33        //绘制到view
34        ivBitamp.setImageBitmap(bitmap);
35    }
36
37    /**
38     * 计算采样率
39     * @param width view的宽
40     * @param height view的高
41     * @param outWidth 图片原始的宽
42     * @param outHeight 图片原始的高
43     * @return
44     */
45    private int getInSampleSize(int width, int height, int outWidth, int outHeight) {
46        int inSampleSize = 1;
47        if (outWidth>width || outHeight>height){
48            int halfWidth = outWidth / 2;
49            int halfHeight = outHeight / 2;
50            //保证采样后的宽高都不小于目标快高,否则会拉伸而模糊
51            while (halfWidth/inSampleSize >=width
52                    && halfHeight/inSampleSize>=height){
53                //采样率一般取2的指数
54                inSampleSize *=2;
55            }
56        }
57
58        return inSampleSize;
59    }
60}

Android中的缓存策略

缓存策略在Android中应用广泛。使用缓存可以节省流量、提高效率。

加载图片时,一般会从网络加载,然后缓存在存储设备上,这样下次就不用请求网络了。并且通常也会缓存一份到内存中,这样下次可以直接取内存中的缓存,要比从存储设备中取快很多。所以一般是先从内存中取,内存没有就取存储设备,也没有才会请求网络,这就是所谓的“三级缓存”。此策略同样适用其他文件类型。

缓存策略中的操作有 添加缓存、获取缓存、删除缓存。添加和获取比较好理解,删除缓存是啥意思?因为缓存大小是有限制的,像移动设备的 内存 和 设备存储都是有限的,不能无限制的添加,只能限定一个最大缓存,到达最大时就会删除一部分缓存。但是删除哪一部分缓存呢?删除 缓存创建时间最老的吗,如果它经常用到呢,好像不太完美,当然这也是一种缓存算法。

目前经典的缓存算法是LRU(Least Recently Used),最近最少使用。具体就是 当缓存满时,会先删除那些 近期 最少使用 的缓存。使用LRU算法的缓存有两种,LruCache和DiskLruCache,LruCache是使用内存缓存,DiskLruCache是实现磁盘缓存。

2.1 LruCache

LruCache是泛型类,使用方法如下:

提供最大缓存容量,创建LruCache实例,并重写其sizeOf方法来计算缓存对象的大小。最大容量和缓存对象大小单位要一致。

 1    private void testLruCache() {
 2        //当前进程的最大内存,单位M
 3        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
 4        //取进程内存的1/8
 5        int cacheMaxSize = (int) (maxMemory/8);
 6        //创建Bitmap实例
 7        mBitmapLruCache = new LruCache<String, Bitmap>(cacheMaxSize){
 8            @Override
 9            protected int sizeOf(String key, Bitmap value) {
10                //缓存对象bitmap的大小,单位M
11                return value.getByteCount()/1024/1024;
12            }
13
14            @Override
15            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
16                //移除旧缓存时会调用,可以在这里进行像资源回收的工作。
17                //evicted为true,表示此处移除是因为快满了要腾出空间
18            }
19        };
20
21        //添加缓存
22        mBitmapLruCache.put("1",mBitmap);
23
24        //获取缓存
25        Bitmap bitmap = mBitmapLruCache.get("1");
26        ivBitamp.setImageBitmap(bitmap);
27
28        //删除缓存,一般不会用,因为快满时会自动删近期最少使用的缓存,就是它的核心功能
29        mBitmapLruCache.remove("1");
30    }

可见使用很简单,那么LruCache是怎么完成 删除“近期最少使用” 的呢?看下LruCache的代码:

  1public class LruCache<K, V> {
  2    //此map以强引用的方式存储缓存对象
  3    private final LinkedHashMap<K, V> map;
  4    //当前缓存的大小(带单位的)
  5    private int size;
  6    //缓存最大容量(带单位的)
  7    private int maxSize;
  8    ...
  9    public LruCache(int maxSize) {
 10        if (maxSize <= 0) {
 11            throw new IllegalArgumentException("maxSize <= 0");
 12        }
 13        this.maxSize = maxSize;
 14        //LinkedHashMap是按照 访问顺序 排序的,所以get、put操作都会把要存的k-v放在队尾
 15        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
 16    }
 17
 18    /**
 19     * 获取缓存,同时会把此k-v放在链表的尾部
 20     */
 21    public final V get(K key) {
 22        if (key == null) {
 23            throw new NullPointerException("key == null");
 24        }
 25
 26        V mapValue;
 27        //get是线程安全的操作
 28        synchronized (this) {
 29            //LinkedHashMap的get方法中调afterNodeAccess,会移到链表尾部
 30            mapValue = map.get(key);
 31            if (mapValue != null) {
 32                hitCount++;
 33                return mapValue;
 34            }
 35            missCount++;
 36        }
 37        ...
 38    }
 39
 40    /**
 41     * 缓存key-value,value会存在 队尾
 42     * @return 之前也是这个key存的value
 43     */
 44    public final V put(K key, V value) {
 45        if (key == null || value == null) {
 46            //不允许 null key、null value
 47            throw new NullPointerException("key == null || value == null");
 48        }
 49
 50        V previous;
 51        //可见put操作是线程安全的
 52        synchronized (this) {
 53            putCount++;
 54            size += safeSizeOf(key, value);
 55            //强引用存入map(不会被动地被系统回收),其因为是LinkedHashMap,会放在队尾
 56            previous = map.put(key, value);
 57            if (previous != null) {
 58                //如果前面已这个key,那么替换后调整下当前缓存大小
 59                size -= safeSizeOf(key, previous);
 60            }
 61        }
 62
 63        if (previous != null) {
 64            entryRemoved(false, key, previous, value);
 65        }
 66        //重新调整大小
 67        trimToSize(maxSize);
 68        return previous;
 69    }
 70
 71    /**
 72     * 比较 当前已缓存的大小 和最大容量,决定 是否删除
 73     */
 74    private void trimToSize(int maxSize) {
 75        while (true) {
 76            K key;
 77            V value;
 78            synchronized (this) {
 79                if (size < 0 || (map.isEmpty() && size != 0)) {
 80                    throw new IllegalStateException(getClass().getName()
 81                            + ".sizeOf() is reporting inconsistent results!");
 82                }
 83
 84                if (size <= maxSize) {
 85                    //大小还没超过最大值
 86                    break;
 87                }
 88
 89                //已经达到最大容量
 90
 91                //因为是访问顺序,所以遍历的最后一个就是最近没有访问的,那么就可以删掉它了!
 92                Map.Entry<K, V> toEvict = null;
 93                for (Map.Entry<K, V> entry : map.entrySet()) {
 94                    toEvict = entry;
 95                }
 96                // END LAYOUTLIB CHANGE
 97
 98                if (toEvict == null) {
 99                    break;
100                }
101
102                key = toEvict.getKey();
103                value = toEvict.getValue();
104                map.remove(key);
105                size -= safeSizeOf(key, value);
106                evictionCount++;
107            }
108            //因为是为了腾出空间,所以这个回调第一个参数是true
109            entryRemoved(true, key, value, null);
110        }
111    }
112
113    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
114
115    ...
116}

由以上代码及注释,可见LruCache的算法实现是依靠 设置了访问顺序的LinkedHashMap。因为是访问顺序模式,get、put操作都会调整k-v到链表尾部。在缓存将满时,遍历LinkedHashMap,因为是访问顺序模式,所以遍历的最后一个就是最近没有使用的,然后删除即可。

2.2 DiskLruCache

DiskLruCache是实现磁盘缓存,所以需要设备存储的读写权限;一般是从网络请求图片后缓存到磁盘中,所以还需要网络权限。

1    <uses-permission android:name="android.permission.INTERNET" />
2    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

DiskLruCache,不是官方提供,所以需要引入依赖:

1implementation 'com.jakewharton:disklrucache:2.0.2'

DiskLruCache的创建,不是通过new,而是open方法,需要传入缓存目录、最大缓存容量。

缓存的添加,是通过Editor,缓存对象的编辑器。传入图片url的key 调用DiskLruCache的edit方法获取Editor(如果缓存正在被编辑就会返回null),可以从Editor得到文件输出流,这样就可以写入到文件系统了。

缓存的获取,传入图片url的key 调用DiskLruCache的get方法 得到SnapShot,可从SnapShoty获取文件输入流,这样就用BitmapFactory得到bitmap了。

缓存的删除,DiskLruCache的remove方法可以删除key对应的缓存。 通过查看源码,发现LinkedHashMap内部也是维护了访问顺序的LinkedHashMap,原理上和LruCache是一致的。只是使用上有点点复杂,毕竟涉及文件的读写。

  1    private void testDiskLruCache(String urlString) {
  2        long maxSize = 50*1024*1024;
  3
  4        try {
  5            //一、创建DiskLruCache
  6            //第一个参数是要存放的目录,这里选择外部缓存目录(若app卸载此目录也会删除);
  7            //第二个是版本一般设1;第三个是缓存节点的value数量一般也是1;
  8            //第四个是最大缓存容量这里取50M
  9            mDiskLruCache = DiskLruCache.open(getExternalCacheDir(), 1, 1, maxSize);
 10
 11            //二、缓存的添加:1、通过Editor,把图片的url转成key,通过edit方法得到editor,然后获取输出流,就可以写到文件系统了。
 12            DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyFormUrl(urlString));
 13            if (editor != null) {
 14                //参数index取0(因为上面的valueCount取的1)
 15                OutputStream outputStream = editor.newOutputStream(0);
 16                boolean downSuccess = downloadPictureToStream(urlString, outputStream);
 17                if (downSuccess) {
 18                    //2、编辑提交,释放编辑器
 19                    editor.commit();
 20                }else {
 21                    editor.abort();
 22                }
 23                //3、写到文件系统,会检查当前缓存大小,然后写到文件
 24                mDiskLruCache.flush();
 25            }
 26        } catch (IOException e) {
 27            e.printStackTrace();
 28        }
 29
 30        //三、缓存的获取
 31        try {
 32            String key = hashKeyFormUrl(urlString);
 33            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
 34            FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
 35//            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
 36//            mIvBitamp.setImageBitmap(bitmap);
 37
 38            //注意,一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的文职属性,导致第二次decode是获取是null
 39            //为解决此问题,可用文件描述符
 40            FileDescriptor fd = inputStream.getFD();
 41
 42            //采样加载(就是前面讲的bitmap的高效加载)
 43            BitmapFactory.Options options = new BitmapFactory.Options();
 44            options.inJustDecodeBounds=true;
 45            BitmapFactory.decodeFileDescriptor(fd,null,options);
 46            ViewGroup.LayoutParams layoutParams = mIvBitamp.getLayoutParams();
 47            options.inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, options.outWidth, options.outHeight);
 48            options.inJustDecodeBounds = false;
 49            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
 50
 51            //存入内容缓存,绘制到view。(下次先从内存缓存获取,没有就从磁盘缓存获取,在没有就请求网络--"三级缓存")
 52            mBitmapLruCache.put(key,bitmap);
 53
 54            runOnUiThread(new Runnable() {
 55                @Override
 56                public void run() {
 57                    mIvBitamp.setImageBitmap(mBitmapLruCache.get(key));
 58                }
 59            });
 60
 61        } catch (IOException e) {
 62            e.printStackTrace();
 63        }
 64    }
 65
 66    /**
 67     * 下载图片到文件输入流
 68     */
 69    private boolean downloadPictureToStream(String urlString, OutputStream outputStream) {
 70        URL url = null;
 71        HttpURLConnection urlConnection = null;
 72        BufferedInputStream in = null;
 73        BufferedOutputStream out = null;
 74        try {
 75            url = new URL(urlString);
 76            urlConnection = (HttpURLConnection) url.openConnection();
 77            in = new BufferedInputStream(urlConnection.getInputStream());
 78            out = new BufferedOutputStream(outputStream);
 79
 80            int b;
 81            while ((b=in.read()) != -1) {
 82                //写入文件输入流
 83                out.write(b);
 84            }
 85            return true;
 86        } catch (IOException e) {
 87            e.printStackTrace();
 88        }finally {
 89            if (urlConnection != null) {
 90                urlConnection.disconnect();
 91            }
 92            try {
 93                if (in != null) {in.close();}
 94                if (out != null) {out.close();}
 95            } catch (IOException e) {
 96                e.printStackTrace();
 97            }
 98        }
 99        return false;
100    }
101
102    /**
103     * 图片的url转成key,使用MD5
104     */
105    private String hashKeyFormUrl(String url) {
106        try {
107            MessageDigest digest = MessageDigest.getInstance("MD5");
108            return byteToHexString(digest.digest(url.getBytes()));
109        } catch (NoSuchAlgorithmException e) {
110            e.printStackTrace();
111        }
112        return null;
113    }
114
115    private String byteToHexString(byte[] bytes) {
116        StringBuffer stringBuffer = new StringBuffer();
117        for (int i = 0; i < bytes.length; i++) {
118            String hex = Integer.toHexString(0XFF & bytes[i]);
119            if (hex.length()==1) {
120                stringBuffer.append(0);
121            }
122            stringBuffer.append(hex);
123        }
124        return stringBuffer.toString();
125    }

Glide缓存机制

image.png

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

  1. active状态资源

    Active resources are those that have been provided to at least one request and have not yet been released. Once all consumers of a resource have released that resource, the resource then goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to the active resources. If the resource is evicted from the cache, its resources are recycled and re-used if possible and the resource is discarded. There is no strict requirement that consumers release their resources so active resources are held weakly.

  2. 内存缓存

  3. data磁盘缓存:原始的没有修改过的数据缓存

  4. resource磁盘资源:经过解码、transformed后的缓存

Glide加载资源的流程:

By default, Glide checks multiple layers of caches before starting a new request for an image:

  1. Active resources - Is this image displayed in another View right now?
  2. Memory cache - Was this image recently loaded and still in memory?
  3. Resource - Has this image been decoded, transformed, and written to the disk cache before?
  4. Data - Was the data this image was obtained from written to the disk cache before?

The first two steps check to see if the resource is in memory and if so, return the image immediately. The second two steps check to see if the image is on disk and return quickly, but asynchronously.

If all four steps fail to find the image, then Glide will go back to the original source to retrieve the data (the original File, Uri, Url etc).

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

LruCache的实现主要靠LinkedHashMap的一个构造参数:accessOrder。 当该参数为true时,每次调用LinkedHashMap的get(key)或getOrDefault(key, defaultValue)方法都会触发afterNodeAccess(Object)方法,此方法会将对应的node移动到链表的末尾。也就是说LinkedHashMap末尾的数据是最近“最多”使用的。 而LruCache清除内存时都会调用trimToSize(size)方法时,会从头到尾进行清理,这样LRU的特点就体现出来了。

DiskLruCache跟上面的差不多.

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

缓存首先会判断active状态的resource,然后是memory cache,最后就交给了job。那么毫无疑问,job中会进行disk cache的读操作。

显然,在多次加载同一个model的过程中,即使有稍许改动(比如View宽高等),Glide都不会认为这是同一个Key。

回到Engine.load方法中,active状态的resource和memory cache状态的资源其实都是DataSource.MEMORY_CACHE状态,从缓存加载成功后的回调中可以看到。而且,加载出来的资源都是EngineResource对象,该对象的管理策略采用了引用计数算法。该算法的特点是实现简单,判定效率也很高。

在release后,如果引用计数为0,那么会调用listener.onResourceReleased(key, this)方法通知外界此资源已经释放了。实际上,所有的listener都是Engine对象,在Engine.onResourceReleased方法中会将此资源放入memory cache中,如果可以被缓存的话.

只要命中了缓存,那么该资源的引用计数就会加一。而且,如果命中的是memory cache,那么此资源会被提高到active状态中。 显然,第一次运行的时候是没有任何内存缓存的,现在来到了DecodeJob和EngineJob这里。还是在前一篇文章中提到过,DecoceJob实现了Runnable接口,然后会被EngineJob.start方法提交到对应的线程池中去执行。

当在两个cache generator中都找不到时,会交给SourceGenerator从source中进行加载。此时,对于一个网络图片资源来说,就是加载网络图片;对于本地资源来说,就是加载本地资源。

那么,resource资源和data资源都是磁盘缓存中的资源,其区别在开头的那段官方文档中有说到,在下面的分析中我们会看到的。

DiskLruCacheWrapper在写入的时候会使用到写锁DiskCacheWriteLocker,锁对象由对象池创建(Glide中对象池和缓存真的是无所不在👍👍👍),写锁WriteLock实现是一个重入锁ReentrantLock,该锁默认是一个不公平锁。 在缓存写入前,会判断key对应的value存不存在,若存在则放弃写入。缓存的真正写入会由DataCacheWriter交给ByteBufferEncoder和StreamEncoder两个具体类来写入,前者负责将ByteBuffer写入到文件,后者负责将InputStream写入到文件。

至此,磁盘缓存的读写都已经完毕,剩下的就是内存缓存的两个层次了。我们回到DecodeJob.notifyEncodeAndRelease方法中,经过notifyComplete、EngineJob.onResourceReady、notifyCallbacksOfResult方法中。 在该方法中一方面会将原始的resource包装成一个EngineResource,然后通过回调传给Engine.onEngineJobComplete,在这里会将资源保持在active resource中.

前面在看EngineResource的代码时我们知道,一旦引用计数为0,就会通知Engine将此资源从active状态变成memory cache状态。如果我们再次加载资源时可以从memory cache中加载,那么资源又会从memory cache状态变成active状态。

也就是说,在资源第一次显示后,我们关闭页面,资源会由active变成memory cache;然后我们再次进入页面,加载时会命中memory cache,从而又变成active状态。

最后,摘抄一下Glide官方文档对于缓存更新的说明:

Because disk cache are hashed keys, there is no good way to simply delete all of the cached files on disk that correspond to a particular url or file path. The problem would be simpler if you were only ever allowed to load or cache the original image, but since Glide also caches thumbnails and provides various transformations, each of which will result in a new File in the cache, tracking down and deleting every cached version of an image is difficult.

In practice, the best way to invalidate a cache file is to change your identifier when the content changes (url, uri, file path etc) when possible.

Since it’s often difficult or impossible to change identifiers, Glide also offers the signature() API to mix in additional data that you control into your cache key. Signatures work well for media store content, as well as any content you can maintain some versioning metadata for.

  • Media store content - For media store content, you can use Glide’s MediaStoreSignature class as your signature. MediaStoreSignature allows you to mix the date modified time, mime type, and orientation of a media store item into the cache key. These three attributes reliably catch edits and updates allowing you to cache media store thumbs. - Files - You can use ObjectKey to mix in the File’s date modified time. - Urls - Although the best way to invalidate urls is to make sure the server changes the url and updates the client when the content at the url changes, you can also use ObjectKey to mix in arbitrary metadata (such as a version number) instead.

If all else fails and you can neither change your identifier nor keep track of any reasonable version metadata, you can also disable disk caching entirely using diskCacheStrategy() and DiskCacheStrategy.NONE.

那我来说说glide4 的缓存机制吧 glide4 缓存主要分为三个 activiteResources(弱引用缓存) MemoryCache(Lru缓存) DiskCache(磁盘缓存) 还有一个http网络缓存, 不多做介绍

首先弱引用缓存代表的是当前正在使用的缓存, 请求图片之后 会分为两个步骤, 1.进入磁盘缓存,2进入弱引用缓存。 在弱引用缓存中使用一个计数器来维护当前资源的是否被使用,如果计数器=0说明当前资源已经不被使用了,则进入Lru缓存,Lru缓存是glide使用linkhashmap来维护的,如果lru满了则删除最长时间未使用的。 如果一个资源加载的时候在弱引用缓存中找不到,就会到lru缓存中查找,如果没找到就去磁盘缓存找,不管在lru中找到 还是在磁盘中找到, 都会把命中的缓存,放在弱引用缓存中.

glide设计的三层缓存模型, 其中内存缓存 减轻了磁盘缓存的压力,避免io操作,而弱引用缓存则减轻了 lru缓存的压力,避免lru过满导致频繁gc,并且提高查找效率

另外再说下 glide4 五种缓存策略 data source all no automic data则是缓存住原始图片资源, source则是缓存住变换后的资源(glide 会针对imageview的大小对图片预先变换,避免小imageview加载大图片浪费内存) all 就是两种都缓存, no就是不缓存 ,automiic 则比较特殊,会缓存data 根据图片的编码 自行判断是否缓存resource

Drawable与Bitmap

juejin.cn/post/684490…