三方框架必学系列#Glide

60 阅读10分钟

三方框架必学系列#EventBus

三方框架必学系列#Rxjava

三方框架必学系列#Retrofit

三方框架必学系列#OkHttp

三方框架必学系列#Glide

三方框架必学系列#屏幕适配

一.资源编写

这块主要是针对缓存的,主要有缓存的path-key的转换,以及key所对应的bitmap的value,并且我们在value中使用计数器的方式进行回收,若无使用可回收bitmap,并且提供回调。

public class KeyBitMap {

    private String key; //对请求地址的进行SHA256得到特定的值


    public KeyBitMap(String key) {
        this.key = Tools.getSHA256StrJava(key);
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getKey() {
        return key;
    }
}

public class ValueBitMap {

    private ValueCallBack valueCallBack;

    public void setValueCallBack(ValueCallBack valueCallBack) {
        this.valueCallBack = valueCallBack;
    }



    /**
     * 这里做了单例?
     *
     * @return
     */


    private String key;
    private Bitmap bitmap;

    private int countUse = 0; //是否被使用标记

    /**
     * 使用1次计数1次
     */
    public void useAction() {
        Tools.checkEmpty(bitmap);
        if (bitmap.isRecycled()) {
            System.out.println("ValueBitMap  useAction bitmap has recycle");
            return;
        }
        countUse++;

    }

    /**
     * 不使用
     */
    public void nonUseAction() {
        countUse--;
        if (countUse <= 0 && valueCallBack != null) {
            //回调不在使用
            valueCallBack.nonUseListener(key, this);
           // recycleBitMap();
        }
    }

    /**
     * 释放bitmap
     */
    public void recycleBitMap() {
        System.out.println("ValueBitMap recycleBitMap recycleBitMap");
        if (countUse > 0) {
            System.out.println("ValueBitMap recycleBitMap bitmap 还再用,不可以用回收");
            return;
        }
        if (bitmap.isRecycled()) {
            System.out.println("recycleBitMap 已经被回收了");
            return;
        }
        bitmap.recycle();
        System.gc();
    }


    public Bitmap getBitmap() {
        return bitmap;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }
}
public interface ValueCallBack {
    /**
     * 计数器清0回调
     * @param key
     * @param valueBitMap
     */
    void nonUseListener(String key,ValueBitMap valueBitMap);
}

二.缓存策略的三条线

gilde的缓存策略:

1.活动缓存:正在使用的缓存,加载时优先查早活动缓存,若无继续往下查找,它的回收策略是弱引用和gc,回收后放置内存缓存中,来源可以是内存缓存或磁盘缓存,注意内存缓存找到后会清楚掉内存缓存;

2.内存缓存:遵循lru算法的缓存,为活动缓存提供资源来源,非手动移除的资源会被放置Bitmap缓存池,如果内存缓存找不到会从磁盘缓存中获取,如果找到了提供给给活动缓存,并手动移除;

3.磁盘缓存:遵循lru算法的缓存,如果找到了直接提供给活动缓存,并且可以复用bitmap缓存池中的bitmap,如果未找到就去网络中加载;

4.本地/网络:加载资源,就没有什么好说的了。

三.活动缓存

  1. 数据结构:使用HashMap<Key, ResourceWeakReference> 存储资源,其中ResourceWeakReference是弱引用( WeakReference )的子类,指向图片资源对象。
  2. 引用管理:
    1. 弱引用确保资源未被强引用持有时可被GC回收。
    2. 配合ReferenceQueue监听回收事件,自动清理无效缓存项。
  1. 引用计数:
    1. 同一资源被多个ImageView使用时,引用计数递增。
    2. 当引用归零时,资源从活动缓存手动移除并转移到内存LRU缓存。
  1. 工作流程
    1. 资源被使用时加入活动缓存 activeResources.activate(key, resource)
    2. 资源释放时移出: activeResources.deactivate(key)
public class ActiveCache {

    private boolean isManuRemove = false; //是否手动移除
    private Thread checkGcThread;
    private boolean stopCheckGc = false;

