Android进阶之缓存机制与实现
1 问题描述
1.1 问题
在正常情况下进入app首页后,图片加载完成,接着退出app;然后断开网络,再进入app首页,页面显示空白。为什么?
1.1 问题
Android开发本质上就是手机和互联网中的web服务器之间进行通信,就必然需要从服务端获取数据,而反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能;以及在断网情况下Android获取到的数据就为空。Android中可通过二级缓存来减少频繁的网络操作,减少流量、提升性能,缓存页面的json数据。
2 二级缓存工作机制
所谓二级缓存实际上并不复杂,当Android端需要获得数据时比如获取网络中的图片,我们首先从内存中查找(按键查找),内存中没有的再从磁盘文件或sqlite中去查找,若磁盘中也没有才通过网络获取;当获得来自网络的数据,就以key-value对的方式先缓存到内存(一级缓存),同时缓存到文件或sqlite中(二级缓存)。注意:内存缓存会造成堆内存泄露,所有一级缓存通常要严格控制缓存的大小,一般控制在系统内存的1/4。
理解了二级缓存大家可能会有个问题网络中的数据是变化的,数据一旦放入缓存中,再取该数据就是从缓存中获得,这样岂不是不能体现数据的变化?我们在缓存数据时会设置有效时间,比如说30分钟,若超过这个时间数据就失效并释放空间,然后重新请求网络中的数据。那么30分钟内咋办?就下拉刷新啦, 实际上这不是问题。
3 LruJsonCache介绍
3.1.1 LruJsonCache是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。
3.1.2LruJsonCache可以缓存普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。
3.1.3LruJsonCache特色主要是:
1️⃣轻,轻到只有一个Java文件。
2️⃣可配置,可以配置缓存路径,缓存大小,缓存数量等。
3️⃣可以设置缓存超时时间,缓存超时自动失效,并被删除。
4️⃣支持多进程
3.1.4LruJsonCache在android中可以用在哪些场景
1️⃣替换SharePreference当做配置文件
2️⃣可以缓存网络请求数据,比如oschina的android客户端可以缓存http请求的新闻内容,缓存时间假设为1个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。
4 LruJsonCache使用
public class NewsListActivity extends Activity {
private List<News> list;
private ListView listView;
private LoadImageAdapter adapter;//适配器
private LruJsonCache lruJsonCache;//缓存框架
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.load_img_listview_activity);
//第1步:创建LruJsonCache组件
lruJsonCache = LruJsonCache.get(this);
initView();//初始化界面,代码不贴了
}
public void loadData(){
//第3步:从缓存中取数据
String cacheData = lruJsonCache.getAsString("newsList");//从缓存中取数据
if(cacheData! = null){//如果缓存中有,就不访问网络
List<News> newsList = gson.fromJson(cacheData, new TypeToken<List<News>>(){}.getType());//将json转为List
list.addAll(newsList);
adapter.notifyDataSetChanged();
return;
}
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);//模拟网络耗时
String json=request();//模拟从网络中获取json数据
//第2步:设置缓存数据,有效时间设置为1小时
lruJsonCache.put("newslist", json, 60*60*1);
List<News> newsList=gson.fromJson(json, new TypeToken<List<News>>(){}.getType());
list.addAll(newsList);
handler.sendEmptyMessage(1);
}
}).start();
}
/**
* 模拟网络请求方法
*/
private String request(){
News news=null;
for(int i=0;i<10;i++){
news=new News();
news.setId(i);
news.setImgUrl("course/img/face_"+i+".png");
news.setTitle("新闻标题"+i);
news.setSummary("测试"+i);
list.add(news);
}
Gson gson=new Gson();
return gson.toJson(list);
}
private Handler handler=new Handler()
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 1:
notify_layout.setVisibility(View.GONE);
adapter.notifyDataSetChanged();
break;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
5 主要代码解析
5.1 创建LruJsonCache组件
LruJsonCache lruJsonCache = LruJsonCache.get(context)
LruJsonCache lruJsonCache = LruJsonCache.get(context,max_size,max_count)
//max_size:设置限制缓存大小,默认为50M
//max_count:设置缓存数据的数量,默认不限制
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
5.2 设置缓存数据
//将数据同时上存入一级缓存(内存Map)和二级缓存(文件)中
lruJsonCache.put(key,data,time)
acache.put(key,data)
//Key:为存入缓存的数据设置唯一标识,取数据时就根据key来获得的
//Data:要存入的数据,acache支持的数据类型如图所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6

