android中图片的三级缓存cache策略(内存/文件/网络)
程序员文章站
2023-12-03 14:12:10
1.简介 现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。现在...
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应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。
现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非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;
}
}