android中图片的三级缓存cache策略(内存/文件/网络)

 更新时间:2013年06月08日 15:34:39   作者:  
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中
1.简介
现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。

2.图片缓存的原理
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。

关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃 圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。

从代码上来说,采用一个ImageManager来负责图片的管理和缓存,函数接口为public void loadBitmap(String url, Handler handler) ;其中url为要下载的图片地址,handler为图片下载成功后的回调,在handler中处理message,而message中包含了图片的信息以及bitmap对象。ImageManager中使用的ImageMemoryCache(内存缓存)、ImageFileCache(文件缓存)以及LruCache(最近最久未使用缓存)会在后续文章中介绍。

3.代码ImageManager.java
复制代码 代码如下:

/*
* 图片管理
* 异步获取图片,直接调用loadImage()函数,该函数自己判断是从缓存还是网络加载
* 同步获取图片,直接调用getBitmap()函数,该函数自己判断是从缓存还是网络加载
* 仅从本地获取图片,调用getBitmapFromNative()
* 仅从网络加载图片,调用getBitmapFromHttp()
*
*/
public class ImageManager implements IManager
{
private final static String TAG = "ImageManager";

private ImageMemoryCache imageMemoryCache; //内存缓存

private ImageFileCache imageFileCache; //文件缓存

//正在下载的image列表
public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>();

//等待下载的image列表
public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>();

//同时下载图片的线程个数
final static int MAX_DOWNLOAD_IMAGE_THREAD = 4;

private final Handler downloadStatusHandler = new Handler(){
public void handleMessage(Message msg)
{
startDownloadNext();
}
};

public ImageManager()
{
imageMemoryCache = new ImageMemoryCache();
imageFileCache = new ImageFileCache();
}

/**
* 获取图片,多线程的入口
*/
public void loadBitmap(String url, Handler handler)
{
//先从内存缓存中获取,取到直接加载
Bitmap bitmap = getBitmapFromNative(url);
if (bitmap != null)
{
Logger.d(TAG, "loadBitmap:loaded from native");
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.obj = bitmap;
msg.setData(bundle);
handler.sendMessage(msg);
}
else
{
Logger.d(TAG, "loadBitmap:will load by network");
downloadBmpOnNewThread(url, handler);
}
}
/**
* 新起线程下载图片
*/
private void downloadBmpOnNewThread(final String url, final Handler handler)
{
Logger.d(TAG, "ongoingTaskMap'size=" + ongoingTaskMap.size());

if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD)
{
synchronized (waitingTaskMap)
{
waitingTaskMap.put(url, handler);
}
}
else
{
synchronized (ongoingTaskMap)
{
ongoingTaskMap.put(url, handler);
}
new Thread()
{
public void run()
{
Bitmap bmp = getBitmapFromHttp(url);
// 不论下载是否成功,都从下载队列中移除,再由业务逻辑判断是否重新下载
// 下载图片使用了httpClientRequest,本身已经带了重连机制
synchronized (ongoingTaskMap)
{
ongoingTaskMap.remove(url);
}

if(downloadStatusHandler != null)
{
downloadStatusHandler.sendEmptyMessage(0);

}
Message msg = Message.obtain();
msg.obj = bmp;
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.setData(bundle);

if(handler != null)
{
handler.sendMessage(msg);
}
}
}.start();
}
}
/**
* 依次从内存,缓存文件,网络上加载单个bitmap,不考虑线程的问题
*/
public Bitmap getBitmap(String url)
{
// 从内存缓存中获取图片
Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url);
if (bitmap == null)
{
// 文件缓存中获取
bitmap = imageFileCache.getImageFromFile(url);
if (bitmap != null)
{
// 添加到内存缓存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
else
{
// 从网络获取
bitmap = getBitmapFromHttp(url);
}
}
return bitmap;
}

/**
* 从内存或者缓存文件中获取bitmap
*/
public Bitmap getBitmapFromNative(String url)
{
Bitmap bitmap = null;
bitmap = imageMemoryCache.getBitmapFromMemory(url);

if(bitmap == null)
{
bitmap = imageFileCache.getImageFromFile(url);
if(bitmap != null)
{
// 添加到内存缓存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
}
return bitmap;
}

/**
* 通过网络下载图片,与线程无关
*/
public Bitmap getBitmapFromHttp(String url)
{
Bitmap bmp = null;

try
{
byte[] tmpPicByte = getImageBytes(url);

if (tmpPicByte != null)
{
bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0,
tmpPicByte.length);
}
tmpPicByte = null;
}
catch(Exception e)
{
e.printStackTrace();
}

if(bmp != null)
{
// 添加到文件缓存
imageFileCache.saveBitmapToFile(bmp, url);
// 添加到内存缓存
imageMemoryCache.addBitmapToMemory(url, bmp);
}
return bmp;
}

