为什么数据缓存?
在使用网络的情况下,我们都是在获取服务器数据,那么当无网络后,我们在此打开页面都会缓存第一次的首要数据。
思路:
创建重写MvpRepository
使用双检索单列模式
将数据存储到对象中,通过HashMap()进行内存缓存 key :对应的栏目id ,value: 该栏目的数据
* OkHttp 网络请求:
* map:负责把json 串转成对象 把对象包装到response上
* doOnNext :插入一个做耗时操作 :
* 可以:
* 做数据缓存 在子线程 拿到MvpResponse对象 有一个Recommend对象
* IO 线程 把线程切入到工作线程
* 主线程 :负责把线程切入到主线程
* compose :制定生命周期执行时接触订阅关系
* observer: 观察者
推荐页的请求方式:
* 1.第一次 Fragment 创建时(启动)请求
* a.冷启动App 时:
* i: 内存里面的缓存肯定没有,尝试加载Sdcard,如果scard没有就请求服务器(数据回来后,存储内存和sdcard缓存),如果有,在子线程里面去读取sdcard缓存的数据,(可以放入内存中)然后主线程里面回调给P层
* b.ViewPager切换时导致Fragment 销毁重建时
* i:从内存里面读取,把上一次该栏目请求的所有的数据,回调给P层,比如你在推荐栏目加载(loading)了5页数据,然后你切换到请求栏目,这个时候推荐Fragment 是不是已经销毁了,再次回到推荐是,应该恢复到上次销毁之前的状态(5页数据不变)。为了实现这个效果。必须对每个栏目的数据做缓存
* 如果缓存没有:从服务器加载,加载回来写入缓存:
* 内存缓存: 清空缓存(本来就没有)
* 2.刷新:下拉时请求
* a.肯定不能读任何缓存,刷新成功后,把对应栏目的缓存清空,用新的数据替换,不管是内存还是sdcard
* i: 内存缓存:清空缓存,放入新的
* ii: 替换原来的缓存
* 3.加载更多: 上拉时请求
* a.也不能读缓存,加载成功后,往对应的栏目的缓存里面的newsList 里面追加数据(还需要把服务器返回的start,nmber,pointTitem 赋值给缓存对应的对象 为了页面切换是,从缓存里面读取数据时加载更多时能衔接上),同时还有替换sdcard里面的缓存。
* 缓存规则:
* 内存替换
* sdcard:替换
准备工作:
* 1.定义一个内存缓存的数据结构 hashMap<String ,RecommendData>
* 2.写一个工具类:
* a.用于缓存对象(把对象转出 json, 加密写入文件)
* b.用于读取缓存对象(把加密后的解密成 json,并把json 转出我们需要的对象)
创建RecommondRepository 继承BaseRepository 用来进行网络缓存
public class RecommondRepository extends BaseRepository {
private static final String CACHE_FILE_NAME_PREFIX = "version_code";
private static RecommondRepository mInstance;
private HashMap<String, RecommendData> mMemoryData = new HashMap<>();
/**
*
* @return
* 单例是的只有一个model对象
*/
public static RecommondRepository getInstance() {
// 判断对象是否创建
if (mInstance == null) {
// 在此处创建线程索让其他线程减少等待时间
synchronized (RecommondRepository.class) {
// 如果线程1拿到锁对象未创建完成,线程2拿到锁创建完对象线程1继续创建此时对象已经通过线程2实例化所以进行二次判空
if (mInstance == null) {
// 创建对象
mInstance = new RecommondRepository();
}
}
}
return mInstance;
}
@Override
public <T> void doRequest(LifecycleProvider lifecycleProvider, MvpRequest<T> request, IBaseCallBack<T> callBack) {
/*取数据*/
// 不调用父类的方法,重写子类
// super.doRequest(lifecycleProvider, request, callBack);
// 得到栏目ID
String columnId = request.getParams().get(Constract.RequestKey.KEY_COLUMN_ID).toString();
switch (request.getRequestType()) {
// case加入括号{}可以重复创建变量局部变量只能在方法里有效
/**
* 默认都是第一次请求
* 刷新和加载更多都不需要读取缓存数据
*/
case FIRST: {
// step1.先从内从存查找直接传入P层
MvpResponse response = getFromMemory(columnId);
if (response != null) {
callBack.onResult(response);
return;
}
// step2.如果内存没有直接从sdcard读取
/*
*Observable 内置线程池节省系统开销
* scared都为耗时操作为 */
Observable.create(new ObservableOnSubscribe<MvpResponse>() {
@Override
public void subscribe(@NonNull ObservableEmitter<MvpResponse> emitter) throws Throwable {
MvpResponse fromSdCard = getFromSdCard(columnId);
if (fromSdCard != null) {
emitter.onNext(fromSdCard);
emitter.onComplete();
} else {
emitter.onError(new NullPointerException("sdcard no data"));
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<MvpResponse>() {
@Override
public void accept(MvpResponse mvpResponse) throws Throwable {
callBack.onResult(mvpResponse);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Throwable {
doRequest(lifecycleProvider, request, callBack, new CacheTask<>(request));
}
});
break;
}
default:{
// 从服务请求
doRequest(lifecycleProvider,request,callBack,new CacheTask<>(request));
}
}
}
// 读取内存数据
private MvpResponse getFromMemory(String key) {
// 看内存是否有该数据
RecommendData data = mMemoryData.get(key);
if (data != null) {
// 设置数据
MvpResponse<RecommendData> response = new MvpResponse<>();
return response.setData(data).setCode(1).setRequestType(RequestType.FIRST).setType(ResponseType.MEMORY);
}
return null;
}
/**
* @param key
* @return 从sd卡读取文件
*/
private MvpResponse getFromSdCard(String key) {
// 从内存卡读取数据
RecommendData data = MvpDataFileCacheUtils.getencryptedDataFromFile(getCacheDataSdcardFile(key), RecommendData.class);
if (data != null) {
MvpResponse<RecommendData> response = new MvpResponse<>();
return response.setCode(1).setData(data).setType(ResponseType.SDCARD);
}
return null;
}
/**
* @param columnId
* @return 创建手机文件
*/
// 找到文件
private File getCacheDataSdcardFile(String columnId) {
return new File(MvpManager.getExternalCacheDir(), CACHE_FILE_NAME_PREFIX + columnId);
}
// 缓存数据
private class CacheTask<T> implements Consumer<MvpResponse<T>> {//实现做缓存接口
private MvpRequest request;
CacheTask(MvpRequest request) {
this.request = request;
}
@SuppressWarnings("ALL")
@Override
public void accept(MvpResponse<T> mvpResponse) throws Throwable {
// 得到保存的Key值
String key = request.getParams().get(Constract.RequestKey.KEY_COLUMN_ID).toString();
saveToMemory((MvpResponse<RecommendData>) mvpResponse, request.getRequestType(), key);
saveToSdcard((MvpResponse<RecommendData>) mvpResponse, key);
}
// 保存到card
private void saveToSdcard(MvpResponse<RecommendData> mvpResponse, String key) {
}
// 保存到内存
private void saveToMemory(MvpResponse<RecommendData> mvpResponse, RequestType requestType, String key) {
if (requestType != RequestType.LOAD_MORE){//不是加载更多
mMemoryData.clear();//将内存设置空
RecommendData recommendData = new RecommendData();//创建空对象开辟内存
RecommendData serverData = mvpResponse.getData();//从网络获取data
// 设置数据
recommendData.setAlbumId(serverData.getAlbumId());
recommendData.setAlbumNews(serverData.getAlbumNews());
recommendData.setAlbumTitle(serverData.getAlbumTitle());
recommendData.setBannerList(serverData.getBannerList());
recommendData.setFlashId(serverData.getFlashId());
recommendData.setFlashNews(serverData.getFlashNews());
recommendData.setMore(serverData.getMore());
recommendData.setPointTime(serverData.getPointTime());
recommendData.setStart(serverData.getStart());
recommendData.setNews(new ArrayList<>(serverData.getNews()));
// 缓存到集合
mMemoryData.put(key,recommendData);
}else {
RecommendData cacheData = mMemoryData.get(key);//获取缓存加入数据
RecommendData serverData = mvpResponse.getData();//获取网络数据
// 如果网络数据和缓存数据都不为空
if(cacheData != null && serverData != null){
// 所有数据只有新闻数据需要实施gen新
cacheData.setStart(serverData.getStart());
cacheData.setNumber(serverData.getNumber());
cacheData.setPointTime(serverData.getPointTime());
cacheData.setMore(serverData.getMore());
cacheData.getNews().addAll(serverData.getNews());
}
}
}
}
对sd卡的数据进行加密:
a.
public class MvpDataFileCacheUtils {
private static final String SECRET_KEY = "tnKqXmiVZFYXIit9uYmnrg==";
//
/**
* 把 json 转出指定的对象并返回
*
* @param tClass 需要转的对象的class
* @param jsonStr json 串
*/
public static <T> T convertToDataFromJson(Class<T> tClass, String jsonStr) {
if (TextUtils.isEmpty(jsonStr)) {
return null;
}
Gson gson = new Gson();
return gson.fromJson(jsonStr, tClass);
}
/**
* 把 json 转出指定的对象List<T> 这种类型并返回,比如 List<Person>
*
* @param tClass :List里面泛型的class,比如 Person.class
* @param jsonStr : json 串
*/
public static <T> List<T> convertToListFromJson(Class<T> tClass, String jsonStr) {
if (TextUtils.isEmpty(jsonStr)) {
return null;
}
Gson gson = new Gson();
ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(List.class, new Type[]{tClass});
return gson.fromJson(jsonStr, parameterizedType);
}
/**
* Map<String,Map<String,List<String>>>
* <p>
* ParameterizedTypeImpl listType = new ParameterizedTypeImpl(List.class,new Type[]{String.class}); // List<String>
* <p>
* ParameterizedTypeImpl mapInnerType = new ParameterizedTypeImpl(Map.class,new Type[]{String.class,listType}); // Map<String,List<String>
* <p>
* ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(Map.class,new Type[]{String.class,mapInnerType}); Map<String,Map<String,List<String>>
*/
public static <K, V> Map<K, V> convertToMapFromJson(Class<K> keyClass, Class<V> vclass, String jsonStr) {
if (TextUtils.isEmpty(jsonStr)) {
return null;
}
Gson gson = new Gson();
ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(Map.class, new Type[]{keyClass, vclass});
return gson.fromJson(jsonStr, parameterizedType);
}
// 把一个对象转出json
public static String convertToJsonFromData(Object data) {
if (data == null) return null;
Gson gson = new Gson();
return gson.toJson(data);
}
/**
* 把一个对象保存到文件里面,
*
* @param file 保存文件
* @param data 需要保存的对象
*/
public static void saveDataToFile(File file, Object data) {
String json = convertToJsonFromData(data);
if (TextUtils.isEmpty(json)) {
return;
}
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
outputStream.write(json.getBytes("utf-8"));
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void saveEncryptedDataToFile(File file, Object data) {
String json = convertToJsonFromData(data);
// 加密
if (TextUtils.isEmpty(json)) {
return;
}
FileOutputStream outputStream = null;
try {
json = EncryptUtils.encrypt(SECRET_KEY,json); // 加密
outputStream = new FileOutputStream(file);
outputStream.write(json.getBytes("utf-8"));
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 从文件里面读取出对象
*
* @param file
* @param tClass
* @param <T>
* @return
*/
public static <T> T getencryptedDataFromFile(File file, Class<T> tClass) {
String json= readFromFile(file);
try {
json = EncryptUtils.decrypt(SECRET_KEY,json);// 解密
} catch (Exception e) {
e.printStackTrace();
return null;
}
return convertToDataFromJson(tClass, json);
}
public static <T> T getDataFromFile(File file, Class<T> tClass) {
return convertToDataFromJson(tClass, readFromFile(file));
}
/**
* 从文件里面读取出对象,这个对象是一个List<T>
*
* @param file
* @param tClass
* @param <T>
* @return
*/
public static <T> List<T> getListFromFile(File file, Class<T> tClass) {
return convertToListFromJson(tClass, readFromFile(file));
}
private static String readFromFile(File file) {
if (file == null || !file.exists()) {
return null;
}
BufferedReader reader = null;
try {
FileInputStream fileInputStream = new FileInputStream(file);
reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"));
int c;
StringBuilder buffer = new StringBuilder();
while ((c = reader.read()) != -1) {
buffer.append((char) c);
}
return buffer.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public static class ParameterizedTypeImpl implements ParameterizedType {
// List<Column>
private final Class raw; // List.class
private final Type[] args; // {Column.class}
public ParameterizedTypeImpl(Class raw, Type[] args) {
this.raw = raw;
this.args = args;
}
@NonNull
@Override
public Type[] getActualTypeArguments() {
return args;// { Column.class}
}
@NonNull
@Override
public Type getRawType() {
return raw; // List.class
}
@Nullable
@Override
public Type getOwnerType() {
return null;
}
}
}
b
/**
* @desc: AES对称加密,对明文进行加密、解密处理
* @author: yong.li
* @createTime: 2018年8月28日 上午9:54:52
* @version: v0.0.1
*/
public class EncryptUtils {
private static final String KEY_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/**
* @desc: AES对称-加密操作
* @author: yong.li
* @createTime: 2018年8月28日 上午10:25:50
* @version: v0.0.1
* @param keyStr 进行了Base64编码的秘钥
* @param data 需要进行加密的原文
* @return String 数据密文,加密后的数据,进行了Base64的编码
*/
public static String encrypt(String keyStr, String data) throws Exception {
// 转换密钥
Key key = new SecretKeySpec(Base64.decode(keyStr,Base64.DEFAULT), KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 加密
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(data.getBytes());
return Base64.encodeToString(result,Base64.DEFAULT);
}
/**
* @desc: AES对称-解密操作
* @author: yong.li
* @createTime: 2018年8月28日 上午10:22:47
* @version: v0.0.1
* @param keyStr 进行了Base64编码的秘钥
* @param data 需要解密的数据<span style="color:red;">(数据必须是通过AES进行加密后,对加密数据Base64编码的数据)</span>
* @return String 返回解密后的原文
*/
public static String decrypt(String keyStr, String data) throws Exception {
// 转换密钥
Key key = new SecretKeySpec(Base64.decode(keyStr,Base64.DEFAULT), KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 解密
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] result = cipher.doFinal(Base64.decode(data,Base64.DEFAULT));
return new String(result);
}
/**
* @desc: 生成AES的秘钥,秘钥进行了Base64编码的字符串
* @author: yong.li
* @createTime: 2018年8月28日 上午10:24:32
* @version: v0.0.1
* @return String 对生成的秘钥进行了Base64编码的字符串
*/
public static String keyGenerate(String key) throws Exception {
// 生成密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
keyGenerator.init(128,new SecureRandom(key.getBytes()));
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyBytes = secretKey.getEncoded();
return Base64.encodeToString(keyBytes,Base64.DEFAULT);
}
}
网友评论