Android中图片的三级缓存浅析

图片的三级缓存机制一般是指应用加载图片的时候,分别去访问内存,文件和网络而获取图片数据的一种行为。以下内容只是简单的介绍了三级缓存的思想和大致流程,还有很多细节未进行处理,如果想深入研究可以在Github上找Picasso的源码进行研究,谢谢。

一、三级缓存流程图

三级缓存流程图

二、代码框架搭建

  • 这里我仿造Picasso的加载图片代码,也做出了with,load,into等方法。

2.1 with(context)

  • 这个方法传入上下文,返回ImageManager对象。

      /**
       * 初始化对象
       *
       * @param context
       * @return
       */
      public static ImageManager with(Context context) {
          mContext = context;
          return getInstance();
      }
    
  • 因为ImageManager会不断的调用,所以要做成单利。

      /**
       * 获取对象的单利
       *
       * @return
       */
      private static ImageManager instance;
      private static ImageManager getInstance() {
          if (instance == null) {
              instance = new ImageManager();
          }
    
          return instance;
      }
    

2.2 load(url)

  • 这个方法返回一个自定义的RequestCreator内部类,对图片的操作都在这个内部类中进行。

      /**
       * 加载图片的url地址,返回RequestCreator对象
       *
       * @param url
       * @return
       */
      public RequestCreator load(String url) {
    
          return new RequestCreator(url);
      }
    
  • RequestCreator的构造方法中接收传入的url 。

      // 初始化图片的url地址
      public RequestCreator(String url) {
          this.url = url;
      }
    

2.3 into(imageview)

  • 这是RequestCreator类的方法,也是工具类的核心方法,这个方法里进行图片的三个缓存处理。

三、内存缓存

3.1 Java中对象的四种引用类型介绍

  • 强引用

    • Java中所有new出来的对象都是强引用类型,回收的时候,GC宁愿抛出OOM异常,也不回收它。

        Map<String, Bitmap> mImageCache = new HashMap<>();
      
  • 软引用,SoftReference

    • 内存足够时,不回收。内存不够时,就回收。这里使用这种方式缓存对象。

        Map<String, SoftReference<Bitmap>> mImageCache = new HashMap<>();
      
  • 弱引用,WeakReference

    • GC一出来工作就回收它。
  • 虚引用,PhantomReference

    • 用完就消失。

3.2 使用LruCache类来做缓存

  • LruCache其实是一个Hash表,内部使用的是LinkedHashMap存储数据。使用LruCache类可以规定缓存内存的大小,并且这个类内部使用到了最近最少使用算法来管理缓存内存。

      LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
    

3.3 代码实现内存缓存

  • 创建缓存集合

      /**
       * 内存储存图片的集合
       * 使用lrucache缓存图片,这里不能申明在方法里,不然会被覆盖掉
       * 使用软引用类型对象
       * 4兆的大小作为缓存
       */
      private LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
    
  • 访问内存时先从集合中取出软引用,获取BitMap

      // 1 去内存之中找,有就显示,没有就往下走
      SoftReference<Bitmap> reference = mImageCache.get(url);
      Bitmap cacheBitmap;
      if(reference != null){
          cacheBitmap = reference.get();
          // 有就显示图片
          imageView.setImageBitmap(cacheBitmap);
    
          Log.d("RequestCreator:", "内存中有图片显示");
          // 不往下走了
          return;
      }
    
  • 如果缓存集合中的数据为空,就继续往下走。

四、文件缓存

4.1 缓存文件存储的路径设定

  • 存储的路径首先要考虑SD卡的缓存目录,当SD卡不存在时,就只能存到内部存储的缓存目录了。

      /**
       * 获取缓存路径目录
       */
      private File getCacheDir() {
    
          // 获取保存的文件夹路径
          File file;
          if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
              // 有SD卡就保存到sd卡
              file = mContext.getExternalCacheDir();
          } else {
              // 没有就保存到内部储存
              file = mContext.getCacheDir();
          }
          return file;
      }
    

