用户在使用我们的APP时,通常会重复浏览一些图片,这时如果每一次浏览都需要通过网络获取图片,那么将会非常流量。为了节省用户流量,提高图片加载效率,我们通常使用图片三级缓存策略,即通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量。
网上已经有很多讲述图片三级缓存的策略,这次我也来实现一次三级缓存,其中用到了LRU+SoftReference关于LRU算法,可以参考我之前的博客LinkedHashMap最佳实践:LruCache。首先我将整个机制流程展示给大家:
下面是源码实现:
/**
* @ClassName: CastielImageLoader
* @Description: LRU+SoftReference
* @author 猴子搬来的救兵 http://blog.csdn.net/mynameishuangshuai
*/public class CastielImageLoader {
private static final int MAX_CAPACITY = 20;// 链表长度
private static Context mContext;// 获取APP的缓存地址
private static CastielImageLoader castielImageLoader; // 键是图片地址、值是软引用
private static final LinkedHashMap> firstCacheMap = new LinkedHashMap>(
MAX_CAPACITY) { protected boolean removeEldestEntry(java.util.Map.Entry> eldest) { // 返回true表示移除最老的软引用,保证内存平衡
if (this.size() > MAX_CAPACITY) { return true;
} else {// 否则往磁盘中添加
diskCache(eldest.getKey(), eldest.getValue()); return false;
}
}
}; /**
* 单例模式加载CastielImageLoader
* @return
*/
public static CastielImageLoader getInstance() { if (castielImageLoader == null) {
castielImageLoader = new CastielImageLoader();
} return castielImageLoader;
} /**
* 加载图片到对应组件
*
* @param key 所需加载的路径
* @param view 被加载的组件
* @param drawable 没有加载前默认显示图片
*/
@SuppressWarnings("deprecation") public void loadImage(String key, ImageView view, Drawable drawable ,Context context) {
mContext = context; synchronized (view) { // 检查缓存中是否已有
Bitmap bitmap = getFromCache(key); if (bitmap != null) { // 如果有了就从缓存中取出显示
view.setImageBitmap(bitmap);
} else { // 软应用缓存中不存在,磁盘中也不存在,只能下载
// 下载之前应该先放一张默认图,用来友好显示
view.setBackgroundDrawable(drawable); // 用异步任务去下载
new CastielAsyncImageLoaderTask(view).execute(key);
}
}
} /**
* 判断缓存中是否已经有了,如果有了就从缓存中取出
*
* @param key
* @return
*/
private Bitmap getFromCache(String key) { // 检查内存软引中是否存在
synchronized (firstCacheMap) { if (firstCacheMap.get(key) != null) {// 内存软引用中有
Bitmap bitmap = firstCacheMap.get(key).get(); if (bitmap != null) {// 说明拿到了
firstCacheMap.put(key, new SoftReference(bitmap)); return bitmap;
}
}
} // 检查磁盘中是否存在
Bitmap bitmap = getFromLocalSD(key); if (bitmap != null) {// 硬盘中有
firstCacheMap.put(key, new SoftReference(bitmap)); return bitmap;
} return null;
} /**
* 判断本地磁盘中是否已经有了该图片,如果有了就从本地磁盘中取出
* @param key
* @return
*/
private Bitmap getFromLocalSD(String key) {
String fileName = MD5Util.getMD5Str(key); if (fileName == null) {// 如果文件名为Null,直接返回null
return null;
} else {
String filePath = mContext.getCacheDir().getAbsolutePath() + File.separator + fileName;
InputStream is = null; try {
is = new FileInputStream(new File(filePath));
Bitmap bitmap = BitmapFactory.decodeStream(is); return bitmap;
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally { try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} return null;
} /**
* 把图片缓存到本地磁盘,拿到图片,写到SD卡中
*
* @param key 图片的URL
* @param value Bitmap
*/
private static void diskCache(String key, SoftReference value) { // 把写入SD的图片名字改为基于MD5加密算法加密后的名字
String fileName = MD5Util.getMD5Str(key);
String filePath = mContext.getCacheDir().getAbsolutePath() + File.separator + fileName;
FileOutputStream os = null; try {
os = new FileOutputStream(new File(filePath)); if (value.get() != null) {
value.get().compress(Bitmap.CompressFormat.JPEG, 60, os);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally { if (os != null) { try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} /**
*
* @ClassName: MyAsyncImageLoaderTask
* @Description: 异步加载图片
* @author
*/
class CastielAsyncImageLoaderTask extends AsyncTask{ private ImageView imageView;// 图片组件
private String key;//图片路径
public CastielAsyncImageLoaderTask(ImageView imageView) { this.imageView = imageView;
} @Override
protected Bitmap doInBackground(String... params) { this.key = params[0];// 图片的路径
Bitmap bitmap = castielDownload(key); return bitmap;
} @Override
protected void onPostExecute(Bitmap result) { super.onPostExecute(result); if (result != null) {// 说明已经下载下来了
addFirstCache(key,result);
imageView.setImageBitmap(result);// 加载网络中的图片
}
}
} /**
* 根据图片路径执行图片下载
* @param key
* @return
*/
public Bitmap castielDownload(String key) {
InputStream is = null; try {
is = CastielHttpUtils.castielDownLoad(key); return BitmapFactory.decodeStream(is);// InputStream这种加载方式暂用内存最小
} catch (IOException e) {
e.printStackTrace();
} finally { try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
} return null;
} /**
* 添加到缓存中去
* @param key
* @param result
*/
public void addFirstCache(String key, Bitmap result) { if (result != null) { synchronized (firstCacheMap) {
firstCacheMap.put(key, new SoftReference(result));
}
}
}
}
网络加载工具类 CastielHttpUtils.Java
public class CastielHttpUtils { public static InputStream castielDownLoad(String key) throws IOException{
HttpURLConnection conn = (HttpURLConnection) new URL(key).openConnection(); return conn.getInputStream();
}
}
测试,调用我们的图片缓存工具 MainActivity.java
public class MainActivity extends Activity {
ImageView img;
String imgURl = "http://img2.imgtn.bdimg.com/it/u=3722998253,3365379445&fm=21&gp=0.jpg"; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = (ImageView) findViewById(R.id.img);
CastielImageLoader.getInstance().loadImage(imgURl, img, this.getResources().getDrawable(R.drawable.ic_launcher),MainActivity.this);
}
}
布局文件 activity_main
<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" >
<TextView android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="猴子搬来的救兵 http://blog.csdn.net/mynameishuangshuai" />
<ImageView android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv"
android:layout_marginLeft="20dp"
android:layout_marginTop="80dp"
android:src="@drawable/tt" />RelativeLayout>
测试加载图片结果如下: