深刻理解Android中的缓存机制(一)缓存简介

概述

提及缓存,你们可能很容易想到Http的缓存机制,LruCache,其实缓存最初是针对于网络而言的,也是狭义上的缓存,广义的缓存是指对数据的复用,我这里提到的也是广义的缓存,比较常见的是内存缓存以及磁盘缓存,不过要想进一步理解缓存体系,其实还须要复习一点计算机知识。java

computer

CPU

CPU分为运算器跟控制器,是计算机的主要设备之一,功能主要是解释计算机指令以及处理计算机软件中的数据。计算机的可编程性主要是指对中央处理器的编程。中央处理器、内部存储器和输入/输出设备是现代电脑的三大核心部件。编程

存储器

存储器的种类不少,按用途能够分为主存储器和辅助存储器,下面依次介绍一下。缓存

主存储器

又称内存是CPU能直接寻址的存储空间,它的特色是存取速率快。内存通常采用半导体存储单元,包括随机存储器(Random Access Memory)、只读存储器(Read Only Memory)和高级缓存(Cache)。服务器

  • RAM:随机存储器能够随机读写数据,可是电源关闭时存储的数据就会丢失;
  • ROM:只能读取,不能更改,即便机器断电,数据也不会丢失
  • Cache:它是介于CPU与内存之间,经常使用有一级缓存(L1)、二级缓存(L2)、三级缓存(L3)(通常存在于Intel系列)。它的读写速度比内存还快,当CPU在内存中读取或写入数据时,数据会被保存在高级缓冲存储器中,当下次访问该数据时,CPU直接读取高级缓冲存储器,而不是更慢的内存。

辅助存储器

辅助存储器又称外存储器,简称外存,对于电脑而言,一般说的是硬盘或者光盘等,对于手机通常指的是SD卡,不过如今不少厂商都已经整合在一块儿了网络

缓存类型

  • 内存缓存:这里的内存主要指的存储器缓存
  • 磁盘缓存:这里主要指的是外部存储器,电脑指的是硬盘,手机的话指的就是SD卡

缓存容量

就是缓存的大小,到达这个限度以后,那么就须要进行缓存清理了框架

缓存策略

不论是内存缓存仍是磁盘缓存,缓存的容量都是有限制的,因此跟线程池满了以后的线程处理策略相似,缓存满了的时候,咱们也须要有相应的处理策略,常见的策略有:less

  • FIFO(first in first out):先进先出策略,相似队列。dom

  • LFU(less frequently used):最少使用策略,RecyclerView的缓存采用了此策略。ide

  • LRU(least recently used):最近最少使用策略,Picasso在进行内存缓存的时候采用了此策略。this

当缓存容量达到设定的容量的时候,会根据制定的策略进行删除相应的元素。

内存泄露

这个主要发生在内存缓存中,当生命周期段的对象持有了生命周期长的对象的引用就会发生内存泄露,解决这种问题一般有两种方式

  • 引用置空:将缓存中引用的对象置空,而后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

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的时候,他们也会闪烁,下面展现一下闪烁的效果

flicker

其实上面两种状况都会出现闪烁,共同缘由就是由于内存缓存的问题,Picasso的issue里面有人提过,做者JakeWharton是这么回答的

flick

是的200ms,若是Bitmap没有读取成功,那么就会出现闪烁,这样正好解释了上面的两种状况,因为咱们设置了占位图,第一种闪烁是由于咱们把内存缓存的读取放到了一个线程里面,线程的建立,切换这些都是须要时间的,那么就致使了总时间会超过200ms;同理,第二种状况若是没有设置内存缓存,那么只能从网络或磁盘中读取这个时间确定会超过200ms,一样会闪烁,因此这也是为何图片加载框架优先从内存中读取,当不设置内存缓存的时候也会闪烁的缘由。

同时磁盘缓存须要借助于Http缓存机制来保证缓存的时效性,后面会具体分析。

总结

其实缓存的改变比较好理解,就是在使用内存缓存的时候须要注意防止内存泄露,使用磁盘缓存的时候须要注意结合Http的缓存机制来来确保缓存的时效性

相关文章
相关标签/搜索