初級時在ListView或是GridView中需要處理大量圖檔時,建立的一個單獨類。ImageLoader類,來實作異步雙緩存加載圖檔功能。
異步:AsyncTask來實作。
雙緩存:一級緩存LinkedHashMap(LRU(Least Recently Used)政策,即當記憶體使用不足時,把最近最少使用的資料從緩存中移除,保留使用最頻繁的資料。
二級緩存ConcurrentHashMap實作。
LinkedHashMap可以記住插入順序,ConcurrentHashMap可以線程安全。
LinkedHashMap實作一級緩存
構造方法 new LinkedHashMap(10, 0.75, true);
參數1:緩存的大小一般為10或是5。
參數2:加載因子 經驗值為0.75。
參數3:true:按照最近通路量的高低排序,false:按照插入順序排序。
/**
* 一級緩存 mFirstLevelCache
* 0.75是加載因子為經驗值
* true則表示按照最近通路量的高低排序,false則表示按照插入順序排序
* */
private HashMap<String, Bitmap> mFirstLevelCache = new LinkedHashMap<String, Bitmap>(MAX_CAPACITY / 2, 0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
if (size() > MAX_CAPACITY) {// 當超過一級緩存門檻值的時候,将老的值從一級緩存搬到二級緩存
mSecondLevelCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else {
return false;
}
}
};
二級緩存ConcurrentHashMap
/**
* 二級緩存 mSecondLevelCache
* 采用的是軟引用 隻有在記憶體吃緊的時候軟引用才會被回收 有效的避免了oom
* */
private ConcurrentHashMap<String, SoftReference<Bitmap>> mSecondLevelCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(MAX_CAPACITY / 2);
一級緩存放入到二級緩存中時
m.put(eldest.getKey(),new SoftReference<Bitmap>(eldest.getValue()));
總結
設定兩級緩存,第一級用LinkedHashMap<String,Bitmap>保留Bitmap的強引用,但是控制緩存的大小MAX_CAPACITY=10,當繼續向該緩存中存資料的時候。
将會把一級緩存中的最近最少使用的元素放入二級緩存ConcurrentHashMap<String, SoftReference<Bitmap>>,二級緩存中保留的Bitmap的軟引用。 SoftReference:它儲存的對象執行個體,除非JVM即将OutOfMemory,否則不會被GC回收。這個特性使得它特别适合設計對象Cache。對于Cache,我們希望被緩存的對象最好始終常駐記憶體,但是如果JVM記憶體吃緊,為了不發生OutOfMemoryError導緻系統崩潰,必要的時候也允許JVM回收Cache的記憶體,待後續合适的時機再把資料重新Load到Cache中。這樣可以系統設計得更具彈性。
工具類
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static final int MAX_CAPACITY = 10;// 一級緩存的最大空間
private static final long DELAY_BEFORE_PURGE = 10 * 1000;// 定時清理緩存
/**
* 一級緩存 mFirstLevelCache
* 0.75是加載因子為經驗值
* true則表示按照最近通路量的高低排序,false則表示按照插入順序排序
* */
private HashMap<String, Bitmap> mFirstLevelCache
= new LinkedHashMap<String, Bitmap>(MAX_CAPACITY / 2, 0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
if (size() > MAX_CAPACITY) {// 當超過一級緩存門檻值的時候,将老的值從一級緩存搬到二級緩存
mSecondLevelCache.put(eldest.getKey(),
new SoftReference<Bitmap>(eldest.getValue()));
return true;
}
return false;
};
};
/**
* 二級緩存 mSecondLevelCache
* 采用的是軟引用 隻有在記憶體吃緊的時候軟引用才會被回收 有效的避免了oom
* */
private ConcurrentHashMap<String, SoftReference<Bitmap>> mSecondLevelCache
= new ConcurrentHashMap<String, SoftReference<Bitmap>>(MAX_CAPACITY / 2);
//Handler對象
private Handler mPurgeHandler = new Handler();
/**
* 定時清理緩存
* */
private Runnable mClearCache = new Runnable() {
public void run() {
clear();
}
};
/**
* 重置緩存清理的timer
* */
private void resetPurgeTimer() {
mPurgeHandler.removeCallbacks(mClearCache);
mPurgeHandler.postDelayed(mClearCache, DELAY_BEFORE_PURGE);
}
/**
* 清理一級二級緩存的方法
*/
private void clear() {
mFirstLevelCache.clear();
mSecondLevelCache.clear();
}
/**
* 從緩存中拿圖檔,先從一級緩存中拿,判斷後再從二級緩存中拿
*/
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap = null;
bitmap=getFromFirstLevelCache(url);//從一級緩存中拿
if(bitmap!=null){
return bitmap;
}else{
bitmap=getFromSecondLevelCache(url);//從二級緩存中拿
return bitmap;
}
}
/**
* 從二級緩存中拿圖檔方法
*/
private Bitmap getFromSecondLevelCache(String url) {
Bitmap bitmap = null;
SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
if (softReference!=null) {
bitmap=softReference.get();
if (bitmap==null) {//由于記憶體吃緊 軟引用已經被gc回收了
mSecondLevelCache.remove(url);
}
}
return bitmap;
}
/**
* 從一級緩存中拿圖檔方法
*/
private Bitmap getFromFirstLevelCache(String url) {
Bitmap bitmap = null;
synchronized (mFirstLevelCache) {
bitmap = mFirstLevelCache.get(url);
if (bitmap!=null) {// 将最近通路的元素放到鍊的頭部,提高下一次通路該元素的檢索速度(LRU算法)
mFirstLevelCache.remove(url);
mFirstLevelCache.put(url, bitmap);
}
}
return bitmap;
}
/**
* 菜單頁面ListView模式 加載圖檔,如果緩存中有就直接從緩存中拿,緩存中沒有就下載下傳
*/
public void loadImageListView(String url, BaseAdapter adapter, ViewHolder holder){
resetPurgeTimer();//重置緩存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//從緩存中讀取
if(bitmap==null){//緩存中沒有相應的圖檔
//先設為預設圖檔
holder.menu_iv.setImageResource(R.drawable.default_s);
//再通過AsyncTask擷取圖檔
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//緩存中有相應的圖檔
holder.menu_iv.setImageBitmap(bitmap);//設為緩存圖檔
}
}
/**
* 菜單頁面GridView模式 加載圖檔,如果緩存中有就直接從緩存中拿,緩存中沒有就下載下傳
*/
public void loadImageGridView(String url, BaseAdapter adapter,com.mealo.adapter.CookBookMenuGridViewAdapter.ViewHolder holder) {
resetPurgeTimer();//重置緩存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//從緩存中讀取
if(bitmap==null){//緩存中沒有相應的圖檔
//先設為預設圖檔
holder.menu_iv.setImageResource(R.drawable.default_s);
//再通過AsyncTask擷取圖檔
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//緩存中有相應的圖檔
holder.menu_iv.setImageBitmap(bitmap);//設為緩存圖檔
}
}
/**
* 菜單頁面ImageView模式 加載圖檔,如果緩存中有就直接從緩存中拿,緩存中沒有就下載下傳
*/
public void loadImageImageView(String url,ImageView iv) {
resetPurgeTimer();//重置緩存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//從緩存中讀取
if(bitmap==null){//緩存中沒有相應的圖檔
//先設為預設圖檔
iv.setImageResource(R.drawable.default_b);
//再通過AsyncTask擷取圖檔
ImageLoadTaskImageView imageLoadTask=new ImageLoadTaskImageView();
imageLoadTask.execute(url,iv);
}else{//緩存中有相應的圖檔
iv.setImageBitmap(bitmap);//設為緩存圖檔
}
}
/**
* 訂單頁面 加載圖檔,如果緩存中有就直接從緩存中拿,緩存中沒有就下載下傳
*/
public void loadImageOrderListView(String url, BaseAdapter adapter,com.mealo.adapter.OrderDialogAdapter.ViewHolder holder) {
resetPurgeTimer();//重置緩存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//從緩存中讀取
if(bitmap==null){//緩存中沒有相應的圖檔
//先設為預設圖檔
holder.titleiv.setImageResource(R.drawable.default_s);
//再通過AsyncTask擷取圖檔
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//緩存中有相應的圖檔
holder.titleiv.setImageBitmap(bitmap);//設為緩存圖檔
}
}
/**
* 菜品詳情頁面 加載圖檔,如果緩存中有就直接從緩存中拿,緩存中沒有就下載下傳
*/
public void loadImageMenuDetailedListView(String url, BaseAdapter adapter,com.mealo.adapter.MenuDetailedDialogAdapter.ViewHolder holder) {
resetPurgeTimer();//重置緩存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//從緩存中讀取
if(bitmap==null){//緩存中沒有相應的圖檔
//先設為預設圖檔
holder.titleiv.setImageResource(R.drawable.default_s);
//再通過AsyncTask擷取圖檔
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//緩存中有相應的圖檔
holder.titleiv.setImageBitmap(bitmap);//設為緩存圖檔
}
}
/**
* 圖檔放入一級緩存方法
*/
public void addImage2Cache(String url, Bitmap value) {
if(value==null||url==null){
return;
}
synchronized (mFirstLevelCache) {
mFirstLevelCache.put(url, value);
}
}
/**
* ImageLoadTask(内部類)繼承AsyncTask相當于新開了一個線程
* 本地加載sd卡,ListView模式和GridView模式
* */
class ImageLoadTask extends AsyncTask<Object, Void, Bitmap> {
String url;
BaseAdapter adapter;
@Override
protected Bitmap doInBackground(Object... params) {
url=(String)params[0];
adapter=(BaseAdapter)params[1];
Bitmap drawable=loadImageFromSd(url);
return drawable;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result==null) {
return;
}
addImage2Cache(url, result);//放入緩存
adapter.notifyDataSetChanged();//觸發getView方法執行 這個時候getView實際上會拿到剛剛緩存好的圖檔
}
}
/**
* ImageLoadTask(内部類)繼承AsyncTask相當于新開了一個線程
* 本地加載sd卡,ImageView模式
* */
class ImageLoadTaskImageView extends AsyncTask<Object, Void, Bitmap>{
String url;
@Override
protected Bitmap doInBackground(Object... params) {
url=(String) params[0];
Bitmap drawable=loadImageFromSd(url);
return drawable;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result==null) {
return;
}
addImage2Cache(url, result);//放入緩存
}
}
/**
* 連接配接網絡下載下傳圖檔方法
* */
public Bitmap loadImageFromInternet(String url) {
Bitmap bitmap = null;
HttpClient client = AndroidHttpClient.newInstance("Android");
HttpParams params = client.getParams();
HttpConnectionParams.setConnectionTimeout(params, 3000);
HttpConnectionParams.setSocketBufferSize(params, 3000);
HttpResponse response = null;
InputStream inputStream = null;
HttpGet httpGet = null;
try {
httpGet = new HttpGet(url);
response = client.execute(httpGet);
int stateCode = response.getStatusLine().getStatusCode();
if (stateCode != HttpStatus.SC_OK) {
Log.d(TAG, "func [loadImage] stateCode=" + stateCode);
return bitmap;
}
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
inputStream = entity.getContent();
return bitmap = BitmapFactory.decodeStream(inputStream);
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (ClientProtocolException e) {
httpGet.abort();
e.printStackTrace();
} catch (IOException e) {
httpGet.abort();
e.printStackTrace();
} finally {
((AndroidHttpClient) client).close();
}
return bitmap;
}
/**
* 本地sd卡擷取圖檔方法
* */
public Bitmap loadImageFromSd(String url) {
Bitmap bitmap=null;
bitmap=BitmapFactory.decodeFile(url);
while(bitmap==null){
bitmap=BitmapFactory.decodeFile(url);
}
return bitmap;
}
}
使用
Adapter中
if(!mBusy){
loader.loadImageListView(url, this, vh);
}else{
Bitmap bitmap = loader.getBitmapFromCache(url);
if(bitmap != null){
vh.menu_iv.setImageBitmap(bitmap);
}else{
vh.menu_iv.setImageResource(R.drawable.default_s);
}
}
附1:LruCache類講解
LruCache是android提供的一個緩存工具類,其算法是最近最少使用算法。它把最近使用的對象用“強引用”存儲在LinkedHashMap中,并且把最近最少使用的對象在緩存值達到預設定值之前就從記憶體中移除。
其中用到的資料對象是LinkedHashMap,是以不要把這個類想的多麼深不可測,還是資料結構 + 算法。既然用到了這個map,自然就要有添加修改和删除操作了,用到了最近最少使用算法,自然就要用到優先級了。
作為緩存,肯定有一個緩存的大小,這個大小是可以設定的(自定義sizeOf())。當你通路了一個item(需要緩存的對象),這個item應該被加入到記憶體中,然後移動到一個隊列的頂部,如此循環後這個隊列的頂部應該是最近通路的item了,而隊尾部就是很久沒有通路的item,這樣我們就應該對隊尾部的item優先進行回收操作。
因為用到了HashMap,那麼就有這個資料存儲對象的特點(KEY-VALUE),放入這個map的item應該會被強引用,要回收這個對象的時候是讓這個key為空,這樣就讓有向圖找不到對應的value,最終被GC。
緩存的最大特點是不做重複的勞動,如果你之前已經緩存過這個item了,當你再次想要緩存這個item時,應該會先判斷是否已經緩存好了,如果已經緩存,那麼就不執行添加的操作。
我們應該能通過某個方法來清空緩存,這個緩存在app被退出後就自動清理,不會常駐記憶體。
sizeof()方法。這個方法預設傳回的是你緩存的item數目,如果你想要自定義size的大小,直接重寫這個方法,傳回自定義的值即可。
附2:LinkedHashMap講解
LinkedHashMap 是HashMap的一個子類,儲存了記錄的插入順序,在用Iterator周遊LinkedHashMap時,先得到的記錄肯定是先插入的.也可以在構造時用帶參數,按照應用次數排序。在周遊的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際資料較少時,周遊起來可能會比 LinkedHashMap慢,因為LinkedHashMap的周遊速度隻和實際資料有關,和容量無關,而HashMap的周遊速度和他的容量有關。