Glide 的缓存机制

Glide 的缓存机制,需要了解 LruCache 和 DiskLruCache,可以参考以下两篇博文

https://blog.csdn.net/guolin_blog/article/details/28863651

下面我们通过写一个阉割版的Glide来了解一下Glide的缓存机制

首先看一下Glide的一行代码经典用法

private final String IMAGE_URL = "http://p1.pstatp.com/large/166200019850062839d3";

 Glide.with(this)
      .load(IMAGE_URL)
      .into(imageView);

在 with()函数中,Glide 使用一个没有界面的空白Fragment 实现了对生命周期(Activity Fragmet等的生命周期)的监听,Glide的缓存机制是在into()函数中实现的

Glide 的缓存主要分为内存中的缓存和本地磁盘中的缓存,我们通过RequestTargetEngine实现生命周期的监听以及网络加载成功后对图片资源的回调,先看代码

    private ActiveCache activeCache; // 活动缓存
    private MemoryCache memoryCache; // 内存缓存
    private DiskLruCacheImpl diskLruCache; //磁盘缓存

    public void into(ImageView imageView) {
        this.imageView = imageView;
        Tool.checkNotEmpty(imageView); // 检测:是否是空
        Tool.assertMainThread(); // 检测:非主线程 抛出异常
        // 触发:缓存机制
        Value value = cacheAction();
        if (value != null) {
            imageView.setImageBitmap(value.getBitmap()); //显示缓存额图片
        }
    }

其中 ActiveCache MemoryCache DiskLruCacheImpl 分别代表了活动缓存、内存缓存、磁盘缓存

ActiveCache 是通过一个HashMap来实现对图片资源的存储,Map的键是将图片的url地址解码后返回的一个只包含16进制字符的唯一的字符串,Value里包含了图片的位图

public class ActiveCache {
    private Map<String, Value> mapList = new HashMap<>();

MemoryCache 使用了Lru算法,实现比较简单直接使用了android的LruCache,重写了sizeOf()方法

public class MemoryCache extends LruCache<String, Value> {
   
   

DiskLruCacheImpl 直接继承了 DiskLruCache,也使用了Lru算法,接下来我们看下 cacheAction()函数,该函数实现了缓存机制

private Value cacheAction() {
        // TODO 第一步:判断活动缓存是否有资源,如果有资源就返回,否则就继续往下找
        Value value = activeCache.get(key);
        if (null != value) {
            Log.d(TAG, "cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>");
            return value;
        }

        // TODO 第二步:判断内存缓存是否有资源,如果有资源 剪切(内存 ---> 活动)然后返回,   否则就继续往下找
        value = memoryCache.get(key);
        if (null != value) {
            Log.d(TAG, "cacheAction: 本次加载的是在(内存缓存)中获取的资源>>>");
            //移动操作 将资源从内存缓存剪切到活动缓存
            activeCache.put(key, value); //把内存缓存中的元素,加入到活动缓存中...
            memoryCache.remove(key); //移除内存缓存
            return value;
        }

        // TODO 第三步:从磁盘缓存中你去找,如果找到了,把磁盘缓存的元素 加入到 活动缓存中...
        value = diskLruCache.get(key);
        if (null != value) {
            Log.d(TAG, "cacheAction: 本次加载的是在(磁盘缓存)中获取的资源>>>");
            //把磁盘缓存中的元素加入活动缓存中....这里不是剪切是复制
            activeCache.put(key, value);
            return value;
        }

        // TODO 第四步:真正去加载外部资源 HTTP / 本地io
        //note:只有本地资源才会直接返回一个 Value对象,如果从网络获取loadResource()方法将返回null
        //最终通过异步回调来得到Value对象,也就是在 responseSuccess 方法中返回
        value = new LoadDataManager().loadResource(path, this, glideContext);
        if (value != null) {
            return value;
        }
        return null;  // 有意这样做的,为了后需好判断
    }

资源加载顺序:活动缓存(ActiveCache)--->内存缓存(MemoryCache)--->磁盘缓存(DiskLruCache)--->外部资源(Http网络加载/本地io)

通过cacheAction()函数可以看出,Glide会首先从活动缓存 ActiveCache 中去获取图片资源,之后会从内存缓存 MemoryCache 中去获取,然后从本地磁盘DiskLruCache中获取

如果以上都无法获取资源将会从外部加载资源,一般通过Http进行异步网络加载,加载成功后通过接口将资源回调回来,这里我们会有一个疑问....

问题:我们既然已经有了实现了Lru算法的 内存缓存MemoryCache,为什么还有再加一个活动缓存 ActiveCache?

内存缓存是通过 Lru算法实现的,也就是最近最少使用的资源将会被移除,会有这么一种情况,被移除的资源恰好是我们页面中正在显示的图片资源,此时如果我们的资源是从内存缓存MemoryCache中获取的将会发生异常(因为正在显示的资源被移除了),所以每次显示的图片都是先从 活动缓存中 ActiveCache中获取的,活动缓存是用一个Map来存储的没有使用LRU算法,所以被显示的资源并不会移除,接下来看下loadResource()函数是怎么加载资源的

