从0到1开发一款APPAndroid技术知识Android开发

【从 0 开始开发一款直播 APP】6 缓存 ACache 源码

2017-04-27  本文已影响167人  菜鸟窝

本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目
视频地址:http://www.cniao5.com/course/10121


一、缓存机制你需要知道的事

Android 中的缓存

Android 缓存分为两种:内存缓存和硬盘缓存。

内存缓存

常用的内存缓存方式是软引用(SofeReference)和 弱引用(WeakReference),大部分的使用方式:HashMap<String url, SoftReference<Drawable>> imageCache。这种形式从 Android 2.3(Level 9)开始,垃圾回收器更倾向于回收 SoftReference 或 WeakReference 对象,这使得 SoftReference 和 WeakReference 变得不是那么实用有效。同时,到了 Android 3.0(Level 11)之后,图片数据 Bitmap 被放置到了内存的堆区域,而堆区域的内存是由 GC 管理的,开发者也就不需要进行图片资源的释放工作,但这也使得图片数据的释放无法预知,增加了造成 OOM 的可能。因此,在 Android 3.1 以后,Android 推出了 LruCache 这个内存缓存类,LruCache 中的对象是强引用。

内存缓存的存取速度非常惊人,远远快于文件读取,如果没有内存限制,首选就是这种方式,但是内存缓存有着 16M 的限制,所以当程序内存告急时,它会主动清理部分弱引用(因此,当引用指向 null,我们必须转向硬盘缓存读取数据,如果硬盘也没有,那就下载吧)。

缓存主要包含缓存的添加、获取和删除。添加和获取很好理解,为什么要删除呢?因为不管是内存缓存还是硬盘缓存,他们的缓存大小都是有限的,当缓存满了之后,再想添加缓存,就需要删除一些旧的缓存并添加新的缓存。

硬盘缓存

硬盘缓存是Google 提供的一套硬盘缓存解决方案:DiskLruCache ( 非 Google 官方编写,但获得官方认证)。DiskLruCache 没有限制数据缓存的位置,可以自由的进行设置,通常情况下多数应用程序都会将缓存位置选择为:/sdcard/Android/data/<application package>/cache 这个路径。

这个位置有两个好处:

1、这是存储在 SDCard 上的,即使缓存再多的数据也不会对手机内置存储空间有任何影响,只要 SDCard 空间足够。

2、这个路径被 Android 系统认定为应用程序的缓存路径,当应用程序被卸载时,这里的数据会被一起清除掉,这样就不会出现删除程序之后手机还残留数据的问题。

getCacheDir()方法用于获取/data/data/<application package>/cache 目录

getFilesDir() 方法用于获取/data/data/<application package>/files 目录

通过 Context.getExternalFilesDir() 方法可以获取到 SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
通过 Context.getExternalCacheDir() 方法可以获取到 SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据

如果使用上面的方法,当你的应用在被用户卸载后,SDCard/Android/data/<application package>/ 这个目录下的所有文件都会被删除,不会留下垃圾信息。

而且上面两个目录分别对应 设置 -> 应用 -> 应用详情里面的 ”清除数据“ 与 ”清除缓存“ 选项。

本篇文章不讲 LruCache 和 DiskLruCache。有兴趣的看看这两篇解析:

彻底解析Android缓存机制——LruCache

Android DiskLruCache完全解析,硬盘缓存的最佳方案

二、Acache 源码解析

ASimpleCache 源码

2.1、官方介绍

ASimpleCache 是一个为 android 制定的轻量级的开源缓存框架。轻量到只有一个 java 文件(由十几个类精简而来)。

1、它可以缓存什么东西?

普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的 java 对象,和 byte 数据。

2、它有什么特色?

特色主要是:

1:轻,轻到只有一个 JAVA 文件。

2:可配置,可以配置缓存路径,缓存大小,缓存数量等。

3:可以设置缓存超时时间,缓存超时自动失效,并被删除。

4:支持多进程。

3、它在android中可以用在哪些场景?

1、替换 SharePreference 当做配置文件

2、可以缓存网络请求数据,比如 oschina 的 android 客户端可以缓存 http 请求的新闻内容,缓存时间假设为 1 个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。

3、您来说...

4、如何使用 ASimpleCache? 以下有个小的 demo,希望您能喜欢:

ACache mCache = ACache.get(this);
mCache.put("test_key1", "test value");
mCache.put("test_key2", "test value", 10);//保存10秒,如果超过10秒去获取这个key,将为null
mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);//保存两天,如果超过两天去获取这个key,将为null

获取数据

