欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Glide 的缓存机制

程序员文章站 2022-03-11 22:58:25
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 的缓存机制,需要了解 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: 本次加载的是在(活动缓存)中获取的资源>>>

 

本文地址:https://blog.csdn.net/lollo01/article/details/112878607