 public Value loadResource(String path, ResponseListener responseListener, Context context) {
        this.path = path;
        this.responseListener = responseListener;
        this.context = context;
        // 加载   网络图片   本地存储图片  ......
        Uri uri = Uri.parse(path);

        //从网络加载图片
        if ("HTTP".equalsIgnoreCase(uri.getScheme()) || "HTTPS".equalsIgnoreCase(uri.getScheme())) {
            // 线程池
            // 异步
            new ThreadPoolExecutor(0,
                    Integer.MAX_VALUE,
                    60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>()).execute(this); // 一执行后   run 函数执行 异步操作
        }
        // SD本地资源直接返回Value,本地资源不需要异步线程,所以可以自己返回
        // ....
        return null;
    }

我们来看一下线程池中的 run()函数

    private String path;//图片url地址
    private ResponseListener responseListener;//回调接口   

    @Override
    public void run() {
        InputStream inputStream =  null;
        HttpURLConnection httpURLConnection = null; //HttpURLConnection内部已经是Okhttp,因为太高效了

        try {
            URL url = new URL(path);
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setConnectTimeout(5000);
            final int responseCode = httpURLConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) { // 200
                inputStream = httpURLConnection.getInputStream();
                final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                // TODO 省略Bitmap 做缩放,做比例,做压缩的代码,重点只关注缓存
                // ......
                //note:注意资源的回调要在主线程中完成,这里通过 post一个runnable的
                //方法将异步线程中的资源切换到主线程 Looper.getMainLooper()-->UI线程
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        Value value = new Value();
                        value.setBitmap(bitmap);
                        // 回调成功
                        responseListener.responseSuccess(value);
                    }
                });
            } else {
                // 失败 切换主线程
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        // 回调失败
                        responseListener.responseException(new IllegalStateException("请求失败,请求码:" + responseCode));
                    }
                });
            }
        }catch (Exception e) {
            e.printStackTrace();
        } finally {//finally中关闭资源
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.d(TAG, "run: 关闭 inputStream.close(); e:" + e.getMessage());
                }
            }

            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
        }
    }

相关接口及方法的回调

   /**
     * 外置资源成功回调
     * @param value
     */
    @Override
    public void responseSuccess(Value value) {
        if (null != value) {
            saveCache(key, value);//如果加载成功就将资源保存在缓存中
            Log.d(TAG, "本次加载的是在(网络)中获取的资源>>>");
            imageView.setImageBitmap(value.getBitmap());//显示图片
        }
    }

