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

概述

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

computer

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

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的缓存机制来来确保缓存的时效性

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容