5.3 从缓存中取数据
提供一系列getAsXXX()方法,如图所示 
6 参考链接
Android 中的缓存机制与实现-本文版权归烟台杰瑞教育科技有限公司和博客园共有
7 LruJsonCache代码
package com.guesslive.caixiangji.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Json缓存工具
* <p>
* Created by chenliguan on 16/8/23.
*/
public class LruJsonCache {
public static final int TIME_HOUR = 60 * 60;
public static final int TIME_DAY = TIME_HOUR * 24;
private static final int MAX_SIZE = 1000 * 1000 * 50; // 50 mb
private static final int MAX_COUNT = Integer.MAX_VALUE; // 不限制存放数据的数量
private static Map<String, LruJsonCache> mInstanceMap = new HashMap<String, LruJsonCache>();
private ACacheManager mCache;
public static LruJsonCache get(Context ctx) {
return get(ctx, "LruJsonCache");
}
public static LruJsonCache get(Context ctx, String cacheName) {
File f = new File(ctx.getCacheDir(), cacheName);
return get(f, MAX_SIZE, MAX_COUNT);
}
public static LruJsonCache get(File cacheDir) {
return get(cacheDir, MAX_SIZE, MAX_COUNT);
}
public static LruJsonCache get(Context ctx, long max_zise, int max_count) {
File f = new File(ctx.getCacheDir(), "LruJsonCache");
return get(f, max_zise, max_count);
}
public static LruJsonCache get(File cacheDir, long max_zise, int max_count) {
LruJsonCache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());
if (manager == null) {
manager = new LruJsonCache(cacheDir, max_zise, max_count);
mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);
}
return manager;
}
private static String myPid() {
return "_" + android.os.Process.myPid();
}
private LruJsonCache(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);
}
// =======================================
// ============ String数据 读写 ==============
// =======================================
/**
* 保存 String数据 到 缓存中
*
* @param key 保存的key
* @param value 保存的String数据
*/
public void put(String key, String value) {
File file = mCache.newFile(key);
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(file), 1024);
out.write(value);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mCache.put(file);
}
}
/**
* 保存 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);
}
}
// =======================================
// ============= JSONObject 数据 读写 ==============
// =======================================
/**
* 保存 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;
}
}
// =======================================
// ============ JSONArray 数据 读写 =============
// =======================================
/**
* 保存 JSONArray数据 到 缓存中
*
* @param key 保存的key
* @param value 保存的JSONArray数据
*/
public void put(String key, JSONArray value) {
put(key, value.toString());
}
/**
* 保存 JSONArray数据 到 缓存中
*
* @param key 保存的key
* @param value 保存的JSONArray数据
* @param saveTime 保存的时间,单位:秒
*/
public void put(String key, JSONArray value, int saveTime) {
put(key, value.toString(), saveTime);
}
/**
* 读取JSONArray数据
*
* @param key
* @return JSONArray数据
*/
public JSONArray getAsJSONArray(String key) {
String JSONString = getAsString(key);
try {
JSONArray obj = new JSONArray(JSONString);
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// =======================================
// ============== byte 数据 读写 =============
// =======================================
/**
* 保存 byte数据 到 缓存中
*
* @param key 保存的key
* @param value 保存的数据
*/
public void put(String key, byte[] value) {
File file = mCache.newFile(key);
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
out.write(value);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mCache.put(file);
}
}
/**
* 保存 byte数据 到 缓存中
*
* @param key 保存的key
*