    //活动缓存的容器
    private Map<String, WeakReference<ValueBitMap>> activeMap = new HashMap<>();

    //GC中的移除
    private ReferenceQueue<ValueBitMap> removeReferQueue;

    private ValueCallBack valueCallBack;

    public ActiveCache(ValueCallBack valueCallBack) {
        this.valueCallBack = valueCallBack;
    }


    private ReferenceQueue getReferenceQueue() {
        if (null == removeReferQueue) {
            removeReferQueue = new ReferenceQueue<>();
            //检测GC移除
            checkGcThread = new Thread(new Runnable() {
                @Override
                public void run() {

                    while (!stopCheckGc) {
                        try {
                            if (!isManuRemove) { //判断下是否是手动的,如果是手动的就不移除了
                                //如果gc中有数据就会执行,不然就阻塞在此
                                Reference<? extends ValueBitMap> remove = removeReferQueue.remove();
                                ValueBitMap value = remove.get();
                                if (activeMap == null && !activeMap.isEmpty()) {
                                    System.out.println("ActiveCache gc remove");
                                    activeMap.remove(value.getKey());
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            checkGcThread.start();
        }
        return removeReferQueue;
    }

    /**
     * 1.中断检测gc
     * 2.清除活动缓存
     * 3.gc
     */
    public void stopCheckGc() {
        System.out.println("ActiveCache stopCheckGc");
        stopCheckGc = true;
        //        if (null != checkGcThread) {
        //            checkGcThread.interrupt(); //手动中断线程
        //            try {
        //                checkGcThread.join(TimeUnit.SECONDS.toMillis(5));//5秒 线程稳定安全停止
        //                if (checkGcThread.isAlive()) { //如果线程依旧未关闭
        //                    throw new IllegalStateException("活动缓存中线程关闭异常");
        //                }
        //            } catch (InterruptedException e) {
        //                e.printStackTrace();
        //            }
        //        }
        activeMap.clear();
        System.gc();
    }

    /**
     * 添加到活动缓存
     *
     * @param key   加密后的key
     * @param value
     */
    public void put(String key, ValueBitMap value) {
        // System.out.println("this:"+this);
        Tools.checkEmpty(key);
        value.setValueCallBack(valueCallBack);
        WeakReference<ValueBitMap> valueBitMapWeakReference = new ValueWeakReference(value, getReferenceQueue(), key);
        activeMap.put(key, valueBitMapWeakReference);
        // System.out.println("ActiveCache put get: key "+ key+",value:"+valueBitMapWeakReference.get());

    }

    /**
     * 获取活动缓存
     *
     * @param key
     * @return
     */
    public ValueBitMap get(String key) {

        ValueBitMap valueBitMap = null;
        WeakReference<ValueBitMap> valueBitMapWeakReference = activeMap.get(key);
       // System.out.println("ActiveCache get: get1 "+ key+",valueBitMapWeakReference:"+valueBitMapWeakReference);
        if (null != valueBitMapWeakReference) {
            valueBitMap = valueBitMapWeakReference.get();
        }
        //System.out.println("ActiveCache get: get2 "+ key+",value:"+valueBitMap);
        return valueBitMap;
    }

    /**
     * 手动移除
     *
     * @param key 加密后的key
     * @return
     */
    public ValueBitMap manualRemove(String key) {
        System.out.println("ActiveCache manualRemove: key "+ key);
        isManuRemove = true;
        WeakReference<ValueBitMap> valueBitMapWeakReference = activeMap.remove(key);
        isManuRemove = false;
        if (null != valueBitMapWeakReference) {
            return valueBitMapWeakReference.get();
        }
        return null;
    }

    class ValueWeakReference extends WeakReference<ValueBitMap> {

        private String key;

        public ValueWeakReference(ValueBitMap referent, ReferenceQueue<? super ValueBitMap> q, String key) {
            super(referent, q);
            this.key = key;
        }
    }


}

四.内存缓存

依靠lru算法,若是非手动移除,回调移除对象。

public class MemoryCache extends LruCache<String, ValueBitMap> {

    public void setCacheCallBack(MemoryCacheCallBack cacheCallBack) {
        this.cacheCallBack = cacheCallBack;
    }

    private boolean isManuRemove = false;

    /**
     * 手动移除内存缓存
     *
     * @param key
     * @return
     */
    public ValueBitMap manuRemove(String key) {
        isManuRemove = true;
        ValueBitMap valueBitMap = remove(key);
        isManuRemove = false;
        return valueBitMap;
    }

    private MemoryCacheCallBack cacheCallBack;

    public MemoryCache(int maxSize) {
        super(maxSize);
    }


    /**
     * 内存缓存被移除
     */
    @Override
    protected void entryRemoved(boolean evicted, String key, ValueBitMap oldValue, ValueBitMap newValue) {
        super.entryRemoved(evicted, key, oldValue, newValue);
        if (null != cacheCallBack && !isManuRemove) {
            System.out.println("MemoryCache entryRemoved");
            cacheCallBack.onMemoryRemoveListener(key, oldValue);
        }
    }

    @Override
    protected int sizeOf(String key, ValueBitMap value) {
        Bitmap bitmap = value.getBitmap();
        return Tools.getBitmapSize(bitmap);
    }

    public  interface MemoryCacheCallBack {
        void onMemoryRemoveListener(String key, ValueBitMap valueBitMap);
    }
}

五.磁盘缓存

磁盘缓存:是对jakeWharton的DiskLruCache的二次封装,提供读取,写入功能

public class DiskCache {
    private BitmapPool bitmapPool = BitmapPoolImpl.getInstance();
    private final String DISK_PATH = "disk_lru_dir";
    private final long CACHE_SIZE = 10_1024_1024;
    private final int APP_VERSION = 0; //改了后 缓存会失效
    private final int VALUE_COUNT = 1;//通常是1
    private DiskLruCache diskLruCache;

    public DiskCache() {
        File file = new File(Environment.getExternalStorageDirectory()+
                             File.separator + DISK_PATH);
        try {
            System.out.println("filePath:"+file.getPath());
            diskLruCache = DiskLruCache.open(file, APP_VERSION, VALUE_COUNT, CACHE_SIZE);
        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    public void put(String key, ValueBitMap valueBitMap) {
        DiskLruCache.Editor editor = null;
        OutputStream outputStream = null;
        try {
            editor =
            diskLruCache.edit(key);
            outputStream = editor.newOutputStream(0); //index不能大于valueCount
            valueBitMap.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, outputStream); //将bitmap写入到磁盘文件中去
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            try {
                editor.abort();
            } catch (IOException ex) {
                System.out.println("editor 提交失败");
                e.printStackTrace();
            }
        } finally {
            try {
                editor.commit();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("editor commit提交失败");
            }
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println();
                }
            }
        }
    }

    public ValueBitMap get(String key) {
        Tools.checkEmpty(key);
        ValueBitMap valueBitMap = new ValueBitMap();
        DiskLruCache.Snapshot snapshot;
        InputStream inputStream = null;
        try {
            snapshot = diskLruCache.get(key);
            if (null != snapshot) {
                inputStream = snapshot.getInputStream(0);



                //--------------------缓存池的策略有问题,算了不处理了,大概知道原理即可---------------------------

                // 将输入流的数据读取到字节数组中 只读不移动
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    byteArrayOutputStream.write(buffer, 0, bytesRead);
                }
                byte[] imageData = byteArrayOutputStream.toByteArray();
                // 第一次获取图片大小
                BitmapFactory.Options options = new BitmapFactory.Options(); //入参出参对象
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options); //获取下图片的大小
                int outHeight = options.outHeight;
                int outWidth = options.outWidth;
                System.out.println("outHeight:" + outHeight + ",outWidth:" + outWidth);


                BitmapFactory.Options options2 = new BitmapFactory.Options(); //入参出参对象
                boolean hitPool = false;
                Bitmap bitmapPoolBitmap = bitmapPool.getBitmap(outWidth, outHeight, Bitmap.Config.RGB_565);
                if (bitmapPoolBitmap == null) {
                    System.out.println("not found in pool---->>>");
//                    //如果缓存池没有 就用解析出来的
                    hitPool = false;
                } else {
                    System.out.println(" found in pool---->>>");
                    hitPool = true;
                }
                //如果inBitmap设置为null,内部就会申请新的内存空间,无法复用,如果缓存中没有肯定是的
                options2.inBitmap = bitmapPoolBitmap;//把复用池的bitMap给inBitMap
                options2.inPreferredConfig = Bitmap.Config.RGB_565;
                options2.inMutable = true;
                options2.inJustDecodeBounds = false;
                final Bitmap bitmap2 = BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options2);//复用的是内存

                if(!hitPool){
                    bitmapPool.putBitmap(bitmap2);
                }

                valueBitMap.setBitmap(bitmap2);
               //-----------------------------------------------

//                valueBitMap.setBitmap(BitmapFactory.decodeStream(inputStream));




                valueBitMap.setKey(key);
                System.out.println("本地磁盘获取成功");
                return valueBitMap;
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("本地磁盘获取失败!");
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("本地磁盘获取失败!");
                }
            }
        }
        return null;
    }
}

六.生命周期的感知

生命周期的原理是通过在Activity中创建一个空的fragment,在fragment的生命周期变化中操作glide加载引擎。主要是由RequestRetriever、RuquestManager、RequestEngin、以及生命周期回调接口ActivityFragment,各自的职责为:

1.RequestRetriever提供RuquestManager;

2.RuquestManager添加感知生命周期的fragment,并且提供可以感知生命周期的RequestEngin,所以RequestEngin持有生命接口周期的回调或者直接继承生命周期回调接口ActivityFragment,在创建感知生命周期的fragment时直接传入RequestEngin,当有生命周期变化时直接回调其对应的方法。

1.生命周期感知接口

public interface MyActivityFragmentLife {

    void glideInitAction();
    void glideStopAction();

    void glideRecycleAction();
}

2.生命周期提供者:持有感知接口

public class MyFragmentActivityFragment extends Fragment {

    private MyActivityFragmentLife myActivityFragmentLife;

    public MyFragmentActivityFragment() {

    }

    public MyFragmentActivityFragment(RequestEngin requestEngin) {
        this.myActivityFragmentLife = requestEngin;

    }

    @Override
    public void onStart() {
        super.onStart();
        if (null != myActivityFragmentLife) {
            myActivityFragmentLife.glideInitAction();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (null != myActivityFragmentLife) {
            myActivityFragmentLife.glideStopAction();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (null != myActivityFragmentLife) {
            myActivityFragmentLife.glideRecycleAction();
        }
    }
}

3.感知生命周期的回调实现(加载引擎)

public class RequestEngin implements MyActivityFragmentLife {

    private final ActiveCache activeCache;
    private final MemoryCache memoryCache;
    private DiskCache diskCache;
    private final int MEMORY_CACHE_SIZE = 10 * 1024 * 1024;

    private ImageView imageView;

    private String path;
    private String key;
    private Context glideContext;

    private ILoader iLoader;

    private BitmapPool bitmapPool = BitmapPoolImpl.getInstance();

    public RequestEngin() {
        activeCache = new ActiveCache(new ValueCallBack() {
            @Override
            public void nonUseListener(String key, ValueBitMap valueBitMap) {
                //引用归0时回调 活动缓存回收了
                System.out.println("活动缓存手动回收"+key+",value:"+valueBitMap);
                //1.手动移除
                activeCache.manualRemove(key);
                //2.放到内存缓存中去
                memoryCache.put(key, valueBitMap);

            }
        });
        memoryCache = new MemoryCache(MEMORY_CACHE_SIZE);
        memoryCache.setCacheCallBack(new MemoryCache.MemoryCacheCallBack() {
            @Override
            public void onMemoryRemoveListener(String key, ValueBitMap valueBitMap) {

                System.out.println("内存缓存被回收了");
                //内存缓存被动丢出 加入到复用池
                bitmapPool.putBitmap(valueBitMap.getBitmap());


            }
        });
        diskCache = new DiskCache();

    }

    @Override
    public void glideInitAction() {
        System.out.println("glideInitAction");
    }

    @Override
    public void glideStopAction() {
        System.out.println("glideStopAction");

    }

    @Override
    public void glideRecycleAction() {

        System.out.println("glideRecycleAction");
        if (null != activeCache) {
            activeCache.stopCheckGc();
        }
    }

    public void loadValueInitAction(String path, Context requestContext) {
        this.path = path;
        this.glideContext = requestContext;
        this.key = new KeyBitMap(this.path).getKey();//缓存存储的key

    }

    public void into(ImageView imageView) {

        Tools.checkEmpty(imageView);
        Tools.assertMainThread();

        this.imageView=imageView;
        ValueBitMap value = cacheAction();//从缓存中获取
        if (null != value) { //从缓存中获取到
            imageView.setImageBitmap(value.getBitmap());
            value.nonUseAction();//模拟完成之后减一 这个时候会回调删除活动缓存
        }

    }

    /**
     * 1.活动缓存去找,活动缓存自动移除后会移动到内存缓存中去
     * 2.内存中去找,找到了手动移除内存缓存后,放置活动缓存 LRU回收后放到Bitmap缓存池中
     * 3.磁盘缓存中去找,找到后放置活动缓存中去
     * 4.网络加载,保存到磁盘中去
     *
     * @return
     */
    private ValueBitMap cacheAction() {

        ValueBitMap valueBitMap = activeCache.get(key);

        System.out.println("cacheAction key:"+key);
        if (null != valueBitMap) {
            valueBitMap.useAction();//使用加1
            System.out.println("cacheAction 使用活动缓存");
            return valueBitMap;
        }
        valueBitMap = memoryCache.get(key);
        if (null != valueBitMap) {
            activeCache.put(key, valueBitMap); //
            memoryCache.manuRemove(key);//手动移除 节省空间 因为已经移动到活动缓存了
            System.out.println("cacheAction 使用内存缓存 使用后放到活动缓存中去 移除内存缓存");
            valueBitMap.useAction();
            return valueBitMap;
        }
        valueBitMap = diskCache.get(key);

        if (null != valueBitMap) {
            System.out.println("cacheAction 使用磁盘缓存:"+valueBitMap.getKey());
            activeCache.put(key, valueBitMap);//放到活动缓存中去
            System.out.println("cacheAction 使用磁盘缓存,使用完后放到活动缓存中去 不移除本地缓存");
            valueBitMap.useAction();
            return valueBitMap;
        }

        if (null == iLoader) {
            iLoader = new LoadDataManager();
        }

        //加载
        valueBitMap = iLoader.loadResource(path, glideContext, new ILoader.ResponseListener() {
            @Override
            public void onLoadSuccess(ValueBitMap valueBitMap) {

                //网络加载成功的回调
                if (null != valueBitMap) {
                    System.out.println("网络加载成功回调显示");
                    valueBitMap.setKey(key);
                    imageView.setImageBitmap(valueBitMap.getBitmap());
                    saveCache(key, valueBitMap);
                }

            }

            @Override
            public void onLoadError(Exception exception) {

            }
        });
        //本地成功的
        if (null != valueBitMap) {
            return valueBitMap;
        }

        return null;
    }

    private void saveCache(String key, ValueBitMap valueBitMap) {
        System.out.println("网络加载成功 放到本地缓存 key:"+key);
        valueBitMap.setKey(key);
        diskCache.put(key, valueBitMap);
    }
}

4.组装生命周期感知架构类(组合生命周期以及感知回调)

public class RequestManager {

    private Context requestContext;
    private final String FRAGMENT_ACTIVITY_FRAGMENT_NAME = "fragment_activity_fragment_name";
    private final String FRAGMENT_ACTIVITY_NAME = "fragment_activity_name";
    private static RequestEngin requestEngin;

    {
        if (null == requestEngin) {
            requestEngin = new RequestEngin();
        }
    }


    public RequestManager(FragmentActivity fragmentActivity) {
        this.requestContext = fragmentActivity;
        FragmentManager supportFragmentManager = fragmentActivity.getSupportFragmentManager();
        androidx.fragment.app.Fragment fragmentByTag = supportFragmentManager.findFragmentByTag(FRAGMENT_ACTIVITY_FRAGMENT_NAME);
        if (fragmentByTag == null) {
            System.out.println("create MyFragmentActivityFragment----->>>>");
            fragmentByTag = new MyFragmentActivityFragment(requestEngin);
            supportFragmentManager.beginTransaction().add(fragmentByTag, FRAGMENT_ACTIVITY_FRAGMENT_NAME).commitAllowingStateLoss();
        }
        new Handler(Looper.getMainLooper()).sendEmptyMessage(1);

    }

    public RequestManager(Activity activity) {
        this.requestContext = activity;
        android.app.FragmentManager fragmentManager = activity.getFragmentManager();
        Fragment fragmentByTag = fragmentManager.findFragmentByTag(FRAGMENT_ACTIVITY_NAME);
        if (fragmentByTag == null) {
            fragmentByTag = new MyFragmentActivity(requestEngin);
            fragmentManager.beginTransaction().add(fragmentByTag, FRAGMENT_ACTIVITY_NAME).commitAllowingStateLoss();
        }
        new Handler(Looper.getMainLooper()).sendEmptyMessage(1);
    }

    public RequestManager(Context context) {
        this.requestContext = context;
    }

    public RequestEngin load(String path) {
        requestEngin.loadValueInitAction(path, requestContext);
        System.out.println("load requestEngin:" + requestEngin);
        return requestEngin;
    }

}

5.架构提供类(暴漏给glide使用)

public class RequestRetriever {

    public RequestManager get(FragmentActivity fragmentActivity) {

        return new RequestManager(fragmentActivity);
    }

    public RequestManager get(Activity activity) {
        return new RequestManager(activity);
    }

    public RequestManager get(Context context) {
        return new RequestManager(context);
    }
}

七.调用实现流程

我们先来看看调用的代码:

Glide.with(this)//返回requestManager
.load("https://tupian.qqw21.com/article/UploadPic/2018-3/20183162315429465.jpg")//返回requesetEngit
.into(imageView);

1.我们的glide需要提供一个with方法,并且它返回的是requestManager,由于我们前面对于requestManager是通过requesetRetriever暴露的,所以我们在glide中提供一个requesetRetriever获取的方法,代码如下:

public class Glide {

    private RequestRetriever requestRetriever;

    public Glide(RequestRetriever requestRetriever) {
        this.requestRetriever = requestRetriever;
    }

    public RequestRetriever getRequestRetriever() {
        return requestRetriever;
    }

    //通过gildeBuilder构建glide,并且在glideBuilder中调用glide的构造方法,传入RequestRetriever
    public static Glide get(Context context) {
        return new Glide.Builder().builder();
    }

    public static RequestRetriever getRetriever(Context context) {

        return Glide.get(context).getRequestRetriever();
    }

    public static RequestManager with(FragmentActivity fragmentActivity) {

        return getRetriever(fragmentActivity).get(fragmentActivity);
    }

    public static RequestManager with(Activity activity) {

        return getRetriever(activity).get(activity);
    }

    public static RequestManager with(Context context) {
        return getRetriever(context).get(context);
    }

    public static class Builder {
        Glide builder() {
            RequestRetriever requestRetriever1 = new RequestRetriever();
            return new Glide(requestRetriever1);
        }
    }
}

2.我们继续看下load方法,主要就是requesetManager中的load方法,我们可以看下感知生命周期的RequesetManager这个类,调用load方法时其实就是调用requestEngin中的load方法,并且返回了这个requestEngin这个对象,这里其实只是做些加载准备;

3.into的方法才是真正去加载图片,那其实就是缓存策略的流程了,这里就不在赘述。

八.流程架构图