Android實(shí)現(xiàn)圖片緩存與異步加載
ImageManager2這個(gè)類具有異步從網(wǎng)絡(luò)下載圖片,從sd讀取本地圖片,內(nèi)存緩存,硬盤緩存,圖片使用動(dòng)畫漸現(xiàn)等功能,已經(jīng)將其應(yīng)用在包含大量圖片的應(yīng)用中一年多,沒有出現(xiàn)oom。
Android程序常常會(huì)內(nèi)存溢出,網(wǎng)上也有很多解決方案,如軟引用,手動(dòng)調(diào)用recycle等等。但經(jīng)過我們實(shí)踐發(fā)現(xiàn)這些方案,都沒能起到很好的效果,我們的應(yīng)用依然會(huì)出現(xiàn)很多oom,尤其我們的應(yīng)用包含大量的圖片。android3.0之后軟引用基本已經(jīng)失效,因?yàn)樘摂M機(jī)只要碰到軟引用就回收,所以帶不來任何性能的提升。
我這里的解決方案是HandlerThread(異步加載)+LruCache(內(nèi)存緩存)+DiskLruCache(硬盤緩存)。
作為程序員,我也不多說,直接和大家共享我的代碼,用代碼交流更方便些。
package com.example.util;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.media.ThumbnailUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.widget.ImageView;
import com.example.MyApplication;
/**
* 圖片加載類
*
* @author 月月鳥
*/
public class ImageManager2 {
private static ImageManager2 imageManager;
public LruCache<String, Bitmap> mMemoryCache;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 20; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";
public DiskLruCache mDiskCache;
private static MyApplication myapp;
/** 圖片加載隊(duì)列,后進(jìn)先出 */
private Stack<ImageRef> mImageQueue = new Stack<ImageRef>();
/** 圖片請求隊(duì)列,先進(jìn)先出,用于存放已發(fā)送的請求。 */
private Queue<ImageRef> mRequestQueue = new LinkedList<ImageRef>();
/** 圖片加載線程消息處理器 */
private Handler mImageLoaderHandler;
/** 圖片加載線程是否就緒 */
private boolean mImageLoaderIdle = true;
/** 請求圖片 */
private static final int MSG_REQUEST = 1;
/** 圖片加載完成 */
private static final int MSG_REPLY = 2;
/** 中止圖片加載線程 */
private static final int MSG_STOP = 3;
/** 如果圖片是從網(wǎng)絡(luò)加載,則應(yīng)用漸顯動(dòng)畫,如果從緩存讀出則不應(yīng)用動(dòng)畫 */
private boolean isFromNet = true;
/**
* 獲取單例,只能在UI線程中使用。
*
* @param context
* @return
*/
public static ImageManager2 from(Context context) {
// 如果不在ui線程中,則拋出異常
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new RuntimeException("Cannot instantiate outside UI thread.");
}
if (myapp == null) {
myapp = (MyApplication) context.getApplicationContext();
}
if (imageManager == null) {
imageManager = new ImageManager2(myapp);
}
return imageManager;
}
/**
* 私有構(gòu)造函數(shù),保證單例模式
*
* @param context
*/
private ImageManager2(Context context) {
int memClass = ((ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
memClass = memClass > 32 ? 32 : memClass;
// 使用可用內(nèi)存的1/8作為圖片緩存
final int cacheSize = 1024 * 1024 * memClass / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
File cacheDir = DiskLruCache
.getDiskCacheDir(context, DISK_CACHE_SUBDIR);
mDiskCache = DiskLruCache.openCache(context, cacheDir, DISK_CACHE_SIZE);
}
/**
* 存放圖片信息
*/
class ImageRef {
/** 圖片對應(yīng)ImageView控件 */
ImageView imageView;
/** 圖片URL地址 */
String url;
/** 圖片緩存路徑 */
String filePath;
/** 默認(rèn)圖資源ID */
int resId;
int width = 0;
int height = 0;
/**
* 構(gòu)造函數(shù)
*
* @param imageView
* @param url
* @param resId
* @param filePath
*/
ImageRef(ImageView imageView, String url, String filePath, int resId) {
this.imageView = imageView;
this.url = url;
this.filePath = filePath;
this.resId = resId;
}
ImageRef(ImageView imageView, String url, String filePath, int resId,
int width, int height) {
this.imageView = imageView;
this.url = url;
this.filePath = filePath;
this.resId = resId;
this.width = width;
this.height = height;
}
}
/**
* 顯示圖片
*
* @param imageView
* @param url
* @param resId
*/
public void displayImage(ImageView imageView, String url, int resId) {
if (imageView == null) {
return;
}
if (imageView.getTag() != null
&& imageView.getTag().toString().equals(url)) {
return;
}
if (resId >= 0) {
if (imageView.getBackground() == null) {
imageView.setBackgroundResource(resId);
}
imageView.setImageDrawable(null);
}
if (url == null || url.equals("")) {
return;
}
// 添加url tag
imageView.setTag(url);
// 讀取map緩存
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap != null) {
setImageBitmap(imageView, bitmap, false);
return;
}
// 生成文件名
String filePath = urlToFilePath(url);
if (filePath == null) {
return;
}
queueImage(new ImageRef(imageView, url, filePath, resId));
}
/**
* 顯示圖片固定大小圖片的縮略圖,一般用于顯示列表的圖片,可以大大減小內(nèi)存使用
*
* @param imageView 加載圖片的控件
* @param url 加載地址
* @param resId 默認(rèn)圖片
* @param width 指定寬度
* @param height 指定高度
*/
public void displayImage(ImageView imageView, String url, int resId,
int width, int height) {
if (imageView == null) {
return;
}
if (resId >= 0) {
if (imageView.getBackground() == null) {
imageView.setBackgroundResource(resId);
}
imageView.setImageDrawable(null);
}
if (url == null || url.equals("")) {
return;
}
// 添加url tag
imageView.setTag(url);
// 讀取map緩存
Bitmap bitmap = mMemoryCache.get(url + width + height);
if (bitmap != null) {
setImageBitmap(imageView, bitmap, false);
return;
}
// 生成文件名
String filePath = urlToFilePath(url);
if (filePath == null) {
return;
}
queueImage(new ImageRef(imageView, url, filePath, resId, width, height));
}
/**
* 入隊(duì),后進(jìn)先出
*
* @param imageRef
*/
public void queueImage(ImageRef imageRef) {
// 刪除已有ImageView
Iterator<ImageRef> iterator = mImageQueue.iterator();
while (iterator.hasNext()) {
if (iterator.next().imageView == imageRef.imageView) {
iterator.remove();
}
}
// 添加請求
mImageQueue.push(imageRef);
sendRequest();
}
/**
* 發(fā)送請求
*/
private void sendRequest() {
// 開啟圖片加載線程
if (mImageLoaderHandler == null) {
HandlerThread imageLoader = new HandlerThread("image_loader");
imageLoader.start();
mImageLoaderHandler = new ImageLoaderHandler(
imageLoader.getLooper());
}
// 發(fā)送請求
if (mImageLoaderIdle && mImageQueue.size() > 0) {
ImageRef imageRef = mImageQueue.pop();
Message message = mImageLoaderHandler.obtainMessage(MSG_REQUEST,
imageRef);
mImageLoaderHandler.sendMessage(message);
mImageLoaderIdle = false;
mRequestQueue.add(imageRef);
}
}
/**
* 圖片加載線程
*/
class ImageLoaderHandler extends Handler {
public ImageLoaderHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
if (msg == null)
return;
switch (msg.what) {
case MSG_REQUEST: // 收到請求
Bitmap bitmap = null;
Bitmap tBitmap = null;
if (msg.obj != null && msg.obj instanceof ImageRef) {
ImageRef imageRef = (ImageRef) msg.obj;
String url = imageRef.url;
if (url == null)
return;
// 如果本地url即讀取sd相冊圖片,則直接讀取,不用經(jīng)過DiskCache
if (url.toLowerCase().contains("dcim")) {
tBitmap = null;
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inSampleSize = 1;
opt.inJustDecodeBounds = true;
BitmapFactory.decodeFile(url, opt);
int bitmapSize = opt.outHeight * opt.outWidth * 4;
opt.inSampleSize = bitmapSize / (1000 * 2000);
opt.inJustDecodeBounds = false;
tBitmap = BitmapFactory.decodeFile(url, opt);
if (imageRef.width != 0 && imageRef.height != 0) {
bitmap = ThumbnailUtils.extractThumbnail(tBitmap,
imageRef.width, imageRef.height,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
isFromNet = true;
} else {
bitmap = tBitmap;
tBitmap = null;
}
} else
bitmap = mDiskCache.get(url);
if (bitmap != null) {
// ToolUtil.log("從disk緩存讀取");
// 寫入map緩存
if (imageRef.width != 0 && imageRef.height != 0) {
if (mMemoryCache.get(url + imageRef.width
+ imageRef.height) == null)
mMemoryCache.put(url + imageRef.width
+ imageRef.height, bitmap);
} else {
if (mMemoryCache.get(url) == null)
mMemoryCache.put(url, bitmap);
}
} else {
try {
byte[] data = loadByteArrayFromNetwork(url);
if (data != null) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inSampleSize = 1;
opt.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0,
data.length, opt);
int bitmapSize = opt.outHeight * opt.outWidth
* 4;// pixels*3 if it's RGB and pixels*4
// if it's ARGB
if (bitmapSize > 1000 * 1200)
opt.inSampleSize = 2;
opt.inJustDecodeBounds = false;
tBitmap = BitmapFactory.decodeByteArray(data,
0, data.length, opt);
if (imageRef.width != 0 && imageRef.height != 0) {
bitmap = ThumbnailUtils
.extractThumbnail(
tBitmap,
imageRef.width,
imageRef.height,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
} else {
bitmap = tBitmap;
tBitmap = null;
}
if (bitmap != null && url != null) {
// 寫入SD卡
if (imageRef.width != 0
&& imageRef.height != 0) {
mDiskCache.put(url + imageRef.width
+ imageRef.height, bitmap);
mMemoryCache.put(url + imageRef.width
+ imageRef.height, bitmap);
} else {
mDiskCache.put(url, bitmap);
mMemoryCache.put(url, bitmap);
}
isFromNet = true;
}
}
} catch (OutOfMemoryError e) {
}
}
}
if (mImageManagerHandler != null) {
Message message = mImageManagerHandler.obtainMessage(
MSG_REPLY, bitmap);
mImageManagerHandler.sendMessage(message);
}
break;
case MSG_STOP: // 收到終止指令
Looper.myLooper().quit();
break;
}
}
}
/** UI線程消息處理器 */
private Handler mImageManagerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg != null) {
switch (msg.what) {
case MSG_REPLY: // 收到應(yīng)答
do {
ImageRef imageRef = mRequestQueue.remove();
if (imageRef == null)
break;
if (imageRef.imageView == null
|| imageRef.imageView.getTag() == null
|| imageRef.url == null)
break;
if (!(msg.obj instanceof Bitmap) || msg.obj == null) {
break;
}
Bitmap bitmap = (Bitmap) msg.obj;
// 非同一ImageView
if (!(imageRef.url).equals((String) imageRef.imageView
.getTag())) {
break;
}
setImageBitmap(imageRef.imageView, bitmap, isFromNet);
isFromNet = false;
} while (false);
break;
}
}
// 設(shè)置閑置標(biāo)志
mImageLoaderIdle = true;
// 若服務(wù)未關(guān)閉,則發(fā)送下一個(gè)請求。
if (mImageLoaderHandler != null) {
sendRequest();
}
}
};
/**
* 添加圖片顯示漸現(xiàn)動(dòng)畫
*
*/
private void setImageBitmap(ImageView imageView, Bitmap bitmap,
boolean isTran) {
if (isTran) {
final TransitionDrawable td = new TransitionDrawable(
new Drawable[] {
new ColorDrawable(android.R.color.transparent),
new BitmapDrawable(bitmap) });
td.setCrossFadeEnabled(true);
imageView.setImageDrawable(td);
td.startTransition(300);
} else {
imageView.setImageBitmap(bitmap);
}
}
/**
* 從網(wǎng)絡(luò)獲取圖片字節(jié)數(shù)組
*
* @param url
* @return
*/
private byte[] loadByteArrayFromNetwork(String url) {
try {
HttpGet method = new HttpGet(url);
HttpResponse response = myapp.getHttpClient().execute(method);
HttpEntity entity = response.getEntity();
return EntityUtils.toByteArray(entity);
} catch (Exception e) {
return null;
}
}
/**
* 根據(jù)url生成緩存文件完整路徑名
*
* @param url
* @return
*/
public String urlToFilePath(String url) {
// 擴(kuò)展名位置
int index = url.lastIndexOf('.');
if (index == -1) {
return null;
}
StringBuilder filePath = new StringBuilder();
// 圖片存取路徑
filePath.append(myapp.getCacheDir().toString()).append('/');
// 圖片文件名
filePath.append(MD5.Md5(url)).append(url.substring(index));
return filePath.toString();
}
/**
* Activity#onStop后,ListView不會(huì)有殘余請求。
*/
public void stop() {
// 清空請求隊(duì)列
mImageQueue.clear();
}
}
這里就是給出了異步加載、內(nèi)存緩存和硬盤緩存的解決方案,希望對大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Android實(shí)現(xiàn)沉浸式狀態(tài)欄功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)沉浸式狀態(tài)欄功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
Pagerslidingtabstrip菜單標(biāo)題欄制作方法
這篇文章主要為大家詳細(xì)介紹了Pagerslidingtabstrip菜單標(biāo)題欄的制作方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
android自定義開關(guān)控件-SlideSwitch的實(shí)例
本篇文章主要介紹了android自定義開關(guān)控件-SlideSwitch的實(shí)例,實(shí)現(xiàn)了手機(jī)控件開關(guān)的功能,感興趣的小伙伴們可以參考一下。2016-11-11
Android ViewFlipper的詳解及實(shí)例
這篇文章主要介紹了Android ViewFlipper的詳解及實(shí)例的相關(guān)資料,通過本文希望能幫助大家理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08
Android Lock鎖實(shí)現(xiàn)原理詳細(xì)分析
這篇文章主要介紹了Android Lock鎖實(shí)現(xiàn)原理,Lock接口的實(shí)現(xiàn)類提供了比使用synchronized關(guān)鍵字更加靈活和廣泛的鎖定對象操作,而且是以面向?qū)ο蟮姆绞竭M(jìn)行對象加鎖2023-02-02
Android_UI 仿QQ側(cè)滑菜單效果的實(shí)現(xiàn)
相信大家對QQ側(cè)滑菜單的效果已經(jīng)不陌生了吧,側(cè)滑進(jìn)入個(gè)人頭像一側(cè),進(jìn)行對頭像的更改,我的收藏,QQ錢包,我的文件等一系列的操作,下面小編給大家分享Android_UI 仿QQ側(cè)滑菜單效果的實(shí)現(xiàn),一起看看吧2017-04-04