外部资源一旦加载成功,我们需要将加载成功后的资源保存到磁盘缓存中,这样下次再次加载同一资源就可以直接从磁盘缓存(内存缓存)中获取了

   /**
     * 外置资源加载成功后  保存到磁盘缓存
     */
    private void saveCache(String key, Value value) {
        Log.d(TAG, "saveCache: >>>>>>>>>>>>>>>>>>>>>>>>>> 加载外置资源成功后 ,保存到缓存中, key:" + key + " value:" + value);
        value.setKey(key);
        if (diskLruCache != null) {
            diskLruCache.put(key, value); //保存到磁盘缓存中....
            //activeCache.put(key, value);//是否保存到活动缓存自由控制
        }
    }

最后我们看一下该缓存机制的执行情况:

场景一:这个是第一次安装打开app后,通过日志看出:当第一次点击加载图片,图片资源是从(网络)中获取的,第二次再次点击加载按钮,图片是从(磁盘缓存)中获取的第三次点击加载按钮,图片是从(活动缓存)中获取,之后无论点击多少次,只要不退出加载页面,图片都会从活动缓存中加载

2021-01-20 16:55:17.008 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经开启了 初始化了....
2021-01-20 16:55:17.899 32265-32265/com.example.customglide D/RequestTargetEngine: saveCache: >>>>>>>>>>>>>>>>>>>>>>>>>> 加载外置资源成功后 ,保存到缓存中, key:ac037ea49e34257dc5577d1796bb137dbaddc0e42a9dff051beee8ea457a4668 value:com.example.customglide.resource.Value@91c4ee1
2021-01-20 16:55:19.482 32265-32265/com.example.customglide D/RequestTargetEngine: 本次加载的是在(网络)中获取的资源>>>
2021-01-20 16:55:40.127 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:40.272 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(磁盘缓存)中获取的资源>>>
2021-01-20 16:55:45.562 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:45.564 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 16:55:48.231 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:48.233 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 16:55:48.392 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:48.394 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 16:55:48.539 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:48.540 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>

 

场景二:退出当前加载页面(没有杀死app),然后再次进入加载页面,第一次点击加载按钮,图片是从内存缓存中获取的资源,因为虽然退出了app加载页面,但是app进程并没有被杀死 所以内存缓存中的资源还在,当我们退出加载页面时,Glide会监听到该Activity已经destroy,此时Glide会释放活动缓存中的资源,所以当我们再次打开加载页面第一次点击加载按钮时将会从(内存缓存)中加载资源,之后无论点击几次(加载页面不退出的情况下)都将从活动缓存中获取

2021-01-20 17:00:08.035 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经停止中 ....
2021-01-20 17:00:08.044 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 进行释放操作 缓存策略释放操作等  释放 活动缓存的所有资源 >>>>>> ....
2021-01-20 17:00:25.686 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2null
2021-01-20 17:00:25.688 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(内存缓存)中获取的资源>>>
2021-01-20 17:00:25.691 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经开启了 初始化了....
2021-01-20 17:00:31.091 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b7a2ae #0 Fragment_Activity_NAME}
2021-01-20 17:00:31.093 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 17:00:34.590 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b7a2ae #0 Fragment_Activity_NAME}
2021-01-20 17:00:34.592 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>

 

场景三:直接杀死app进程后再重新打开,进入加载页面第一次点击加载按钮时,Glide将从磁盘缓存中加载,因为当我们杀死app进程的时候,内存缓存也将被释放,此时只有磁盘缓存中还保留之前从网络加载的资源,之后无论点击几次(加载页面不退出的情况下)都将从活动缓存中获取

2021-01-20 17:04:23.759 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(磁盘缓存)中获取的资源>>>
2021-01-20 17:04:23.801 498-498/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经开启了 初始化了....
2021-01-20 17:04:28.125 498-498/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b4bc01 #0 Fragment_Activity_NAME}
2021-01-20 17:04:28.127 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 17:04:29.829 498-498/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b4bc01 #0 Fragment_Activity_NAME}
2021-01-20 17:04:29.830 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 17:04:30.961 498-498/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b4bc01 #0 Fragment_Activity_NAME}
2021-01-20 17:04:30.962 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>

 

Guess you like

Origin blog.csdn.net/lollo01/article/details/112878607