ACache mCache = ACache.get(this);
String value = mCache.getAsString("test_key1");

2.2、源码解析

2.2.1、Acache 类结构图


接下来看看 put(File file) 方法的执行过程,首先获取缓存数量,判断缓存数量是否超过最大缓存数量,显然这里没有超过,看到刚开始的缓存数量是7,增加了一个之后变成了8,所以 addAndGet(1) 方法将数量增加了 1,并将缓存数量重新保存更新为 8,接下来获取缓存大小,判断是否超过最大限制,这里也没用超过,接下来更新缓存大小,缓存大小增加的是要添加的文件的长度,为 2,更新之后 又 417 变为了 419,获取当前更新的系统时间,将文件修改时间改为最新,最后调用 lastUsageDates.put(file, currentTime),lastUsageDates 是 Map<File, Long> lastUsageDates = Collections.synchronizedMap(new HashMap<File, Long>()) 这个类型,可知其 put() 方法就是存储的 key - value 键值对,以 fie 为 key,当前修改时间为 value。并且当前长度为 7.

/data/data/com.dali.admin.livastreaming/cache/Acache 目录下查看缓存文件,这里用的是 shell 命令查看,也可以通过 DDMS 查看,更加直观。笔者为了偷懒就这样看看好啦,不懂命令对要学哦。

接下来看看获取缓存文件等执行步骤吧。调用的方法是 getAsString(String key),首先获取缓存目录,读取缓存文件,可以看到最后调用的方法是 Utils.clearDateInfo(readString) 获取 value,即用户名。接下来讲解 Utils 类。

clearDateInfo(String strInfo) 源码

private static String clearDateInfo(String strInfo) {
   if (strInfo != null && hasDateInfo(strInfo.getBytes())) {
      strInfo = strInfo.substring(strInfo.indexOf(mSeparator) + 1,
            strInfo.length());
   }
   return strInfo;
}

private static boolean hasDateInfo(byte[] data) {
    return data != null && data.length > 15 && data[13] == '-'
                    && indexOf(data, mSeparator) > 14;
}

内部类 Utils

/**
 * @author 杨福海(michael) www.yangfuhai.com
 * @version 1.0
 * @title 时间计算工具类
 */
