ImageCache——图片三级缓存(内存、文件、网络)

 
现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。 (现在很多开源框架都集成了三级缓存,比如Universal-Image-Loader,所以我们没必要自己写,写出来的也没有这些开源框架的效率高,但原理我们还是要了解一下

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非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;  
}  
}  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Android 中使用内存缓存图片,可以使用 LruCache 或自定义实现的内存缓存。 1. LruCache LruCache 是 Android 提供的一个可以回收不常用的 Bitmap 对象的缓存类,它的大小是通过构造函数中传入的 maxsize 来指定的。 以下是使用 LruCache 的示例代码: ``` public class ImageCache { private LruCache<String, Bitmap> mMemoryCache; public ImageCache() { int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; // 可以根据需求进行调整 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 返回 Bitmap 对象的大小,单位为 KB return bitmap.getByteCount() / 1024; } }; } public void addToMemoryCache(String key, Bitmap bitmap) { if (getFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getFromMemoryCache(String key) { return mMemoryCache.get(key); } } ``` 2. 自定义实现的内存缓存 自定义实现的内存缓存可以根据需求进行调整和优化,以下是一个简单的示例代码: ``` public class ImageCache { private Map<String, Bitmap> mMemoryCache; public ImageCache() { mMemoryCache = new HashMap<String, Bitmap>(); } public void addToMemoryCache(String key, Bitmap bitmap) { if (getFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getFromMemoryCache(String key) { return mMemoryCache.get(key); } } ``` 使用内存缓存时,需要注意内存泄漏的问题,可以在 Activity 或 Fragment 的 onDestroy() 方法中释放缓存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值