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

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

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

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 类结构图

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

2.2.2、用户登录信息缓存

以存储用户登录信息为例:

Acache 的初始化 -> UserInfoCache.java

public class UserInfoCache extends IDontObfuscate{

    public static void saveCache(Context context, UserInfo info){
        ACache.get(context).put("user_id",info.getUserId());
        ACache.get(context).put("nickname",info.getNickname());
        ACache.get(context).put("head_pic",info.getHeadPic());
        ACache.get(context).put("sig_id",info.getSigId());
        ACache.get(context).put("token",info.getToken());
        ACache.get(context).put("sdk_app_id",info.getSdkAppId());
        ACache.get(context).put("adk_account_type",info.getSdkAccountType());
        ACache.get(context).put("sex",info.getSex());

        if (info.getSdkAppId() != null && TextUtils.isDigitsOnly(info.getSdkAccountType())){
            Constants.IMSDK_ACCOUNT_TYPE = Integer.parseInt(info.getSdkAccountType());
        }
    }


    public static String getUserId(Context context){
        return ACache.get(context).getAsString("user_id");
    }

    public static String getNickname(Context context){
        return ACache.get(context).getAsString("nickname");
    }

    public static String getHeadPic(Context context){
        return ACache.get(context).getAsString("head_pic");
    }

    public static String getSigId(Context context){
        return ACache.get(context).getAsString("sig_id");
    }

    public static String getToken(Context context){
        return ACache.get(context).getAsString("token");
    }

    public static String getSdkAccountType(Context context){
        return ACache.get(context).getAsString("adk_account_type");
    }

    public static String getSdkAppId(Context context){
        return ACache.get(context).getAsString("sex");
    }

    public static String getSex(Context context){
        return ACache.get(context).getAsString("sdk_app_id");
    }

    public static void clearCache(Context context){
        ACache.get(context).remove("user_id");
        ACache.get(context).remove("nickname");
        ACache.get(context).remove("head_pic");
        ACache.get(context).remove("sig_id");
        ACache.get(context).remove("token");
        ACache.get(context).remove("adk_account_type");
        ACache.get(context).remove("sdk_app_id");
        ACache.get(context).remove("sex");
    }
}

Acache 的使用 -> LoginPresenter.java

@Override
public void userNameLogin(final String userName, final String password) {

    if (checkUserNameLogin(userName, password)) {
        LoginRequest request = new LoginRequest(RequestComm.loginUsername, userName, password);
        AsyncHttp.instance().postJson(request, new AsyncHttp.IHttpListener() {
            @Override
            public void onStart(int requestId) {
                mLoginView.showLoading();
            }

            @Override
            public void onSuccess(int requestId, Response response) {
                if (response.getStatus() == RequestComm.SUCCESS) {
                    UserInfo info = (UserInfo) response.getData();
                    //将用户信息存储在cache中
                    UserInfoCache.saveCache(mLoginView.getContext(), info);
                    ACache.get(mLoginView.getContext()).put(CacheConstants.LOGIN_USERNAME, userName);
                    ACache.get(mLoginView.getContext()).put(CacheConstants.LOGIN_PASSWORD, password);

                    mLoginView.loginSuccess();
                } else {
                    mLoginView.loginFailed(response.getStatus(), response.getMsg());
                    mLoginView.dismissLoading();
                }
            }

            @Override
            public void onFailure(int requestId, int httpStatus, Throwable error) {
                mLoginView.loginFailed(httpStatus, error.getMessage());
                mLoginView.dismissLoading();
            }
        });
    }
}

Acache 的使用 -> LoginActivity.java

protected void initData() {
      //从缓存获取用户名和密码
 etLogin.setText(ACache.get(this).getAsString(CacheConstants.LOGIN_USERNAME));
 etPassword.setText(ACache.get(this).getAsString(CacheConstants.LOGIN_PASSWORD));
}

从上面的例子可以看出,缓存用户信息只需要三步:

1、在 UserInfoCache 类中通过 get 方法获取缓存实例 ACache.get(context)

2、在用户登录逻辑 LoginPresenter 中通过 put 方法往缓存中保存字符串 ACache.get(mLoginView.getContext()).put(CacheConstants.LOGIN_USERNAME, userName)

3、在 LoginActivity 通过 getAsString 方法从缓存实例里读取字符串 etLogin.setText(ACache.get(this).getAsString(CacheConstants.LOGIN_USERNAME))

2.2.3、源码解析