/**
* 下载链接的图片资源
*
* @param url
*
* @return 图片
*/
public byte[] getImageBytes(String url)
{
byte[] pic = null;
if (url != null && !"".equals(url))
{
Requester request = RequesterFactory.getRequester(
Requester.REQUEST_REMOTE, RequesterFactory.IMPL_HC);
// 执行请求
MyResponse myResponse = null;
MyRequest mMyRequest;
mMyRequest = new MyRequest();
mMyRequest.setUrl(url);
mMyRequest.addHeader(HttpHeader.REQ.ACCEPT_ENCODING, "identity");
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
myResponse = request.execute(mMyRequest);
is = myResponse.getInputStream().getImpl();
baos = new ByteArrayOutputStream();
byte[] b = new byte[512];
int len = 0;
while ((len = is.read(b)) != -1)
{
baos.write(b, 0, len);
baos.flush();
}
pic = baos.toByteArray();
Logger.d(TAG, "icon bytes.length=" + pic.length);
}
catch (Exception e3)
{
e3.printStackTrace();
try
{
Logger.e(TAG,
"download shortcut icon faild and responsecode="
+ myResponse.getStatusCode());
}
catch (Exception e4)
{
e4.printStackTrace();
}
}
finally
{
try
{
if (is != null)
{
is.close();
is = null;
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
try
{
if (baos != null)
{
baos.close();
baos = null;
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
try
{
request.close();
}
catch (Exception e1)
{
e1.printStackTrace();
}
}
}
return pic;
}

/**
* 取出等待队列第一个任务,开始下载
*/
private void startDownloadNext()
{
synchronized(waitingTaskMap)
{
Logger.d(TAG, "begin start next");
Iterator iter = waitingTaskMap.entrySet().iterator();

while (iter.hasNext())
{

Map.Entry entry = (Map.Entry) iter.next();
Logger.d(TAG, "WaitingTaskMap isn't null,url=" + (String)entry.getKey());

if(entry != null)
{
waitingTaskMap.remove(entry.getKey());
downloadBmpOnNewThread((String)entry.getKey(), (Handler)entry.getValue());
}
break;
}
}
}

public String startDownloadNext_ForUnitTest()
{
String urlString = null;
synchronized(waitingTaskMap)
{
Logger.d(TAG, "begin start next");
Iterator iter = waitingTaskMap.entrySet().iterator();

while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
urlString = (String)entry.getKey();
waitingTaskMap.remove(entry.getKey());
break;
}
}
return urlString;
}

/**
* 图片变为圆角
* @param bitmap:传入的bitmap
* @param pixels:圆角的度数,值越大,圆角越大
* @return bitmap:加入圆角的bitmap
*/
public static Bitmap toRoundCorner(Bitmap bitmap, int pixels)
{
if(bitmap == null)
return null;
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = pixels;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}

public byte managerId()
{
return IMAGE_ID;
}
}

相关文章

  • Android自定义ViewGroup实现朋友圈九宫格控件

    Android自定义ViewGroup实现朋友圈九宫格控件

    在我们的实际应用中,经常需要用到自定义控件,比如自定义圆形头像,自定义计步器等等,这篇文章主要给大家介绍了关于Android自定义ViewGroup实现朋友圈九宫格控件的相关资料,需要的朋友可以参考下
    2021-07-07
  • Flutter 如何正确显示SnackBar

    Flutter 如何正确显示SnackBar

    Snackbar是Android支持库中用于显示简单消息并且提供和用户的一个简单操作的一种弹出式提醒。当使用Snackbar时,提示会出现在消息最底部,通常含有一段信息和一个可点击的按钮。本文主要介绍了Flutter 如何正确显示 SnackBar
    2021-05-05
  • Android TextView字体颜色设置方法小结

    Android TextView字体颜色设置方法小结

    这篇文章主要介绍了Android TextView字体颜色设置方法,结合实例形式总结分析了Android开发中TextView设置字体颜色的常用技巧,需要的朋友可以参考下
    2016-02-02
  • Android ContentProvider获取手机联系人实例

    Android ContentProvider获取手机联系人实例

    这篇文章主要介绍了Android ContentProvider获取手机联系人实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-02-02
  • Android如何修改默认gradle路径的方法

    Android如何修改默认gradle路径的方法

    Android Studio每次新建项目,都会默认在C盘生成并下载gradle相关文件,那么Android如何修改默认gradle路径的方法,本文就来介绍一下
    2023-08-08
  • Android设备上非root的抓包实现方法(Tcpdump方法)

    Android设备上非root的抓包实现方法(Tcpdump方法)

    通常我们在Android应用中执行某个命令时会使用“Runtime.getRuntime().exec("命令路径")”这种方式,但是当我们执行抓包操作时,使用这条命令无论如何都不行,通过下面代码打印结果发现,该命令一定要在root权限下才能执行,具体实现思路,请参考本教程
    2016-11-11
  • Android RecycleView使用(CheckBox全选、反选、单选)

    Android RecycleView使用(CheckBox全选、反选、单选)

    这篇文章主要为大家详细介绍了Android RecycleView使用,CheckBox全选、反选、单选效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • Android adb logcat 命令查看日志详细介绍

    Android adb logcat 命令查看日志详细介绍

    这篇文章主要介绍了Android adb logcat 命令详细介绍的相关资料,这里对logcat 命令进行了详细介绍,并介绍了过滤日志输出的知识,需要的朋友可以参考下
    2016-12-12
  • 浅析Android手机卫士关闭自动更新

    浅析Android手机卫士关闭自动更新

    保存数据的四种方式,网络,广播提供者,SharedPreferences,数据库。接下来通过本文给大家介绍android手机卫士关闭自动更新的相关知识,感兴趣的朋友一起学习吧
    2016-04-04
  • android通过servlet服务器保存文件到手机

    android通过servlet服务器保存文件到手机

    这篇文章主要为大家详细介绍了android通过servlet服务器保存文件到手机,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-06-06

最新评论