现在很多App的某些功能会把图片以及内容缓存在本地,即使是没有网络的情况下也还是可以加载出之前浏览过的内容来,这些功能就是使用了DiskLruCache技术;
LruCache负责管理内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时,所以Google又提供了一套硬盘缓存的解决方案:DiskLruCache;
DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,不过一般来说会存储在SD卡中, 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。 前者获取到的就是 /sdcard/Android/data//cache 这个路径 而后者获取到的是 data/data/<application - package>/cache这个路径,接下来就用一个简单的照片墙demo来阐述DiskLruCache以及LruCache缓存技术吧;
先附上我的项目结构
DiskLruCache这个类并没有在android api中,所以需要从网上下载下来,然后加在项目中就可以了,这里我就附上链接了
https://download.csdn.net/download/qq_42889476/11485240
因为图片的话是需要机密处理的,所以我们需要一个工具类----MD5转换工具类
package com.example.disklrucache_demo;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
//使用MD5加密算法
public static String md5(String plainText){
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
//16进制
String md5Code = new BigInteger(1,secretBytes).toString(16);
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - md5Code.length(); i++) {
md5Code = "0" + md5Code;
}
return md5Code;
}
}
然后是数据集合
package com.example.disklrucache_demo;
public class Images {
public final static String[] imageThumbUrls = new String[]{
"http://image.biaobaiju.com/uploads/20180211/01/1518283483-ZSaWgidtGK.jpg",
"http://img.51ztzj.com/upload/image/20131024/dn201310242017_670x419.jpg",
"http://image.biaobaiju.com/uploads/20180803/20/1533300593-nYwpoTMUEs.jpg",
"http://img.juimg.com/tuku/yulantu/130316/267856-1303160U31010.jpg",
"http://pic1.win4000.com/wallpaper/2017-10-18/59e7096016774.jpg",
"http://gss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/b64543a98226cffcad22c094ba014a90f603ea88.jpg",
"http://img.pconline.com.cn/images/photoblog/1/0/1/5/10159318/200911/4/1257320021267_mthumb.jpg",
"http://img3.duitang.com/uploads/item/201605/18/20160518232450_zxyFi.thumb.700_0.jpeg",
"http://img.tukexw.com/img/252557a08a4ae4d1.jpg",
"http://img4q.duitang.com/uploads/blog/201409/18/20140918211053_zaFRk.thumb.700_0.jpeg",
"http://www.zhutizhijia.net/uploads/120323/24-120323155524102.jpg",
"http://image.biaobaiju.com/uploads/20180706/04/1530821556-qYtMwiolae.jpg",
"http://b-ssl.duitang.com/uploads/item/201302/17/20130217212232_xAK5C.jpeg",
"http://b-ssl.duitang.com/uploads/item/201201/23/20120123195204_A8CRN.thumb.700_0.jpg",
"http://image.yy.com/yywebalbumbs2bucket/28e43cdd19324941b73fe57c09046ddd_1487870799169.jpeg",
"http://img5.duitang.com/uploads/blog/201508/10/20150810145410_5xEQJ.thumb.700_0.jpeg",
"http://photocdn.sohu.com/20120704/Img347284777.jpg",
"http://img4q.duitang.com/uploads/item/201411/03/20141103115630_HMhnU.jpeg",
"http://img1.doubanio.com/pview/event_poster/raw/public/6eec72f07eb8c98.jpg",
"http://img4q.duitang.com/uploads/item/201306/23/20130623230957_fmVV4.thumb.700_0.jpeg",
"http://img2.ph.126.net/RJc77dzeQTv8z2CHGmzzgQ==/1828179973736115230.jpg",
"http://s10.sinaimg.cn/middle/950ebfd0nb5be42e39a09&690",
"http://g.hiphotos.baidu.com/zhidao/pic/item/ae51f3deb48f8c54fa5fd5673a292df5e1fe7fed.jpg",
"http://img21.mtime.cn/mg/2010/09/14/162329.60324301.jpg",
"http://i1.szhomeimg.com/n/2013/04/10/0410171600540.JPG",
"http://b-ssl.duitang.com/uploads/item/201609/18/20160918141907_WBG2A.jpeg",
"http://k.zol-img.com.cn/sjbbs/6319/a6318625_s.jpg",
"http://b-ssl.duitang.com/uploads/item/201205/16/20120516125715_AfAiz.jpeg",
"http://b-ssl.duitang.com/uploads/item/201206/15/20120615095518_UB3CS.jpeg",
"http://i1.sinaimg.cn/edu/2012/0413/U7516P42DT20120413140544.jpg",
"http://www.cnr.cn/newscenter/gjxw/list/201305/W020130516373227379082.jpg",
"http://b-ssl.duitang.com/uploads/item/201204/12/20120412130056_kKxcd.jpeg",
"http://img.boqiicdn.com/Data/BK/A/1301/23/img84111358931890_y.jpg",
"http://img3.100bt.com/upload/ttq/20130323/1364004564120.png",
"http://img01.tooopen.com/Downs/images/2011/7/19/sy_2011071921084512107.jpg",
"http://img2.shangxueba.com/img/lady_Img/20140808/17/E7EACCFD9928B5B0064022F8E0E7DB36.jpg"
};
}
注:(如果程序跑起来了是默认的图片的话,建议自己去网上找一点图片的链接,然后再跑一遍就OK了)
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<GridView
android:id="@+id/grid_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:columnWidth="90dp"
android:stretchMode="columnWidth"
android:gravity="center">
</GridView>
</RelativeLayout>
item_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/img_photo"
android:layout_width="120dp"
android:layout_height="120dp" />
</LinearLayout>
网格布局有了,子item也有了,接下来就需要一个适配器了
流程:继承ArrayAdatper,在构造函数传入必要参数后,然后在getView方法中来设置图片源,首先先设置成一张默认的图片,然后根据图片的URL去缓存中找是否有相关联的资源,如果没找到在磁盘缓存中找,如果还是没找到再去网络上下载,然后保存在磁盘缓存里,下载资源的话是一个耗时的操作,所以开启了一个内部类Async异步类去完成,把所有的耗时操作都安排在doInBackground里执行,然后下载好了会缓存在内存和磁盘中
这个适配器的其他功能我都用代码注释好了,就不一一讲解了
package com.example.disklrucache_demo;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import static com.example.disklrucache_demo.MainActivity.TAG;
public class ImageAdapter extends ArrayAdapter {
//声明LruCache缓存对象
private LruCache<String, Bitmap> lruCache;
// 声明DiskLruCache硬盘缓存对象
private DiskLruCache diskLruCache;
//任务列表
private Set<LoadImageAsync> tasks;
//声明GridView对象
private GridView gridView;
public ImageAdapter(Context context, int textViewResourceId,String[] objects,GridView gridView) {
super(context, textViewResourceId,objects);
this.gridView = gridView;
tasks = new HashSet<LoadImageAsync>();
//初始化内存缓存LruCache,获取应用最大可占内存
int MaxMemory = (int) Runtime.getRuntime().maxMemory();
//设置最大内存的八分之一作为图片资源的缓存大小
int LruMemory = MaxMemory / 8;
lruCache = new LruCache<String, Bitmap>(LruMemory){
@Override
protected int sizeOf(String key, Bitmap value) {
//返回Bitmap对象所占大小,单位:kb
return value.getByteCount();
}
};
//初始化硬盘缓存DiskLruCache,获取硬盘的缓存路径,参数2为所在缓存路径的文件夹的名称
File directory = getDiskCacheDir(getContext(),"bitmap");
if (!directory.exists()){
//若文件不存在,则创建新的文件夹
directory.mkdirs();
}
//获取App版本号
int appVersion = getAppVersion(getContext());
//缓存1:缓存文件路径,参数2:系统版本号,参数3:一个缓存路径对几个文件,缓存4:缓存的空间大小
try {
diskLruCache = DiskLruCache.open(directory,appVersion,1,1024*1024*10);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param context
* @param uniqueName
* @return
* 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir
* ()方法来获取缓存路径。 前者获取到的就是 /sdcard/Android/data/<application-
* package>/cache 这个路径 而后者获取到的是 data/data/application - package>/cache
* 这个路径。
*/
public 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();
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* @param context
* @return 获取系统版本号
*/
public int getAppVersion(Context context){
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//获取图片资源URL地址
String path = (String) getItem(position);
View view = null;
if (convertView == null){
//加载子布局
view = LayoutInflater.from(getContext()).inflate(R.layout.item_activity,null);
}else {
view = convertView;
}
//获取控件实例
ImageView imageView = view.findViewById(R.id.img_photo);
//设置唯一一个标识符,避免异步加载时错位
imageView.setTag(path);
//设置默认显示图片
imageView.setImageResource(R.drawable.timg);
// 根据图片URL到缓存中去找图片资源并设置
setImageFromLruCache(path, imageView);
return view;
}
/**
* 根据图片URL地址获取缓存中图片,若不存在去磁盘缓存中查找,磁盘中没有就进行网络下载
*
* @param path
* @param imageView
*/
private void setImageFromLruCache(String path,ImageView imageView){
//获取图片的地址
Bitmap bitmap = lruCache.get(path);
if (bitmap != null){
//有缓存,直接取出图片
Log.d(TAG, "setImageFromLruCache: 在内存缓存中找到图片" );
imageView.setImageBitmap(bitmap);
}else {
// 缓存不存在,先找硬盘缓存,没有的话就去网络下载(开启异步任务)
LoadImageAsync loadImageAsync = new LoadImageAsync();
loadImageAsync.execute(path);
//添加在任务列表
tasks.add(loadImageAsync);
}
}
/**
* 取消队列中准备下载和正在下载的任务
*/
public void cancelTask(){
for (LoadImageAsync task : tasks){
task.cancel(false);
}
}
/**
* 同步内存操作到journal文件
*/
public void flushCache() {
if (diskLruCache != null) {
try {
diskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class LoadImageAsync extends AsyncTask<String,Void,Bitmap>{
String path = null;
@Override
protected Bitmap doInBackground(String... strings) {
//图片的下载地址
this.path = strings[0];
DiskLruCache.Snapshot snapshot = null;
OutputStream outputStream = null;
Bitmap bitmap = null;
String pathMd5 = MD5Utils.md5(path);
//根据图片的URL查找图片是否存在于硬盘
try {
snapshot = diskLruCache.get(pathMd5);
if (snapshot == null){
//在磁盘缓存里没有找到对应的图片资源
DiskLruCache.Editor editor = diskLruCache.edit(pathMd5);
if (editor != null){
outputStream = editor.newOutputStream(0);
// 开启异步网络任务获取图片,并存入磁盘缓存
if (DownLoadToStream(path,outputStream)){
//下载成功
// Log.d(TAG, "doInBackground: DownLoad Image Suc" );
editor.commit();
}
}
}
// 图片写入磁盘缓存后,再一次的查找磁盘缓存
snapshot = diskLruCache.get(pathMd5);
if (snapshot != null){
// 若查找到,获取图片,并把图片资源写入内存缓存
bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
Log.d(TAG, "doInBackground: 在磁盘缓存中找到");
}
if (bitmap != null) {
// 将Bitmap对象添加到内存缓存当中
lruCache.put(path,bitmap);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//根据Tag获取控件对象并设置图片
ImageView imageView = gridView.findViewWithTag(path);
if (imageView != null && bitmap != null) {
//加载图片
imageView.setImageBitmap(bitmap);
}
tasks.remove(this);
}
/**
* 根据图片URL地址下载图片,成功返回true,失败false
*
* @param urlString
* @param outputStream
* @return
*/
private boolean DownLoadToStream(String urlString,OutputStream outputStream){
HttpURLConnection connection = null;
BufferedInputStream inputStream = null;
BufferedOutputStream outputStream1 = null;
try {
final URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
inputStream = new BufferedInputStream(connection.getInputStream(),8*1024);
outputStream = new BufferedOutputStream(outputStream,8*1024);
int b;
//读
while ((b = inputStream.read()) != -1){
//写
outputStream.write(b);
}
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (connection != null) {
connection.disconnect();
}
try{
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
}
MainActivity类
在onPause方法里进行flush操作(更新磁盘缓存操作日志,磁盘缓存之所以能够被读取取决于日志文件
package com.example.disklrucache_demo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.jakewharton.disklrucache.DiskLruCache;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private GridView gv_photo;
private ImageAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gv_photo= findViewById(R.id.grid_view);
adapter = new ImageAdapter(MainActivity.this,0,Images.imageThumbUrls,gv_photo);
gv_photo.setAdapter(adapter);
}
@Override
protected void onPause() {
super.onPause();
adapter.flushCache();
}
@Override
protected void onDestroy() {
super.onDestroy();
adapter.cancelTask();
}
}
附上效果图
最后的话附上一个Demo下载链接