1、获取缓存实例 ACache.get(context) ,Acache 的构造方法是 private 类型,只能通过 Acache.get() 来获取实例

//以 ACache 为文件名的缓存实例
public static ACache get(Context ctx) {
   return get(ctx, "ACache");
}

public static ACache get(Context ctx, String cacheName) {
  //文件名为应用程序缓存目录
   File f = new File(ctx.getCacheDir(), cacheName);
   return get(f, MAX_SIZE, MAX_COUNT);
}

public static ACache get(File cacheDir) {
   return get(cacheDir, MAX_SIZE, MAX_COUNT);
}

public static ACache get(Context ctx, long max_zise, int max_count) {
   File f = new File(ctx.getCacheDir(), "ACache");
   return get(f, max_zise, max_count);
}

public static ACache get(File cacheDir, long max_zise, int max_count) {
  //返回的 key 为缓存目录 + 每次应用程序开启进程 id 的 map 的 value 值,赋给缓存实例 manager
   ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());
   if (manager == null) {//初次运行,mInstanceMap 没有键值对,需要判断 manager == null
      manager = new ACache(cacheDir, max_zise, max_count);
      mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);
   }
   return manager;
}

在调用 ACache.get() 方法过程中,执行了三个方法:

1、ACache get(Context ctx)

2、ACache get(Context ctx, String cacheName)

3、ACache get(File cacheDir, long max_zise, int max_count)

在 2 中创建了缓存目录,路径为:

/data/data/app-package-name/cache/ACache

打开 DDMS 可以查看到缓存文件。

缓存大小 MAX_SIZE 和 数量 MAX_COUNT 为 final 类型。

初次运行,mInstanceMap 没有任何键值对,所以 manager == null。故通过 ACache 构造方法构造新实例,最后将该实例引用存入 mInstanceMap。

Acache 的构造方法

//ACache构造函数privat私有(所以在其他类里获得实例只能通过 get() 方法)
private ACache(File cacheDir, long max_size, int max_count) {
    if (!cacheDir.exists() &amp;&amp; !cacheDir.mkdirs()) {   
        throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath());
    }
    mCache = new ACacheManager(cacheDir, max_size, max_count);//实例化ACacheManager内部类实例
}

创建 ACache 对象时,先通过 Map Cache 缓存查找是否已经存在该对象,有直接返回,否则创建。以上代码可知,最终我们调用的是 ACacheManager 实例,在此进行初始化(文件路径,缓存大小,缓存数量)以及数据的保存和获取。

ACacheManager 内部类实例

public class ACacheManager {
          //cacheSize 和 cacheCount 是线程安全的。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由 JVM 从等待队列中选择一个另一个线程进入。
        private final AtomicLong cacheSize;//缓存大小
        private final AtomicInteger cacheCount;//缓存数量
        private final long sizeLimit;//缓存大小最大限制
        private final int countLimit;//缓存数量最大限制
        private final Map<File, Long> lastUsageDates = Collections
                .synchronizedMap(new HashMap<File, Long>()); //最新使用日期
        protected File cacheDir;//缓存目录
        //构造函数
        private ACacheManager(File cacheDir, long sizeLimit, int countLimit) {
            this.cacheDir = cacheDir;
            this.sizeLimit = sizeLimit;
            this.countLimit = countLimit;
            cacheSize = new AtomicLong();//原子类实例 cacheSize,不用加锁保证线程安全
            cacheCount = new AtomicInteger();//原子类实例 cacheCount,不用加锁保证线程安全
            calculateCacheSizeAndCacheCount();
        }

