介绍
本文介绍了三级缓存的原理及操作,并且用策略模式的架子框起来。
1.图片的三级缓存是什么
图片的三级缓存顾名思义就是指三级图片的缓存技术,哦,不对,我在说什么。图片的三级缓存顾名思义就是指在图片加载过程中使用缓存技术,在加载网上的图片时,第一次加载时下载图片然后显示,然后会把图片进行缓存操作,之后如果再次使用这个图片时就不用再次下载了,这样子的话可以节约系统资源,也可以节省用户流量。
那么三级缓存是哪三级呢?
- 网络缓存:这一步其实就是利用网络去下载图片的操作,严格说不能算“缓存操作”,但是为了名称的统一,大家就叫它网络缓存。
- 本地缓存:就是说将图片保存在本地SD卡上,使用时直接从SD卡取出就好了。
- 内存缓存:最麻烦的一步,就是将图片保存在内存中,要知道内存缓存是最快的,但是会占用用户内存,所以需要适当的释放内存,后文有具体描述。
2.流程图来一波
我觉得这张图不错,就直接拿来用了,图很好理解,优先从内存中加载,其次是SD卡,内存是最快的,最后才是从网络下载。记得获取到图片以后要相应的存储在缓存中。
3.上代码
代码使用了策略模式封装,如果不了解策略模式的可以去看看本人另一篇关于策略模式的博客。
缓存的抽象父类:
//缓存的抽象类
public abstract class Cache {
abstract Bitmap get(String url) throws IOException;
abstract void put(String url,Bitmap bitmap);
}
所有的缓存类应当有put和get方法,以便存和取。
文件缓存:
public class FileCache extends Cache {
//文件目录
private static final String LOCAL_CACHE_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath() + "/Cache";
private static final String TAG = "ImageDown";
@Override
Bitmap get(String url) {
try {
File cacheFile = new File(LOCAL_CACHE_PATH, MD5Encoder.encode(url));
if (cacheFile.exists()) {
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
cacheFile));
Log.e(TAG, "get: 图片来源文件");
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
void put(String url, Bitmap bitmap) {
File dir = new File(LOCAL_CACHE_PATH);
if (!dir.exists() || !dir.isDirectory()) {
boolean b = dir.mkdirs();// 创建文件夹
if (!b){
Log.e(TAG, "put: 创建文件夹失败");
}
}
try {
String fileName = MD5Encoder.encode(url);
File cacheFile = new File(dir, fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(
cacheFile));// 参1:图片格式;参2:压缩比例0-100; 参3:输出流
} catch (Exception e) {
e.printStackTrace();
} }
}
这就是一些文件的读写操作,需要注意几点
如果创建文件夹失败,可能是6.0权限的问题。
这里存的名字不是直接以url为名字,而是以url计算的MD5值来作为文件名的,因为有些url不能直接作为文件名
补充一下MD5的工具类:
public class MD5Encoder {
public static String encode(String string) throws Exception {
byte[] hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) {
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
}
内存缓存:
先上代码:
public class MemoryCache extends Cache {
private static final String TAG = "ImageDown";
public MemoryCache() {
this.memortCache = new LruCache<String,Bitmap>((int) (Runtime.getRuntime().maxMemory()/1024/40)){
// 返回每个对象的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;
}
};
}
private LruCache<String,Bitmap> memortCache ;
@Override
Bitmap get(String url) {
Bitmap bitmap = memortCache.get(url);
if (bitmap != null){
Log.e(TAG, "get: 图片来源内存" );
return bitmap;
}else {
return null;
}
}
@Override
void put(String url, Bitmap bitmap) {
memortCache.put(url,bitmap);
}
}
解释几个东西:
java的四种引用:、
- 默认强引用, A a = new A(),当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。所以用完以后记得a = null;
- 软引用SoftReference, 垃圾回收器会考虑回收, 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
SoftReference<String> str=new SoftReference<String>("xxx");
- 弱引用 WeakReference, 垃圾回收器更会考虑回收,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
WeakReference<String> str= new WeakReference<String>("xxx");
- 虚引用PhantomReference, 垃圾回收器最优先回收,就是形同虚设,在任何时候都可能被垃圾回收器回收。
由于内存缓存把图片放入内存中,倘若所有所有图片都强引用,图片越来越多,内存就爆炸了,所以以前很多都是把图片bitmap用成弱引用,然后放进hashmap中,这样可以适当的回收,后来出现了新东西LruCache,就不这么做了。
关于LruCache:
这个是谷歌提供的工具类,采用了一种叫最近最少使用算法,这个LruCache封装了一个LinkedHashMap,这里面放的引言它会根据情况,如果占用内存超出了预设值,就优先释放最近最少使用的引用。
初始化LruCache:
在构造方法里设置LruCache占用的内存应该多大,这里取的是虚拟机分配给本app的内存的1/4
this.memortCache = new LruCache<String,Bitmap>((int) (Runtime.getRuntime().maxMemory()/1024/4)){
// 返回每个对象的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;
}
};
ImageUtils:
public class ImageUtils {
private ImageView imageView;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == GOT_BITMAP) {
imageView.setImageBitmap((Bitmap) msg.obj);
Log.e(TAG, "handleMessage: 图片来源网络");
}
}
};
private static final int GOT_BITMAP = 0;
private static final String TAG = "ImageDown";
Cache mCache = new MemoryCache();
//创建一个线程池,线程数量为cpu的核心数
private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//注入缓存类型
public void setImageCache(Cache imageCache) {
mCache = imageCache;
}
public void ShowPic(String url, ImageView imageView) throws IOException {
this.imageView = imageView;
Bitmap bitmap = mCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
//没有缓存
LoadPic(url, imageView);
}
private void LoadPic(final String url, final ImageView imageView) {
executorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = DownLoadImage(url);
Message message = handler.obtainMessage();
message.what = GOT_BITMAP;
message.obj = bitmap;
handler.sendMessage(message);
mCache.put(url, bitmap);
}
});
}
private Bitmap DownLoadImage(String url) {
Bitmap bitmap = null;
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().get().url(url).build();
Response response = null;
try {
response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
byte[] bytes = response.body().bytes();
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Log.e(TAG, "DownandSet: decodeByteArray");
} else {
Log.e(TAG, "DownLoadImage: 从网络下载图片失败--" + response.message());
}
if (bitmap == null) {
Log.e(TAG, "DownandSet: bitmap==null");
}
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
因为网络缓存是备用方案,所以放进这个类里面实现了。
在showPic方法中有缓存就使用缓存,没缓存就loadPic下载图片。
使用者:
先看一下xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="st.zlei.com.imageload.MainActivity"
android:orientation="vertical">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
android:id="@+id/down_button"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<Button
android:text="清空图片"
android:id="@+id/dclear_button"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
</LinearLayout>
布局长这样:
具体调用:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
down_button = (Button) findViewById(R.id.down_button);
clear_button = (Button) findViewById(R.id.dclear_button);
image = (ImageView) findViewById(R.id.image);
imageUtils = new ImageUtils();
//这里传入使用的缓存类型
imageUtils.setImageCache(new MemoryCache());
down_button.setText("显示图片");
down_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
imageUtils.ShowPic(imageURL, image);
} catch (IOException e) {
e.printStackTrace();
}
}
});
clear_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
image.setImageBitmap(null);
}
});
}
看一下运行结果
首先使用文件缓存:
首先点击显示图片,然后清空图片,再次点击显示图片:
可以看到第一次加载是从网络下载的图片,第二次加载就直接从文件中获取图片了。
在手机内存卡上多了一个cache文件夹,里面存放的就是缓存的图片文件:
使用内存缓存
综合使用三级缓存
上面都只是单独使用的文件缓存或者内存缓存,现在我们综合一下,优先使用内存缓存,如果没有就使用文件缓存,都没有的话才去网络下载图片。
OverAllCache 缓存类,在调用的时候注入这个类即可
public class OverAllCache extends Cache {
private static final String TAG = "ImageDown";
MemoryCache memoryCache = new MemoryCache();
FileCache fileCache = new FileCache();
@Override
Bitmap get(String url) throws IOException {
Bitmap bitmap;
bitmap = memoryCache.get(url);
if (bitmap == null){
bitmap = fileCache.get(url);
if (bitmap != null){
memoryCache.put(url,bitmap);
}
}
return bitmap;
}
@Override
void put(String url, Bitmap bitmap) {
memoryCache.put(url,bitmap);
fileCache.put(url,bitmap);
}
}
看看运行结果
由于之前加载过图片,SD卡上已经有缓存了,此时第一次加载就从文件中获取,之后每次调用都是从内存中获取了。
扩展缓存类型
这部分就是策略模式的架子带来的好处了,当以后想要自己定义怎么缓存,就可以这样调用了:
这里直接传父类Cache,则必须重写get,put方法,于是用户就可以在这里自定义get,put的内容了。
总结
到这里图片的三级缓存就讲完了,一口气写了一天,一顿操作猛如虎,但是我还是得说,当我们真的需要图片加载框架的时候还是Picasso或者Glide什么的更靠谱,本文的内容只是让我们理解三级缓存的知识,跟Picasso什么的相比还是图样图森破。
另外本文所使用代码可在本人github上找到 -->传送门