android中圖片的三級(jí)緩存cache策略(內(nèi)存/文件/網(wǎng)絡(luò))
現(xiàn)在android應(yīng)用中不可避免的要使用圖片,有些圖片是可以變化的,需要每次啟動(dòng)時(shí)從網(wǎng)絡(luò)拉取,這種場(chǎng)景在有廣告位的應(yīng)用以及純圖片應(yīng)用(比如百度美拍)中比較多。
現(xiàn)在有一個(gè)問(wèn)題:假如每次啟動(dòng)的時(shí)候都從網(wǎng)絡(luò)拉取圖片的話(huà),勢(shì)必會(huì)消耗很多流量。在當(dāng)前的狀況下,對(duì)于非wifi用戶(hù)來(lái)說(shuō),流量還是很貴的,一個(gè)很耗流量的應(yīng)用,其用戶(hù)數(shù)量級(jí)肯定要受到影響。當(dāng)然,我想,向百度美拍這樣的應(yīng)用,必然也有其內(nèi)部的圖片緩存策略。總之,圖片緩存是很重要而且是必須的。
2.圖片緩存的原理
實(shí)現(xiàn)圖片緩存也不難,需要有相應(yīng)的cache策略。這里我采用 內(nèi)存-文件-網(wǎng)絡(luò) 三層cache機(jī)制,其中內(nèi)存緩存包括強(qiáng)引用緩存和軟引用緩存(SoftReference),其實(shí)網(wǎng)絡(luò)不算cache,這里姑且也把它劃到緩存的層次結(jié)構(gòu)中。當(dāng)根據(jù)url向網(wǎng)絡(luò)拉取圖片的時(shí)候,先從內(nèi)存中找,如果內(nèi)存中沒(méi)有,再?gòu)木彺嫖募胁檎?,如果緩存文件中也沒(méi)有,再?gòu)木W(wǎng)絡(luò)上通過(guò)http請(qǐng)求拉取圖片。在鍵值對(duì)(key-value)中,這個(gè)圖片緩存的key是圖片url的hash值,value就是bitmap。所以,按照這個(gè)邏輯,只要一個(gè)url被下載過(guò),其圖片就被緩存起來(lái)了。
關(guān)于Java中對(duì)象的軟引用(SoftReference),如果一個(gè)對(duì)象具有軟引用,內(nèi)存空間足夠,垃 圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。只要垃圾回收器沒(méi)有回收它,該對(duì)象就可以被程序使用。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高 速緩存。使用軟引用能防止內(nèi)存泄露,增強(qiáng)程序的健壯性。
從代碼上來(lái)說(shuō),采用一個(gè)ImageManager來(lái)負(fù)責(zé)圖片的管理和緩存,函數(shù)接口為public void loadBitmap(String url, Handler handler) ;其中url為要下載的圖片地址,handler為圖片下載成功后的回調(diào),在handler中處理message,而message中包含了圖片的信息以及bitmap對(duì)象。ImageManager中使用的ImageMemoryCache(內(nèi)存緩存)、ImageFileCache(文件緩存)以及LruCache(最近最久未使用緩存)會(huì)在后續(xù)文章中介紹。
3.代碼ImageManager.java
/*
* 圖片管理
* 異步獲取圖片,直接調(diào)用loadImage()函數(shù),該函數(shù)自己判斷是從緩存還是網(wǎng)絡(luò)加載
* 同步獲取圖片,直接調(diào)用getBitmap()函數(shù),該函數(shù)自己判斷是從緩存還是網(wǎng)絡(luò)加載
* 僅從本地獲取圖片,調(diào)用getBitmapFromNative()
* 僅從網(wǎng)絡(luò)加載圖片,調(diào)用getBitmapFromHttp()
*
*/
public class ImageManager implements IManager
{
private final static String TAG = "ImageManager";
private ImageMemoryCache imageMemoryCache; //內(nèi)存緩存
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>();
//同時(shí)下載圖片的線(xiàn)程個(gè)數(shù)
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();
}
/**
* 獲取圖片,多線(xiàn)程的入口
*/
public void loadBitmap(String url, Handler handler)
{
//先從內(nèi)存緩存中獲取,取到直接加載
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);
}
}
/**
* 新起線(xiàn)程下載圖片
*/
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);
// 不論下載是否成功,都從下載隊(duì)列中移除,再由業(yè)務(wù)邏輯判斷是否重新下載
// 下載圖片使用了httpClientRequest,本身已經(jīng)帶了重連機(jī)制
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();
}
}
/**
* 依次從內(nèi)存,緩存文件,網(wǎng)絡(luò)上加載單個(gè)bitmap,不考慮線(xiàn)程的問(wèn)題
*/
public Bitmap getBitmap(String url)
{
// 從內(nèi)存緩存中獲取圖片
Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url);
if (bitmap == null)
{
// 文件緩存中獲取
bitmap = imageFileCache.getImageFromFile(url);
if (bitmap != null)
{
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
else
{
// 從網(wǎng)絡(luò)獲取
bitmap = getBitmapFromHttp(url);
}
}
return bitmap;
}
/**
* 從內(nèi)存或者緩存文件中獲取bitmap
*/
public Bitmap getBitmapFromNative(String url)
{
Bitmap bitmap = null;
bitmap = imageMemoryCache.getBitmapFromMemory(url);
if(bitmap == null)
{
bitmap = imageFileCache.getImageFromFile(url);
if(bitmap != null)
{
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
}
return bitmap;
}
/**
* 通過(guò)網(wǎng)絡(luò)下載圖片,與線(xiàn)程無(wú)關(guān)
*/
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);
// 添加到內(nèi)存緩存
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);
// 執(zhí)行請(qǐng)求
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;
}
/**
* 取出等待隊(duì)列第一個(gè)任務(wù),開(kāi)始下載
*/
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;
}
/**
* 圖片變?yōu)閳A角
* @param bitmap:傳入的bitmap
* @param pixels:圓角的度數(shù),值越大,圓角越大
* @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;
}
}
相關(guān)文章
Android自定義ViewGroup實(shí)現(xiàn)朋友圈九宮格控件
在我們的實(shí)際應(yīng)用中,經(jīng)常需要用到自定義控件,比如自定義圓形頭像,自定義計(jì)步器等等,這篇文章主要給大家介紹了關(guān)于A(yíng)ndroid自定義ViewGroup實(shí)現(xiàn)朋友圈九宮格控件的相關(guān)資料,需要的朋友可以參考下2021-07-07Android TextView字體顏色設(shè)置方法小結(jié)
這篇文章主要介紹了Android TextView字體顏色設(shè)置方法,結(jié)合實(shí)例形式總結(jié)分析了Android開(kāi)發(fā)中TextView設(shè)置字體顏色的常用技巧,需要的朋友可以參考下2016-02-02Android ContentProvider獲取手機(jī)聯(lián)系人實(shí)例
這篇文章主要介紹了Android ContentProvider獲取手機(jī)聯(lián)系人實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法)
通常我們?cè)贏(yíng)ndroid應(yīng)用中執(zhí)行某個(gè)命令時(shí)會(huì)使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當(dāng)我們執(zhí)行抓包操作時(shí),使用這條命令無(wú)論如何都不行,通過(guò)下面代碼打印結(jié)果發(fā)現(xiàn),該命令一定要在root權(quán)限下才能執(zhí)行,具體實(shí)現(xiàn)思路,請(qǐng)參考本教程2016-11-11Android RecycleView使用(CheckBox全選、反選、單選)
這篇文章主要為大家詳細(xì)介紹了Android RecycleView使用,CheckBox全選、反選、單選效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android adb logcat 命令查看日志詳細(xì)介紹
這篇文章主要介紹了Android adb logcat 命令詳細(xì)介紹的相關(guān)資料,這里對(duì)logcat 命令進(jìn)行了詳細(xì)介紹,并介紹了過(guò)濾日志輸出的知識(shí),需要的朋友可以參考下2016-12-12淺析Android手機(jī)衛(wèi)士關(guān)閉自動(dòng)更新
保存數(shù)據(jù)的四種方式,網(wǎng)絡(luò),廣播提供者,SharedPreferences,數(shù)據(jù)庫(kù)。接下來(lái)通過(guò)本文給大家介紹android手機(jī)衛(wèi)士關(guān)閉自動(dòng)更新的相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2016-04-04android通過(guò)servlet服務(wù)器保存文件到手機(jī)
這篇文章主要為大家詳細(xì)介紹了android通過(guò)servlet服務(wù)器保存文件到手機(jī),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06