        /**
         * 计算 cacheSize和cacheCount
         */
        private void calculateCacheSizeAndCacheCount() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int size = 0;
                    int count = 0;
                    File[] cachedFiles = cacheDir.listFiles();//返回cacheDir目录下的文件数组
                    if (cachedFiles != null) {
                        for (File cachedFile : cachedFiles) {
                            size += calculateSize(cachedFile);//将每个缓存文件长度添加到size中
                            count += 1;//缓存数量 +1
                            lastUsageDates.put(cachedFile,
                                    cachedFile.lastModified());//将最新修改日期作为value存入cacheDir 目录
                        }
                        cacheSize.set(size);//设置缓存大小
                        cacheCount.set(count);//设置缓存数量
                    }
                }
            }).start();
        }
        //将文件存入缓存
        private void put(File file) {
              //获取缓存数量
            int curCacheCount = cacheCount.get();
              //如果缓存数量大于最大限制的数量
            while (curCacheCount + 1 > countLimit) {
                  //获取缓存文件长度
                long freedSize = removeNext();
                  //将缓存文件长度置为空
                cacheSize.addAndGet(-freedSize);
                  //缓存数量置为空
                curCacheCount = cacheCount.addAndGet(-1);
            }
              //初始化缓存数量
            cacheCount.addAndGet(1);
              //计算文件长度
            long valueSize = calculateSize(file);
              //计算缓存大小
            long curCacheSize = cacheSize.get();
              //如果缓存大小 + 文件长度 > 限制的最大长度
            while (curCacheSize + valueSize > sizeLimit) {
                  //获取缓存文件大小
                long freedSize = removeNext();
                  //更新缓存大小
                curCacheSize = cacheSize.addAndGet(-freedSize);
            }
              //重置缓存大小
            cacheSize.addAndGet(valueSize);
            Long currentTime = System.currentTimeMillis();
              //更新缓存文件最新修改时间
            file.setLastModified(currentTime);
              //将文件加入缓存,以最新时间更新
            lastUsageDates.put(file, currentTime);
        }
        //根据 key 获取相应缓存文件,并更新缓存文件最后修改时间,将修改后的文件存入缓存
        private File get(String key) {
            File file = newFile(key);
            Long currentTime = System.currentTimeMillis();
            file.setLastModified(currentTime);
            lastUsageDates.put(file, currentTime);
            return file;
        }
        //创建缓存文件
        private File newFile(String key) {
            return new File(cacheDir, key.hashCode() + "");
        }
        //移除指定 key 的缓存文件
        private boolean remove(String key) {
            File image = get(key);
            return image.delete();
        }
        //清空所有缓存文件
        private void clear() {
            lastUsageDates.clear();
            cacheSize.set(0);
            File[] files = cacheDir.listFiles();
            if (files != null) {
                for (File f : files) {
                    f.delete();
                }
            }
        }

        //移除旧文件
        private long removeNext() {
            if (lastUsageDates.isEmpty()) {
                return 0;
            }
            Long oldestUsage = null;
            File mostLongUsedFile = null;
              //最新使用文件
            Set<Entry<File, Long>> entries = lastUsageDates.entrySet();
              //将旧文件更新为最新使用文件,这里采用同步机制,避免多线程下同时操作缓存空间
            synchronized (lastUsageDates) {
                for (Entry<File, Long> entry : entries) {
                    if (mostLongUsedFile == null) {
                        mostLongUsedFile = entry.getKey();
                        oldestUsage = entry.getValue();
                    } else {
                        Long lastValueUsage = entry.getValue();
                        if (lastValueUsage < oldestUsage) {
                            oldestUsage = lastValueUsage;
                            mostLongUsedFile = entry.getKey();
                        }
                    }
                }
            }
            //获取文件长度
            long fileSize = calculateSize(mostLongUsedFile);
              //移除旧文件
            if (mostLongUsedFile.delete()) {
                lastUsageDates.remove(mostLongUsedFile);
            }
              //返回最新文件缓存文件大小
            return fileSize;
        }
        //获取缓存大小
        private long calculateSize(File file) {
            return file.length();
        }
    }

由上面的代码可以知道,缓存实例化做了以下一些工作:

1、新建缓存目录文件
2、通过 Acache 构造新实例,将实例存入 mInstanceMap 中
3、实例化 ACacheManager,计算 cacheSize 和 cacheCount

存取数据就是使用思维导图中的一系列 public method(读写方法),就是那些 put() 方法和 get() 方法。

ACache.get(this).getAsString("key")
ACache.get(this).put("key", "value");

put(String key, String value) 源码

/**
 * 保存 String数据 到 缓存中
 * @param key   保存的key
 * @param value 保存的String数据
 */