4.2 解析文件生成Bitmap对象

  • 存储的文件的名字截取URL中的名字。

  • 文件名使用Md5加密。

      /**
       * 从文件中获取bitmap
       *
       * @return
       */
      private Bitmap getBitmapFromFile() {
          // 从url中获取文件名字
          String fileName = url.substring(url.lastIndexOf("/") + 1);
    
          File file = new File(getCacheDir(),MD5Util.encodeMd5(fileName));
          // 确保路径没有问题
          if (file.exists() && file.length() > 0) {
              // 返回图片
              return BitmapFactory.decodeFile(file.getAbsolutePath());
          } else {
              return null;
          }
      }
    

4.3 判断是否有缓存

  • 有缓存则读取出来显示,并且将缓存存入内存,没有就继续往下走。

      // 2 去本地硬盘中找,有就显示,没有就继续往下走
      // 将文件转换成bitmap对象
      Bitmap diskBitmap = getBitmapFromFile();
      if (diskBitmap != null) {
          // 本地磁盘有就显示图片
          imageView.setImageBitmap(diskBitmap);
    
          // 保存到内存中去
          mImageCache.put(url, new SoftReference<Bitmap>(diskBitmap));
    
          Log.d("RequestCreator:", "磁盘中有图片显示");
    
          // 不往下走了
          return;
      }
    

五、联网加载

5.1 简单线程池处理耗时的网络请求

  • 创建线程池对象

      /**
       * 构建出线程池,5条线程
       */
      private  ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
    
  • 提交任务,让RequestCreator实现Runnable接口,run方法中执行任务

      // 3 联网请求数据
      // 前面两步都没有的话就去联网加载数据
      // 将从网络上获取的数据放到线程池去执行
      mExecutorService.submit(this);
    

5.2 联网加载数据

  • 使用HttpUrlConnection连接网络

      // 子线程
      // 处理网络请求
      try {
          URL loadUrl = new URL(url);
          HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection();
    
          conn.setRequestMethod("GET");
          conn.setConnectTimeout(2000);
    
          if (conn.getResponseCode() == 200) {
              InputStream is = conn.getInputStream();
    
              // 获取到图片进行显示
              final Bitmap bm = BitmapFactory.decodeStream(is);
    
              mHandler.post(new Runnable() {
                  @Override
                  public void run() {
                      // 主线程
                      imageView.setImageBitmap(bm);
                  }
              });
    
              Log.d("RequestCreator:", "联网显示图片");
          } else {
              // 联网失败,显示失败图片
              showError();
          }
      } catch (Exception e) {
          e.printStackTrace();
          // 发生异常显示失败图片
          showError();
      }
    

5.3 保存数据到内存和文件

  • 使用缓存保存数据

      // 3.1 保存到内存
      mImageCache.put(url, new SoftReference<>(bm));
    
      // 3.2 保存到磁盘
      // 从url中获取文件名字
      String fileName = url.substring(url.lastIndexOf("/") + 1);
    
      // 获取存储路径
      File file = new File(getCacheDir(), MD5Util.encodeMd5(fileName));
      FileOutputStream os = new FileOutputStream(file);
      // 将图片转换为文件进行存储
      bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
    

六、细节处理

6.1 设置占位图

  • 界面一上来加载图片时肯定是空白的,所以需要一张占位图。

  • RequestCreator类提供一个方法,将占位图片资源ID传进来。

      /**
       * 设置默认图片,占位图片
       *
       * @param holderResId
       */
      public RequestCreator placeholder(int holderResId) {
          this.holderResId = holderResId;
    
          return this;
      }
    
  • 在into方法中,读取缓存之前,就让默认图显示。

       // 一进来先设置占位图片
       imageView.setImageResource(holderResId);
    

6.2 设置错误图片

  • 加载数据出现错误和异常都显示错误图片。

      /**
       * 显示错误图片
       */
      private void showError() {
          mHandler.post(new Runnable() {
              @Override
              public void run() {
                  imageView.setImageResource(errorResId);
              }
          });
      }
    

七、使用自己封装的小框架加载图片

  • 使用很简单,和Picasso一样,一行代码就搞定。

       // 使用自己封装的图片缓存工具类加载图片
      ImageManager.with(mContext).load(imgUrl).placeholder(R.drawable.ic_default).error(R.drawable.ic_error).into(ivImage);
    
  • 效果图

效果图

欢迎大家访问我的简书博客GitHub

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

推荐阅读更多精彩内容