思路来源与图片的三级缓存,即:内存->磁盘->网络
对于部分数据(如:静态数据或者配置信息)我们可能同样需要进行缓存来提升效率,
具体实现思路如下:
1、使用多级缓存完成资源的复用 如:内存 -> 磁盘 ->…
2、使用责任链设计模式,可以通过自定义添加链节点完成多级缓存
3、使用LruCache完成内存部分存储
4、使用DiskLruCache完成磁盘部分存储
缓存策略主要包含缓存的添加、获取和删除三类操作。删除缓存是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想添加缓存就需要删除一些旧的缓存。
Android的三级缓存主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LRU算法。
LRU(Least Recently Used)缓存算法是近期最少使用算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
LruCache是Android 3.1提供的一个缓存类,在Android中可以直接使用LruCache实现内存缓存。而硬盘缓存DisLruCache目前还不是Android SDK的一部分,但Android官方文档推荐使用该算法来实现硬盘缓存。
LruCache的核心思想就是维护一个缓存对象列表,这个队列就是由LinkedHashMap维护的。列表的排列方式按照访问顺序实现,即一直没访问的对象,将放在队尾,即将被淘汰,而最近访问的对象将放在队头,最后被淘汰。
LinkedHashMap由数组+双向链表实现。其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的
LinkedHashMap通过构造函数来指定双向链表的结构是访问顺序还是插入顺序。
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
accessOrder为true是访问顺序,为false是插入顺序。
比如,当设置accessOrder为true时:
public static final void main(String[] args) {
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
map.put(0, 0);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
map.put(5, 5);
map.put(6, 6);
map.get(1);
map.get(2);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
输出结果:
0:0
3:3
4:4
5:5
6:6
1:1
2:2
即最近访问的最后输出,这正好满足LRU缓存算法的思想。LruCache的实现就是利用了LinkedHashMap的这种数据结构
磁盘读写也是用的LRU算法。但是这个和内存的LRU算法有一点小区别。为什么呢?因为内存缓存是我们运行的时候,程序加载内存里面的资源,可以直接通过一个LinkedHashMap去实现。但是磁盘不同,我总不可能吧所有磁盘的资源读出来然后加载在内存里面吧,这样的话,肯定会引发oom了。那么Glide是怎么做磁盘的LRU的呢?
Glide 是使用一个日志清单文件来保存这种顺序,DiskLruCache 在 APP 第一次安装时会在缓存文件夹下创建一个 journal 日志文件来记录图片的添加、删除、读取等等操作,后面每次打开 APP 都会读取这个文件,把其中记录下来的缓存文件名读取到 LinkedHashMap 中,后面每次对图片的操作不仅是操作这个 LinkedHashMap 还要记录在 journal 文件中. journal 文件内容如下图:
data/data/应用包名/cache/。。。。。
日志文件:
**********kotlin单例模式(掌握)
java版本
public class LRUCacheUtils<V> {
int maxSize= (int) (Runtime.getRuntime().totalMemory()/8);
private LRUCacheUtils(){
lruCache=new LruCache<String, V>(maxSize);
}
private static volatile LRUCacheUtils instance=null;
public static LRUCacheUtils getInstance(){
if (null==instance){
synchronized (LRUCacheUtils.class){
if (null==instance){
instance=new LRUCacheUtils();
}
}
}
return instance;
}
LruCache<String,V> lruCache=null;
/**
* 按Key存储值
* @param key
* @param value
*/
public void putValue(String key,V value){
lruCache.put(key,value);
}
/**
* 按Key获取值
* @param key
* @return
*/
public V getValue(String key){
return lruCache.get(key);
}
/**
* 按Key删除指定值
* @param key
*/
public void removeValue(String key){
lruCache.remove(key);
}
/**
* 清空
*/
public void clear(){
lruCache.evictAll();
}
}
kotlin版本
/**
* @Author : yaotianxue
* @Time : On 2023/5/23 07:59
* @Description : LRUCacheUtils
*/
class LRUCacheUtils<V> {
var maxSize = (Runtime.getRuntime().totalMemory() / 8).toInt()//内存的1/8
var lruCache:LruCache<String,V> = LruCache<String,V>(maxSize)//lruCache
//双重锁单例模式
companion object{
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
LRUCacheUtils<Any>()
}
}
/**
* 按Key存储值
*/
fun putValue(key:String,value:V){
lruCache.put(key,value)
}
/**
* 按Key获取值
*/
fun getValue(key:String): V? {
return lruCache.get(key)
}
/**
* 按key删除
*/
fun removeValue(key:String){
lruCache.remove(key)
}
/**
* 清空
*/
fun clear(){
lruCache.evictAll()
}
}
java版本
public final class DiskLRUCacheUtils<V> {
private DiskLruCache diskLruCache;
private static DiskLRUCacheUtils instance=new DiskLRUCacheUtils();
/**
* 容量上限200M
*/
private static final int MAX_SIZE=200*1024*1024;
private DiskLRUCacheUtils(){
/**
* 如下 初始化DiskLruCache
*/
String diskCachePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"bawei6diskcache";
File file=new File(diskCachePath);
if (!file.exists()){
file.mkdirs();
}
try {
diskLruCache = DiskLruCache.open(file, 1, 1, MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
public static DiskLRUCacheUtils getInstance(){
return instance;
}
public void putValue(String key, V data) {
String mKey= MD5.encrypt(key);
OutputStream outputStream = null;
DiskLruCache.Editor edit=null;
try {
edit = diskLruCache.edit(mKey);
if (edit!=null){
//对象转byte数组
byte[] bytes= ObjUtils.obj2ByteArray(data);
outputStream = edit.newOutputStream(0);
outputStream.write(bytes);
edit.commit();
diskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
if (edit!=null){
try {
edit.abort();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}finally {
if (outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public V getValue(String key) {
InputStream is=null;
try {
List<Byte> data = new ArrayList<>();
String mKey = MD5.encrypt(key);
DiskLruCache.Snapshot snapShot = diskLruCache.get(mKey);
if (snapShot != null) {
is = snapShot.getInputStream(0);
byte[] bytes = new byte[2048];
int len;
while ((len = is.read(bytes)) != -1) {
for (int i = 0; i < len; i++) {
data.add(bytes[i]);
}
}
bytes = new byte[data.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = data.get(i);
}
return ObjUtils.byteArray2Object(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public void removeValue(String key) {
String mKey=MD5.encrypt(key);
try {
diskLruCache.remove(mKey);
} catch (IOException e) {
e.printStackTrace();
}
}
public void clear() {
try {
diskLruCache.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
}
kotlin版本
/**
* @Author : yaotianxue
* @Time : On 2023/5/23 08:09
* @Description : DiskLRUCacheUtils
*/
class DiskLRUCacheUtils<V> {
//磁盘缓存路径
private var diskCachePath:String = Environment.getExternalStorageDirectory().absolutePath + File.separator +"2010baweidiskcache"
//磁盘缓存对象
var diskCache: DiskLruCache
//初始化
init {
var file = File(diskCachePath)
if(!file.exists()){//文件夹不存在
file.mkdirs()//创建文件夹
}
diskCache = DiskLruCache.open(file,1,1, MAX_SIZE.toLong())
}
companion object{
const val MAX_SIZE = 200*1024*1024
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
DiskLRUCacheUtils<Any>()
}
}
/**
* 按Key存储值
*/
fun putValue(key:String,data:V){
var mKey = MD5.encrypt(key)//使用工具类将keyMD5加密
var editor = diskCache.edit(mKey)
editor.let {
var arrays = ObjUtils.obj2ByteArray(data)//对象转数组
var outputStream = editor.newOutputStream(0)
outputStream.write(arrays)
editor.commit()
diskCache.flush()
outputStream.close()
}
}
/**
* 按Key获取值
*/
fun getValue(key:String): V? {
var mKey = MD5.encrypt(key)//使用工具类将keyMD5加密
var snapshot = diskCache.get(mKey)//根据键获得snapshot对象
var inputStream = snapshot.getInputStream(0)//获得输入流
var bytes = ByteArray(2048)
var outputStream = ByteArrayOutputStream()//字节数组输出流
var len = 0
//inputStream读取数据到outputStream
while ((inputStream.read(bytes).also { len = it })!=-1){
outputStream.write(bytes)
}
return ObjUtils.byteArray2Object(outputStream.toByteArray())
}
/**
* 按key删除
*/
fun removeValue(key:String){
var mKey = MD5.encrypt(key)//使用工具类将keyMD5加密
diskCache.remove(mKey)
}
/**
* 清空
*/
fun clear(){
diskCache.delete()
}
}
测试使用
//测试
var diskLRUCacheUtils = DiskLRUCacheUtils.instance.putValue("111","我是测试数据")
var str = DiskLRUCacheUtils.instance.getValue("111")
Toast.makeText(this,"$str",Toast.LENGTH_LONG).show()
顾名思义,责任链模式(Chain of Responsibility
Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求
的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不
能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
public interface ResultCallback<T> {
/**
* 成功
* @param
* @return
* @author zhangyue
* @time 2021/9/25 8:28
*/
void Success(T t);
/**
* 失败
* @param
* @return
* @author zhangyue
* @time 2021/9/25 8:28
*/
void Failed(Throwable throwable);
}
import com.zy.storage.callback.ResultCallback;
/**
* @ProjectName: FrameworkApp
* @Package: com.zy.storage
* @ClassName: StorageChain
* @Description:
* 存储链节点的父类提供一些通用的默认实现
* @Author: 张跃 企鹅:444511958
* @CreateDate: 2021/8/18 11:18
* @UpdateUser: 张跃
* @UpdateDate: 2021/8/18 11:18
* @UpdateRemark:
* @Version: 1.0
*/
public abstract class StorageChain<T> {
/**
* 前一个节点
*/
protected StorageChain previousChain;
/**
* 后一个节点
*/
protected StorageChain nextChain;
/**
* 存储值
* @param
* @return
* @author zhangyue
* @time 2021/8/18 11:21
*/
protected abstract void saveData(String key,T data);
/**
* 获取值
* @param key
* @param callback
*/
protected abstract void getData(String key, ResultCallback<T> callback);
/**
* 按key删除具体的值
* @param key
*/
protected abstract void removeAtKey(String key);
/**
* 清空所有的数据
*/
protected abstract void clear();
/**
* 设置下一个节点及设置下个节点的上一个节点是自己
* @param nextChain
*/
public void setNextChain(StorageChain nextChain){
this.nextChain=nextChain;
nextChain.previousChain=this;
}
public StorageChain getNextChain() {
return nextChain;
}
/**
* 判断是否有下级节点
* @param
* @return
* @author zhangyue
* @time 2021/8/19 9:16
*/
public boolean hasNext(){
if (nextChain!=null){
return true;
}
return false;
}
/**
* 存入值暴露给别人使用 已经适配了对应关系
* @param
* @return
* @author zhangyue
* @time 2021/8/18 15:25
*/
public void putValue(String key,T value){
//当前节点已经存储
saveData(key,value);
if (this.nextChain!=null){
this.nextChain.putValue(key,value);
}
}
/**
* 取值暴露给别人使用 已经适配取的对应关系
* @param
* @return
* @author zhangyue
* @time 2021/8/18 15:30
*/
public void getValue(final String key, final ResultCallback<T> callback){
getData(key, new ResultCallback<T>() {
@Override
public void Success(T t) {
//当前链节点没有取到对应数据则向下级节点获取
if (null==t){
nextChain.getValue(key,callback);
}
//如果已经取到同步前面的节点
else{
if (null!=previousChain){
previousChain.putValue(key,t);
}
}
callback.Success(t);
}
@Override
public void Failed(Throwable throwable) {
}
});
}
/**
* 根据key删除数据暴露给别人使用 已经适配取的对应关系
* @param
* @return
* @author zhangyue
* @time 2021/8/18 15:37
*/
public void removeValue(String key){
removeAtKey(key);
if (this.nextChain!=null){
this.nextChain.removeValue(key);
}
}
/**
* 清空数据
*/
public void removeAll(){
clear();
if (this.nextChain!=null){
this.nextChain.removeAll();
}
}
/**
* 获取首节点
* @return
*/
public StorageChain getFirstChain(){
StorageChain own=this;
while (own.previousChain!=null){
own=previousChain;
}
return own;
}
}
DiskChain:磁盘链
package com.zy.storage.impl;
import com.zy.storage.StorageChain;
import com.zy.storage.callback.ResultCallback;
import com.zy.storage.utils.DiskLRUCacheUtils;
/**
* @ProjectName: FrameworkApp
* @Package: com.zy.storage.impl
* @ClassName: DiskChain
* @Description:
* @Author: 张跃 企鹅:444511958
* @CreateDate: 2021/8/18 15:42
* @UpdateUser: 张跃
* @UpdateDate: 2021/8/18 15:42
* @UpdateRemark:
* @Version: 1.0
*/
public class DiskChain<T> extends StorageChain<T> {
@Override
protected void saveData(String key, T data) {
DiskLRUCacheUtils.getInstance().putValue(key,data);
}
@Override
protected void getData(String key, ResultCallback<T> callback) {
T value = (T) DiskLRUCacheUtils.getInstance().getValue(key);
callback.Success(value);
}
@Override
protected void removeAtKey(String key) {
DiskLRUCacheUtils.getInstance().removeValue(key);
}
@Override
protected void clear() {
DiskLRUCacheUtils.getInstance().clear();
}
}
DiskChain:内存链
package com.zy.storage.impl;
import com.zy.storage.StorageChain;
import com.zy.storage.callback.ResultCallback;
import com.zy.storage.utils.LRUCacheUtils;
/**
* @ProjectName: FrameworkApp
* @Package: com.zy.storage.impl
* @ClassName: MemoryChain
* @Description:内存上的存储链节点逻辑
* @Author: 张跃 企鹅:444511958
* @CreateDate: 2021/8/18 15:39
* @UpdateUser: 张跃
* @UpdateDate: 2021/8/18 15:39
* @UpdateRemark:
* @Version: 1.0
*/
public class MemoryChain<T> extends StorageChain<T> {
@Override
protected void saveData(String key, T data) {
LRUCacheUtils.getInstance().putValue(key,data);
}
@Override
protected void getData(String key, ResultCallback<T> callback) {
T value = (T) LRUCacheUtils.getInstance().getValue(key);
if (null!=callback){
callback.Success(value);
}
}
@Override
protected void removeAtKey(String key) {
LRUCacheUtils.getInstance().removeValue(key);
}
@Override
protected void clear() {
LRUCacheUtils.getInstance().clear();
}
}
package com.zy.storage;
import android.util.ArrayMap;
import com.zy.storage.callback.ResultCallback;
import com.zy.storage.impl.DiskChain;
import com.zy.storage.impl.MemoryChain;
import java.sql.SQLTransactionRollbackException;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ProjectName: FrameworkApp
* @Package: com.zy.storage
* @ClassName: StorageChainManager
* @Description:
* @Author: 张跃 企鹅:444511958
* @CreateDate: 2021/8/18 16:31
* @UpdateUser: 张跃
* @UpdateDate: 2021/8/18 16:31
* @UpdateRemark:
* @Version: 1.0
*/
public class StorageChainManager {
private static ConcurrentHashMap<String,StorageChain> chainMap;
private static StorageChainManager instance=null;
private StorageChainManager(){
chainMap=new ConcurrentHashMap<>();
}
private static class Handler{
private static StorageChainManager INSTANCE=new StorageChainManager();
}
public static StorageChainManager getInstance(){
return Handler.INSTANCE;
}
/**
* 第一个节点 内存
*/
private MemoryChain memoryChain=null;
/**
* 第二个节点 磁盘
*/
private DiskChain diskChain=null;
/**
* 初始化链
* @param
* @return
* @author zhangyue
* @time 2021/8/19 9:00
*/
private StorageChain initChain(String key){
memoryChain=new MemoryChain();
diskChain=new DiskChain();
memoryChain.setNextChain(diskChain);
chainMap.put(key,memoryChain);
return diskChain;
}
/**
* 追加链节点
* @param key-链的key标识
* @return
* @author zhangyue
* @time 2021/8/19 9:03
*/
public StorageChainManager addChain(String key,StorageChain storageChain){
if (chainMap.containsKey(key)){
StorageChain chain = chainMap.get(key);
while (chain.hasNext()){
chain=chain.getNextChain();
}
chain.setNextChain(storageChain);
}else{
StorageChain lastestChain = initChain(key);
lastestChain.setNextChain(storageChain);
chainMap.put(key,lastestChain.getFirstChain());
}
return this;
}
/**
* 获取指定链上的值
* @param
* @return
* @author zhangyue
* @time 2021/8/19 9:15
*/
public void getValue(String chainKey,String key, ResultCallback callback){
if (chainMap.containsKey(chainKey)){
chainMap.get(chainKey).getValue(key,callback);
}
}
/**
* 获取到对应链然后存储值
* @param
* @return
* @author zhangyue
* @time 2021/8/19 9:16
*/
public <T> void putValue(String chainKey,String key,T data){
if (chainMap.containsKey(chainKey)){
chainMap.get(chainKey).putValue(key,data);
}
}
}
目前移动端 Deeplink 的说法有狭义和广义之分,分为 Deeplink 和 Deferred Deeplink。
深度链接,指已安装相应 App 的情况下,把特定的参数通过 url 的形式传递给 App,从而直接打开指定的内部页面,实现
从链接直达 App 内部页面的跳转。
延迟深度链接,主要增加了一个是否已安装相应 App 的判断,用户点击链接时,如果未安装 App,则引导用户前往应用市场,下载完对应 App 后,首次打开该 App 时自动跳转进入指定的内部页面。一般业内常说的“Deeplink”其实就是这两层含义相结合的简称。事实上只要结合好这两者,用户不管是否下载了对应 App,只要点击链接,都能一键拉起 App,一步直达想去的任何页面