Android中的图片三级缓存
为什么要使用三级缓存
- 如今时代,获取网络图片是件再正常不过的事情。
- 假如每次启动应用程序都要重新从网络获取图片,流量使用量将是巨大的。
- 所以提出图片三级缓存。内存,本地,网络三级缓存来减少和网络之间不必要的交互,避免流量浪费。
什么是三级缓存
- 内存缓存 优先加载。
- 本地缓存 次优先加载。
- 网络缓存 后加载,速度慢。
三级缓存原理
- 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中
- 之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片
- 总之,只在初次访问新内容时,才通过网络获取图片资源
具体实现代码
1 . 自定义图片缓存工具类(CacheUtils)
import android.graphics.Bitmap;
import android.util.Log;
import android.widget.ImageView;
public class CacheUtils {
private static final String TAG = "CacheUtils";
private MemoryCacheUtils mMemoryCacheUtils;
private LocalCacheUtils mLocalCacheUtils;
private NetCacheUtils mNetCacheUtils;
public CacheUtils() {
mMemoryCacheUtils = new MemoryCacheUtils();
mLocalCacheUtils = new LocalCacheUtils();
mNetCacheUtils = new NetCacheUtils(mMemoryCacheUtils, mLocalCacheUtils);
}
public void diaplay(ImageView imageView, String url) {
//内存缓存 生命周期同调用者
Bitmap bitmap = mMemoryCacheUtils.getBitmapToMemory(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
Log.i(TAG, "从内存中获取图片******");
return;
}
//本地缓存
bitmap = LocalCacheUtils.getBitmapToLoacl(url);
if (bitmap != null) {
Log.i(TAG, "从本地中获取图片******");
imageView.setImageBitmap(bitmap);
mMemoryCacheUtils.putBitmapToMemory(bitmap, url);
return;
}
//网络缓存
mNetCacheUtils.getBitmapFromNet(imageView, url);
}
}
2 . 网络缓存
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class NetCacheUtils {
private MemoryCacheUtils mMemoryCacheUtils;
private LocalCacheUtils mLocalCacheUtils;
public NetCacheUtils(MemoryCacheUtils mMemoryCacheUtils, LocalCacheUtils mLocalCacheUtils) {
this.mLocalCacheUtils = mLocalCacheUtils;
this.mMemoryCacheUtils = mMemoryCacheUtils;
}
/**
* 从网络下载图片
* @param imageView 显示图片的imageview
* @param url 下载图片的网络地址
*/
public void getBitmapFromNet(ImageView imageView, String url) {
imageView.setTag(url);
Bitmaptask task = new Bitmaptask();
task.execute(imageView, url);
}
/**
* AsyncTask就是对handler和线程池的封装
* 第一个泛型:参数类型
* 第二个泛型:更新进度的泛型
* 第三个泛型:onPostExecute的返回结果
*/
class Bitmaptask extends AsyncTask<Object, Void, Bitmap> {
private HttpURLConnection urlConnection;
private ImageView imageView;
private String url;
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
if (url.equals(imageView.getTag())) {
imageView.setImageBitmap(bitmap);
System.out.print("onPostExecute"+"从网络获取图片*****");
mLocalCacheUtils.putBitmapToLoacl(bitmap, url);
mMemoryCacheUtils.putBitmapToMemory(bitmap, url);
}
}
}
@Override
protected Bitmap doInBackground(Object[] params) {
imageView = (ImageView) params[0];
url = (String) params[1];
Bitmap bitmap = downloadFromNet(url);
return bitmap;
}
private Bitmap downloadFromNet(String url) {
try {
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setConnectTimeout(5000);
urlConnection.setRequestMethod("GET");
int responseCode = urlConnection.getResponseCode();
if (responseCode == 200) {
InputStream inputStream = urlConnection.getInputStream();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;//压缩图片2倍
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, opts);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
urlConnection.disconnect();
}
return null;
}
}
}
3 . 本地缓存
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import static android.content.ContentValues.TAG;
public class LocalCacheUtils {
/**
* 从网络获取图片后,保存至本地缓存
* @param url
* @param bitmap
*/
public static void putBitmapToLoacl(Bitmap bitmap, String url) {
String encode = Md5Utils.encode(url);//使用路径为文件名,进行md5加密
File file = new File(Environment.getExternalStorageDirectory(), encode);
Log.i(TAG, "putBitmapToLoacl: " + file.toString());
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
try {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 从本地读取图片
* @param url
*/
public static Bitmap getBitmapToLoacl(String url) {
String encode = Md5Utils.encode(url);
File file = new File(Environment.getExternalStorageDirectory(), encode);
if (file.exists()) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 3;
try {
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, opts);
return bitmap;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
return null;
}
}
4 . 内存缓存
通过 HashMap<String,Bitmap>键值对的方式保存图片,key为地址,value为图片对象,但因是强引用对象,很容易造成内存溢出,可以尝试SoftReference软引用对象。
通过 HashMap<String, SoftReference<Bitmap>>SoftReference 为软引用对象(GC垃圾回收会自动回收软引用对象),但在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
通过 LruCache<String,Bitmap> least recentlly use 最少最近使用算法
会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定.
import android.graphics.Bitmap;
import android.util.Log;
import android.util.LruCache;
import static android.content.ContentValues.TAG;
public class MemoryCacheUtils {
private LruCache<String, Bitmap> mMemoryCache;
public MemoryCacheUtils() {
int maxmemory = (int) Runtime.getRuntime().maxMemory();
Log.i(TAG, "MemoryCacheUtils: " + maxmemory);
mMemoryCache = new LruCache<String, Bitmap>(maxmemory / 8) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
/**
* 往内存中写图片
* @param url
* @param bitmap
*/
public void putBitmapToMemory(Bitmap bitmap, String url) {
Log.i(TAG, "putBitmapToMemory: ");
mMemoryCache.put(url, bitmap);
}
/**
* 从内存中读图片
* @param url
*/
public Bitmap getBitmapToMemory(String url) {
Log.i(TAG, "getBitmapToMemory: ");
Bitmap bitmap = mMemoryCache.get(url);
return bitmap;
}
}
5 . 本地缓存用到的md5工具类
import java.security.MessageDigest;
public class Md5Utils {
public static String encode(String pwd) {
try {
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] bs = digest.digest(pwd.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : bs) {
int number = b & 0xff;
String str = Integer.toHexString(number);
if (str.length() == 1) {
sb.append("0");
}
sb.append(number);
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}