public void put(String key, String value) {
   if (value == null) {
      return;
   }
   File file = mCache.newFile(key);//新建文件
   BufferedWriter out = null;//换成字符输出流,为字符流添加缓冲功能
   try {
      out = new BufferedWriter(new FileWriter(file), 1024);//获取字符输出流
      out.write(value);    //将value写入
   } catch (IOException e) {
      e.printStackTrace();
   } finally {
         //关流
      if (out != null) {
         try {
            out.flush();
            out.close();
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
      mCache.put(file);//更新 cacheCount 和 cacheSize lastUsageDates 插入新建文件和时间的键值对
   }
}

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

    /**
     * 读取 String数据
     * @param key
     * @return String 数据
     */
    public String getAsString(String key) {
        File file = mCache.get(key);
        if (!file.exists())
            return null;
        boolean removeFile = false;
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(file));
            String readString = "";
            String currentLine;
            while ((currentLine = in.readLine()) != null) {
                readString += currentLine;
            }
            if (!Utils.isDue(readString)) {
                return Utils.clearDateInfo(readString);
            } else {
                removeFile = true;
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (removeFile)
                remove(key);
        }
    }

put(String key, String value) 中调用的是 newFile() 方法新建文件,文件名 为 key 的整型哈希码,然后通过 out.write(value) 将数据存入文件。最后调用了 mCache.put(file),进行 AcacheManager 的更新。

private File newFile(String key) {
   return new File(cacheDir, key.hashCode() + "");
}

put(File file) 源码。主要有以下功能,更新 cacheCount 和 calculateSize,lastUsageDates 插入新的键值对,文件放入程序缓存后,统计缓存大小,数量,文件放到map中,缓存大小没有超过最大限制,将文件存入缓存,更新缓存数量。缓存超过限制,则减少缓存大小,缓存数量。通过 removeNext 方法找到最老的文件大小

//将文件存入缓存
private void put(File file) {
   //获取缓存数量
   int curCacheCount = cacheCount.get();
   //如果缓存数量大于最大限制的数量
   while (curCacheCount + 1 > countLimit) {
     //移除旧文件,返回文件大小
     long freedSize = removeNext();
     //更新 cacheSize
     cacheSize.addAndGet(-freedSize);
     //更新 cacheCount
     curCacheCount = cacheCount.addAndGet(-1);
   }
   //更新 cacheCount
   cacheCount.addAndGet(1);
   //计算文件长度
   long valueSize = calculateSize(file);
   //计算缓存大小
   long curCacheSize = cacheSize.get();
   //如果缓存大小 + 文件长度 > 限制的最大长度
   while (curCacheSize + valueSize > sizeLimit) {
      //获取最老的缓存文件大小
      long freedSize = removeNext();
      //更新缓存大小
      curCacheSize = cacheSize.addAndGet(-freedSize);
   }
   //重置缓存大小
   cacheSize.addAndGet(valueSize);
   Long currentTime = System.currentTimeMillis();
   //更新缓存文件最新修改时间
   file.setLastModified(currentTime);
   //将文件加入缓存,以最新时间更新
   lastUsageDates.put(file, currentTime);
}

接下来,我们运行程序,看看 put() 方法如何执行,并查看缓存文件。

这里以存入 user_id 为例。可以很清楚查看到首先创建一个缓存文件文件夹为 ACache,然后创建对应的缓存文件,缓存文件的路径是 /data/data/com.dali.admin.livastreaming/cache/Acache 这个包名下,接着将缓存文件存放在 /data/data/com.dali.admin.livastreaming/cache/Acache/-147132913 文件里
《【从 0 开始开发一款直播 APP】6 缓存 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.
《【从 0 开始开发一款直播 APP】6 缓存 ACache 源码解析》
/data/data/com.dali.admin.livastreaming/cache/Acache 目录下查看缓存文件,这里用的是 shell 命令查看,也可以通过 DDMS 查看,更加直观。笔者为了偷懒就这样看看好啦,不懂命令对要学哦。
《【从 0 开始开发一款直播 APP】6 缓存 ACache 源码解析》
接下来看看获取缓存文件等执行步骤吧。调用的方法是 getAsString(String key),首先获取缓存目录,读取缓存文件,可以看到最后调用的方法是 Utils.clearDateInfo(readString) 获取 value,即用户名。接下来讲解 Utils 类。
《【从 0 开始开发一款直播 APP】6 缓存 ACache 源码解析》

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 类了解了吧,下面看看它的运行流程。
《【从 0 开始开发一款直播 APP】6 缓存 ACache 源码解析》
参考文章:

blog.csdn.net/u011494050/…

my.oschina.net/ryanhoo/blo…

www.kancloud.cn/digest/fast…

www.androidchina.net/2640.html

更多内容,请关注菜鸟窝(微信公众号ID: cniao5),程序猿的在线学习平台。 如需转载,请注明出处(菜鸟窝 , 原文链接:
http://www.cniao5.com/forum/thread/1e7d0d1a228711e7887b00163e0230fa

关注公众号免费领取” N套客户端实战项目教程”

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

    原文作者:Android
    原文地址: https://juejin.im/entry/5902be9044d9040069135dbe
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