本文为菜鸟窝作者蒋志碧的连载。“从 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 DiskLruCache完全解析,硬盘缓存的最佳方案
二、Acache 源码解析
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 类结构图
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() && !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 文件里
接下来看看 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
(菜鸟窝,程序员的黄埔军校。转载请注明出处)
添加菜鸟窝运营微信:yrioyou,备注【菜鸟直播】,入群交流。
微信图片_20170803172638.jpg关注菜鸟窝官网,免费领取“140套开源项目”
菜鸟窝官网公号二维码.png
网友评论