概述
说起缓存, 大家可能很容易想到 Http 的缓存机制, LruCache, 其实缓存最初是针对于网络而言的, 也是狭义上的缓存, 广义的缓存是指对数据的复用, 我这里提到的也是广义的缓存, 比较常见的是内存缓存以及磁盘缓存, 不过要想进一步理解缓存体系, 其实还需要复习一点计算机知识
CPU
CPU 分为运算器跟控制器, 是计算机的主要设备之一, 功能主要是解释计算机指令以及处理计算机软件中的数据计算机的可编程性主要是指对中央处理器的编程中央处理器内部存储器和输入 / 输出设备是现代电脑的三大核心部件
存储器
存储器的种类很多, 按用途可以分为主存储器和辅助存储器, 下面依次介绍一下
主存储器
又称内存是 CPU 能直接寻址的存储空间, 它的特点是存取速率快内存一般采用半导体存储单元, 包括随机存储器 (Random Access Memory) 只读存储器 (Read Only Memory) 和高级缓存(Cache)
RAM: 随机存储器可以随机读写数据, 但是电源关闭时存储的数据就会丢失;
ROM: 只能读取, 不能更改, 即使机器断电, 数据也不会丢失
Cache: 它是介于 CPU 与内存之间, 常用有一级缓存 (L1) 二级缓存 (L2) 三级缓存 (L3)(一般存在于 Intel 系列) 它的读写速度比内存还快, 当 CPU 在内存中读取或写入数据时, 数据会被保存在高级缓冲存储器中, 当下次访问该数据时, CPU 直接读取高级缓冲存储器, 而不是更慢的内存
辅助存储器
辅助存储器又称外存储器, 简称外存, 对于电脑而言, 通常说的是硬盘或者光盘等, 对于手机一般指的是 SD 卡, 不过现在很多厂商都已经整合在一起了
缓存类型
内存缓存: 这里的内存主要指的存储器缓存
磁盘缓存: 这里主要指的是外部存储器, 电脑指的是硬盘, 手机的话指的就是 SD 卡
缓存容量
就是缓存的大小, 到达这个限度之后, 那么就需要进行缓存清理了
缓存策略
不管是内存缓存还是磁盘缓存, 缓存的容量都是有限制的, 所以跟线程池满了之后的线程处理策略类似, 缓存满了的时候, 我们也需要有相应的处理策略, 常见的策略有:
FIFO(first in first out): 先进先出策略, 类似队列
LFU(less frequently used): 最少使用策略, RecyclerView 的缓存采用了此策略
LRU(least recently used): 最近最少使用策略, Picasso 在进行内存缓存的时候采用了此策略
当缓存容量达到设定的容量的时候, 会根据制定的策略进行删除相应的元素
内存泄露
这个主要发生在内存缓存中, 当生命周期段的对象持有了生命周期长的对象的引用就会发生内存泄露, 解决这种问题通常有两种方式
引用置空: 将缓存中引用的对象置空, 然后 GC 就能够回收这些对象
采用弱引用: 采用弱引用关联对象, 这样就能够不干涉对象的生命周期, 以便 GC 能够正常回收
实际上在防止内存泄露的过程中这两种方式都使用地比较平凡, 不过我们大多数时候使用的还是弱引用
其实 Java 有四种引用, 强引用, 软引用, 弱引用, 虚引用, 这些并没什么好说的, 我们平时使用最多的还是弱引用, 也就是 WeakReference
弱引用 VS 软引用:
只具有弱引用的对象拥有更短暂的生命周期在垃圾回收器线程扫描它所管辖的内存区域的过程中, 一旦发现了只具有弱引用的对象, 不管当前内存空间足够与否, 都会回收它的内存不过, 由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象
下面简单描述一下这两种防止内存泄露的方法的区别
引用置空
RecyclerView 的内部类 LayoutManager 持有了 RecyclerView 的使用, 没有采用弱引用, 但是提供了置空的方法
- public static abstract class LayoutManager {
- ChildHelper mChildHelper;
- RecyclerView mRecyclerView;@Nullable SmoothScroller mSmoothScroller;
- private boolean mRequestedSimpleAnimations = false;
- boolean mIsAttachedToWindow = false;
- private boolean mAutoMeasure = false;
- private boolean mMeasurementCacheEnabled = true;
- private int mWidthMode,
- mHeightMode;
- private int mWidth,
- mHeight;
- void setRecyclerView(RecyclerView recyclerView) {
- if (recyclerView == null) {
- // 回收
- mRecyclerView = null;
- mChildHelper = null;
- mWidth = 0;
- mHeight = 0;
- } else {
- // 初始化
- mRecyclerView = recyclerView;
- mChildHelper = recyclerView.mChildHelper;
- mWidth = recyclerView.getWidth();
- mHeight = recyclerView.getHeight();
- }
- mWidthMode = MeasureSpec.EXACTLY;
- mHeightMode = MeasureSpec.EXACTLY;
- }
采用弱引用
用 Picasso 中的 Action 为例, 父类采用了 WeakReference
Action 父类
- abstract class Action < T > {
- final WeakReference < T > target;
- Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
- this.picasso = picasso;
- this.request = request;
- this.target = target;
- this.memoryPolicy = memoryPolicy;
- this.networkPolicy = networkPolicy;
- this.noFade = noFade;
- this.errorResId = errorResId;
- this.errorDrawable = errorDrawable;
- this.key = key;
- this.tag = (tag != null ? tag: this);
- }
ImageAction 子类
- class ImageViewAction extends Action<ImageView> {
- Callback callback;
- ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
- int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
- Callback callback, boolean noFade) {
- super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,tag, noFade);
- this.callback = callback;
- }
- @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
- if (result == null) {
- throw new AssertionError(
- String.format("Attempted to complete action with no result!\n%s", this));
- }
- ImageView target = this.target.get();
- if (target == null) {
- return;
- }
- Context context = picasso.context;
- boolean indicatorsEnabled = picasso.indicatorsEnabled;
- PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
- }
由于 ImageView 持有 Context 的引用, 所以导致 Activity 回收之后, 如果 ImageView 是强引用, 那么 GC 就不会去回收, 而采用了弱引用之后, 一旦 Activity 被回收, 那么 ImageViewAction 的引用不会干扰到 Activity 的回收
缓存时间
根据业务需要可以自行设定, 但是注意, 缓存的其实判断时间都应该以服务器时间为准, 可以从服务器的返回数据的 Response 的 header 中的时间戳作为判断依据
读取顺序
内存缓存读取速度远远高于磁盘缓存, 我们都知道 Picasso 是采用了内存缓存跟磁盘缓存这两种缓存的, 但是他获取的时候首先是从内存中进行读取, 然后把磁盘缓存加到网络缓存中去, 其实一开始, 我不是这样子做的, 我是把内存缓存, 磁盘缓存以及网络缓存读取都实例化了一个 Runnable, 然后在加载下一页的时候, 总是会出现图片闪烁, 但是我用 Picasso,UIL 跟 Glide 就不会闪烁, 但是当我设置 Picasso 他们的内存缓存策略为 MemoryPolicy.NO_CACHE 的时候, 他们也会闪烁, 下面展示一下闪烁的效果
其实上面两种情况都会出现闪烁, 共同原因就是因为内存缓存的问题, Picasso 的 issue 里面有人提过, 作者 JakeWharton 是这么回答的
是的 200ms, 如果 Bitmap 没有读取成功, 那么就会出现闪烁, 这样正好解释了上面的两种情况, 由于我们设置了占位图, 第一种闪烁是因为我们把内存缓存的读取放到了一个线程里面, 线程的创建, 切换这些都是需要时间的, 那么就导致了总时间会超过 200ms; 同理, 第二种情况如果没有设置内存缓存, 那么只能从网络或磁盘中读取这个时间肯定会超过 200ms, 同样会闪烁, 所以这也是为什么图片加载框架优先从内存中读取, 当不设置内存缓存的时候也会闪烁的原因
同时磁盘缓存需要借助于 Http 缓存机制来保证缓存的时效性, 后面会具体分析
总结
其实缓存的改变比较好理解, 就是在使用内存缓存的时候需要注意防止内存泄露, 使用磁盘缓存的时候需要注意结合 Http 的缓存机制来来确保缓存的时效性
来源: https://juejin.im/post/5a7566a7f265da4e84090706