private static class Utils {
   /**
    * 判断缓存的String数据是否到期
    * @param str
    * @return true:到期了 false:还没有到期
    */
   private static boolean isDue(String str) {
      return isDue(str.getBytes());
   }
   /**
    * 判断缓存的byte数据是否到期
    * @param data
    * @return true:到期了 false:还没有到期
    */
   private static boolean isDue(byte[] data) {
      String[] strs = getDateInfoFromDate(data);
      if (strs != null && strs.length == 2) {
         String saveTimeStr = strs[0];
         while (saveTimeStr.startsWith("0")) {
            saveTimeStr = saveTimeStr
                  .substring(1, saveTimeStr.length());
         }
         long saveTime = Long.valueOf(saveTimeStr);
         long deleteAfter = Long.valueOf(strs[1]);
         if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) {
            return true;
         }
      }
      return false;
   }
    //构造新的字符串
   private static String newStringWithDateInfo(int second, String strInfo) {
      return createDateInfo(second) + strInfo;
   }
    //构造字节数组,将新数组拷贝添加在后面
   private static byte[] newByteArrayWithDateInfo(int second, byte[] data2) {
      byte[] data1 = createDateInfo(second).getBytes();
      byte[] retdata = new byte[data1.length + data2.length];
      System.arraycopy(data1, 0, retdata, 0, data1.length);
      System.arraycopy(data2, 0, retdata, data1.length, data2.length);
      return retdata;
   }
    //根据 strInfo 获取新的 value
   private static String clearDateInfo(String strInfo) {
      if (strInfo != null && hasDateInfo(strInfo.getBytes())) {
         strInfo = strInfo.substring(strInfo.indexOf(mSeparator) + 1,
               strInfo.length());
      }
      return strInfo;
   }
    //用 mSeparator 分割字节数组
   private static byte[] clearDateInfo(byte[] data) {
      if (hasDateInfo(data)) {
         return copyOfRange(data, indexOf(data, mSeparator) + 1,
               data.length);
      }
      return data;
   }
    //判断 - 后是否还有字符串
   private static boolean hasDateInfo(byte[] data) {
      return data != null && data.length > 15 && data[13] == '-'
            && indexOf(data, mSeparator) > 14;
   }
    //构造新字符数组,存储 0-13 以及 14 后面的字符串,13 是分割符 -
   private static String[] getDateInfoFromDate(byte[] data) {
      if (hasDateInfo(data)) {
         String saveDate = new String(copyOfRange(data, 0, 13));
         String deleteAfter = new String(copyOfRange(data, 14,
               indexOf(data, mSeparator)));
         return new String[]{saveDate, deleteAfter};
      }
      return null;
   }
    //根据指定字符查找字符串
   private static int indexOf(byte[] data, char c) {
      for (int i = 0; i < data.length; i++) {
         if (data[i] == c) {
            return i;
         }
      }
      return -1;
   }
    //指定范围拷贝字符数组
   private static byte[] copyOfRange(byte[] original, int from, int to) {
      int newLength = to - from;
      if (newLength < 0)
         throw new IllegalArgumentException(from + " > " + to);
      byte[] copy = new byte[newLength];
      System.arraycopy(original, from, copy, 0,
            Math.min(original.length - from, newLength));
      return copy;
   }

   private static final char mSeparator = ' ';
    //创建字符串 小于 13,currentTime 前面加 0 -> "0" + currentTime,大于 13,加 - 分割符,currentTime + "-" + second + mSeparator
   private static String createDateInfo(int second) {
      String currentTime = System.currentTimeMillis() + "";
      while (currentTime.length() < 13) {
         currentTime = "0" + currentTime;
      }
      return currentTime + "-" + second + mSeparator;
   }

   /*
    * Bitmap → byte[]
    */
   private static byte[] Bitmap2Bytes(Bitmap bm) {
      if (bm == null) {
         return null;
      }
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
      return baos.toByteArray();
   }

   /*
    * byte[] → Bitmap
    */
   private static Bitmap Bytes2Bimap(byte[] b) {
      if (b.length == 0) {
         return null;
      }
      return BitmapFactory.decodeByteArray(b, 0, b.length);
   }

   /*
    * Drawable → Bitmap
    */
   private static Bitmap drawable2Bitmap(Drawable drawable) {
      if (drawable == null) {
         return null;
      }
      // 取 drawable 的长宽
      int w = drawable.getIntrinsicWidth();
      int h = drawable.getIntrinsicHeight();
      // 取 drawable 的颜色格式
      Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
            : Bitmap.Config.RGB_565;
      // 建立对应 bitmap
      Bitmap bitmap = Bitmap.createBitmap(w, h, config);
      // 建立对应 bitmap 的画布
      Canvas canvas = new Canvas(bitmap);
      drawable.setBounds(0, 0, w, h);
      // 把 drawable 内容画到画布中
      drawable.draw(canvas);
      return bitmap;
   }

   /*
    * Bitmap → Drawable
    */
   @SuppressWarnings("deprecation")
   private static Drawable bitmap2Drawable(Bitmap bm) {
      if (bm == null) {
         return null;
      }
      return new BitmapDrawable(bm);
   }
}

其他缓存类型解析。JsonObject、JsonArray、Bitmap、Drawable、序列化的存入缓存都是转化为字符串/byte格式,再调用函数 put(String key, T value) 即可。

/**
 * 保存 JSONObject数据 到 缓存中
 * @param key   保存的key
 * @param value 保存的JSON数据
 */
public void put(String key, JSONObject value) {
   put(key, value.toString());
}

/**
 * 保存 JSONObject数据 到 缓存中
 * @param key      保存的key
 * @param value    保存的JSONObject数据
 * @param saveTime 保存的时间,单位:秒
 */
public void put(String key, JSONObject value, int saveTime) {
   put(key, value.toString(), saveTime);
}

/**
 * 读取JSONObject数据
 * @param key
 * @return JSONObject数据
 */
public JSONObject getAsJSONObject(String key) {
   String JSONString = getAsString(key);
   try {
      JSONObject obj = new JSONObject(JSONString);
      return obj;
   } catch (Exception e) {
      e.printStackTrace();
      return null;
   }
}

其他的将参数改下就一样啦,具体的请查看源码吧。
看了如此多的源码,你应该对 ACache 类了解了吧,下面看看它的运行流程。



参考文章:

http://blog.csdn.net/u011494050/article/details/39671159

https://my.oschina.net/ryanhoo/blog/93406

https://www.kancloud.cn/digest/fastdev4android/109644

http://www.androidchina.net/2640.html

【五一大促】菜鸟窝全场android项目实战课程低至五折,更有价值33元的四款热门技术免费领,17年初优惠力度最大的一次活动,有意向的童鞋不要错过
狂戳>>http://www.cniao5.com/hd/2017/51.html

上一篇 下一篇

猜你喜欢

热点阅读