LRUCache(内存)DiskLruCache (硬盘)缓存的
了解
什么是LruCache?
LruCache是Android在API12上引入的一种缓存机制,这种缓存有个特点,就是当缓存容量达到上限时,会将最近最少使用的对象从cache中清除出去。
那么这种缓存方式跟普通的缓存有什么区别呢?为什我需要使用它?
曾经,在各大缓存图片的框架没流行的时候。有一种很常用的内存缓存技术:SoftReference 和 WeakReference(软引用和弱引用),但是走到了 Android 2.3(Level 9)时代,垃圾回收机制更倾向于回收 SoftReference 或 WeakReference 的对象。后来,又来到了Android3.0,图片缓存在native中,因为不知道要在是什么时候释放内存,没有策略,没用一种可以预见的场合去将其释放,这就造成了内存溢出。
所以LruCache的出现就是为了解决上述问题,它使用强引用来缓存,消除了垃圾回收机制的影响,同时通过设定缓存上限以及著名的LRU算法来管理内存,使得内存的管理变得更加高效和灵活。
如何使用LruCache?
LruCache是通过LinkedHashMap来实现LRU内存管理的,所以我们可以将其当作一个map来使用,如put,get,remove等。但是要注意,LruCache是不允许key和value为空的。
接下来是针对LruCache这个知识点的一些延伸
Lru是什么?
LRU是操作系统内存管理中比较经典的一种缓存淘汰算法,相信学过操作系统的童鞋应该都有耳闻。操作系统里面还存在一些其他的缓存淘汰算法,如LIFO,FIFO,LFU等,它们都是从某一个维度来评估缓存中条目的重要性,从而当缓存不足时淘汰掉其中最不重要的条目。可见当我们在做App开发时,操作系统里面很多的技术思想其实也可以拿来做参考。
LinkedHashMap为什么能实现LruCache中的Lru?
LinkedHashMap跟HashMap一个重要区别就是其内部的entry是有序的,它有两种排序规则:插入排序和访问排序,其实也就是按put调用时间排序还是按get调用时间来排序。LinkedHashMap通过设置访问排序来是实现Lru的。
LRUCache缓存一张图片
1.建立一个类(图片通过这个类进行缓存)
public class MemCache {
private LruCache<String, Bitmap> mImageCache;
// 初始化 - 构造
public MemCache() {
initCache();
}
private void initCache() {
// 计算可使用的最大内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取4分之一的可用内存作为缓存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
/**
* 保存数据--bitmap
* @param url 图片的name
* @param bitmap
*/
public void putBitmap(String url, Bitmap bitmap) {
if (mImageCache != null) {
mImageCache.put(url, bitmap);
}
}
//获取缓存资源
public Bitmap getBitmap(String url){
return mImageCache.get(url);
}
}
2.在activity中实现缓存
public class MainActivity extends AppCompatActivity {
private String url = "http://cms-bucket.ws.126.net/2019/02/15/97392c98fe22485a8a2458ba9dd907d9.jpeg";
private ImageView img;
private MemCache memCache=new MemCache();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
@Override
protected void onStart() {
super.onStart();
/*
* 1.在activity开始就要实现
* 2.首先获取缓存资源判断是否为空
* 3.如果为空就请求网络缓存
* 4.如果不为空就用缓存的数据
* */
Bitmap bitmap = memCache.getBitmap(url);
if(bitmap==null){
initData();
}else {
img.setImageBitmap(bitmap);
}
}
private void initData() {
//1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//2.创建Request请求对象
Request build = new Request.Builder()
.get()
.url(url)
.build();
//3.创建Call对象
Call call = okHttpClient.newCall(build);
//4.异步
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//通过BitmapFactory把加载成bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(response.body().byteStream());
if(memCache.getBitmap(url)!=null){
memCache.putBitmap(url,bitmap);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
img.setImageBitmap(bitmap);
}
});
}
});
}
private void initView() {
img = (ImageView) findViewById(R.id.img);
}
}
DiskLruCache 是缓存在硬盘中的
依赖
implementation 'com.jakewharton:disklrucache:2.0.2'
给字符加密一些准备措施的类
public class Utils {
private static final String TAG = "Utils";
public File getDiskCacheDir;
/**
* 获取缓存文件夹,这里优先选择SD卡下面的android/data/packageName/cache/路径,若没有SD卡,就选择data/data/packageName/cache
*
* @param context 上下文环境
* @param uniqueName 缓存文件夹名称
* @return 返回缓存文件
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
File file = new File(cachePath + File.separator + uniqueName);
Log.d(TAG, "getDiskCacheDir: file="+file.getAbsolutePath());
return file;
}
/**
* 获取本App的版本号
*
* @param context context上下文
* @return 返回版本号
*/
public static int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* 给字符串来个md5加密,
* @param key 需要加密的string
* @return 返回加密后的string ,或者加密失败,就返回string的哈希值
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
//md5加密
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
//若md5加密失败,就用哈希值
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* 字节数组转为十六进制字符串
* @param bytes 字节数组
* @return 返回十六进制字符串
*/
private static String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length()==1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
建立一个操控缓存对象的类
public class DiskCache {
private static final String TAG = "DiskCache";
DiskLruCache mDiskLruCache;
// 初始化
public DiskCache(File directory, long maxSize) {
try {
mDiskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 保存bitmap到磁盘
* 通过图片url 然后md5 加密的内容,作为key 来保存bitmap 对象
*
* @param url 图片请求url --》 md5 去除特殊字符
* @param bitmap
*/
public void saveBitmap(String url, Bitmap bitmap) {
try {
String key = Utils.hashKeyForDisk(url);
//editor 操作数据保存逻辑
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OutputStream os = editor.newOutputStream(0);
//此处存的一个 bitmap 对象因此用 ObjectOutputStream
ObjectOutputStream outputStream = new ObjectOutputStream(os);
// 下载图片
if (downloadImage(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
//别忘了关闭流和提交编辑
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过url 获取md5加密后的key。然后通过key 获取bitmap 对象
*
* @param url
* @return
*/
public Bitmap getBitmap(String url) {
//使用DiskLruCache获取缓存,需要传入key,而key是imageUrl加密后的字符串,
Bitmap bitmap = null;
String key = Utils.hashKeyForDisk(url);
//通过key获取的只是一个快照
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(key);
} catch (IOException e) {
e.printStackTrace();
}
if (snapshot != null) {
InputStream inputStream = snapshot.getInputStream(0);//类似写缓存时候,传入的是缓存的编号
//可以使用bitmapFactory
bitmap = BitmapFactory.decodeStream(inputStream);
}
return bitmap;
}
/**
* 下载图片
*
* @param imgUrl 图片网址链接
* @param outputStream 输出流对象
* @return 返回时候完成下载成功
*/
private boolean downloadImage(String imgUrl, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
URL url = new URL(imgUrl);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);//Buffer输入流,8M大小的缓存
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;//正在读取的byte
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//关闭资源
finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
}
在activity中的使用
public class Main2Activity extends AppCompatActivity {
private String imgUrl = "http://cms-bucket.ws.126.net/2019/02/15/97392c98fe22485a8a2458ba9dd907d9.jpeg";
private ImageView mImg;
DiskCache diskCache;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
// 初始化硬盘缓存对象
diskCache = new DiskCache(Utils.getDiskCacheDir(this,"cache"),10*1024);
/**
* 动态获取权限,Android 6.0 新特性,一些保护权限,除了要在AndroidManifest中声明权限,还要使用如下代码动态获取
*/
if (Build.VERSION.SDK_INT >= 23) {
int REQUEST_CODE_CONTACT = 101;
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,WRITE_SECURE_SETTINGS};
//验证是否许可权限
for (String str : permissions) {
if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
//申请权限
this.requestPermissions(permissions, REQUEST_CODE_CONTACT);
return;
}
}
}
}
@Override
protected void onStart() {
super.onStart();
if (diskCache.getBitmap(imgUrl)!=null){
mImg.setImageBitmap(diskCache.getBitmap(imgUrl));
}else{
requestNetBitmap();
}
}
private void initView() {
mImg = (ImageView) findViewById(R.id.img);
}
/**
* 请求网络的图片对象
*/
private void requestNetBitmap() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.get()
.url(imgUrl)
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: ");
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
// final byte[] bytes = response.body().bytes();
final InputStream inputStream = response.body().byteStream();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
if (bitmap != null) {
// 保存磁盘数据
diskCache.saveBitmap(imgUrl,bitmap);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
mImg.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
});
}
}