對開發藝術中的ImageLoader作了下整理。
三級緩存分别為 記憶體, 硬碟, 網絡 , 其中記憶體與硬碟存儲用到 LruCache與DiskLruCache.
用法會封裝在工具類裡面,先看ImageLoader的實作步驟
1. 單例實作ImageLoader, 構造方法中開啟LruCache與DiskLruCache
2. 三個私有化方法,分别從緩存中取出Bitmap
3. 同步加載方法 loadBitmap
4. 異步加載方法 bindBitmap, 開啟線程放入線程池中去執行, 線程中調用同步方法
5. 定義出線程池
6. 定義handler,在handler中去設定imageView
LruCacheDiskCache的代碼都被封裝到的工具類,這個類結構已經相當清晰。下面是源碼
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static final int TAG_KEY_URL = R.id.ivIcon;
private static final int MESSAGE_POST_RESULT = ;
private Context mContext;
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskCache;
private static final long DISK_CACHE_SIZE = * * ; // 指定DiskLruCache緩存空間大小 10M的空間
private static final String CACHE_DIR_NAME = "bitmap"; // 緩存檔案夾名稱
private static final int MEMORY_CACHE_SIZE = (int) (Runtime.getRuntime().maxMemory() / ) / ; //指定LruCache緩存大小 記憶體的八分之一
/****************線程池相關參數********************/
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + ;
private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * ;
private static final int KEEP_ALIVE = ;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
}
};
// 建立線程池
private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactory);
// 實作運作在主線程的handler
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
LoadResult result = (LoadResult) msg.obj;
ImageView imageView = result.imageView;
String url = (String) imageView.getTag(TAG_KEY_URL);
if (url.equals(result.url)) { //解決加載出來後,ImageView已經滑過的問題
imageView.setImageBitmap(result.bitmap);
} else {
Log.w(TAG, "set image bitmap ,but url has changed , ignored");
}
}
};
private ImageLoader(Context context) {
mContext = context;
mMemoryCache = ImageUtil.openLruCache(MEMORY_CACHE_SIZE);
mDiskCache = ImageUtil.openDiskLruCache(mContext, DISK_CACHE_SIZE, CACHE_DIR_NAME);
}
public static ImageLoader build(Context context) {
return new ImageLoader(context);
}
/**
* 把bitmap放入記憶體中
* @param key
* @param bitmap
*/
private void addBitmap2MemoryCache(String key, Bitmap bitmap) {
if (loadBitmMapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 從記憶體中加載
* @param key
* @return
*/
private Bitmap loadBitmMapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 從磁盤中加載
* @param url
* @param reqWidth
* @param reqHeight
* @return
*/
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) {
Bitmap bitmap = ImageUtil.getFromDiskLruCache(url, mDiskCache, reqWidth, reqHeight);
if (bitmap != null) { //放入到記憶體中
mMemoryCache.put(url, bitmap);
}
return bitmap;
}
/**
* 從網絡中加載
* @param url
* @param reqWidth
* @param reqHeight
* @return
*/
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) {
if (Looper.myLooper() == Looper.getMainLooper()) { //判斷是否在主線程
throw new RuntimeException("can not visit net work from UI Thread");
}
if (mDiskCache == null) {
return null;
}
String key = ImageUtil.encode(url);
ImageUtil.put2DisLruCache(url, mDiskCache);
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
/**
* 同步加載
* @return
*/
public Bitmap loadBitmap(String url, int reqWidth, int reqHeight) {
Bitmap bitmap = loadBitmMapFromMemoryCache(url);
if (bitmap != null) {
Log.d(TAG, "loadBitmMapFromMemoryCache, url:" + url);
return bitmap;
}
bitmap = loadBitmapFromDiskCache(url, reqWidth, reqHeight);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromDiskCache, url:" + url);
return bitmap;
}
bitmap = loadBitmapFromHttp(url, reqWidth, reqHeight);
Log.d(TAG, "loadBitmapFromHttp, url:" + url);
if (bitmap != null) {
Log.d(TAG, "DiskLrucache is not created");
bitmap = ImageUtil.downLoadImage(url);
}
return bitmap;
}
/**
* 異步加載
* @param url
* @param imageView
* @param reqWidth
* @param reqHeight
*/
public void bindBitmap(final String url, final ImageView imageView, final int reqWidth, final int reqHeight) {
imageView.setTag(TAG_KEY_URL, url);
Bitmap bitmap = loadBitmMapFromMemoryCache(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(url, reqWidth, reqHeight);
if (bitmap != null) {
LoadResult result = new LoadResult(imageView, url, bitmap);
Message msg = mHandler.obtainMessage(MESSAGE_POST_RESULT, result);
mHandler.sendMessage(msg);
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
/**
* 消息的封裝實體
*/
private static class LoadResult {
public ImageView imageView;
public String url;
public Bitmap bitmap;
public LoadResult(ImageView imageView, String url, Bitmap bitmap) {
this.imageView = imageView;
this.url = url;
this.bitmap = bitmap;
}
}
}
下面是工具類中封裝了LruCache,DiskCache以及從網絡中下載下傳圖檔的具體實作
public class ImageUtil {
/**
* bitmap壓縮
*
* @param res
* @param resId
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* 通過檔案描述符的方法加載bitmap
*
* @param fd
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(FileDescriptor fd, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
/**
* 計算壓縮比例
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = ;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / ;
final int halfWidth = width / ;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= ;
}
}
return inSampleSize;
}
/**
* 打開一個LruCache
*
* @return
*/
public static LruCache<String, Bitmap> openLruCache(int caCheSize) {
LruCache<String, Bitmap> imageCache = new LruCache<String, Bitmap>(caCheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) { //傳回bitmap的大小
return bitmap.getRowBytes() * bitmap.getHeight() / ;
}
};
return imageCache;
}
/**
* 打開DiskLruCache
*
* @param context
* @return
*/
public static DiskLruCache openDiskLruCache(Context context, long maxSize, String cacheDirName) {
DiskLruCache diskLruCache = null;
File cacheDir = getDiskCacheDir(context, cacheDirName);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
Log.i("cacheDir", cacheDir.getPath());
try {
if (getUsableSpace(cacheDir) > maxSize) {
diskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), , maxSize);
}
} catch (IOException e) {
e.printStackTrace();
}
return diskLruCache;
}
/**
* 擷取緩存路徑
*
* @param context
* @param name
* @return
*/
private static File getDiskCacheDir(Context context, String name) {
String cacheDir;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cacheDir = context.getExternalCacheDir().getPath();
} else {
cacheDir = context.getCacheDir().getPath();
}
return new File(cacheDir + File.separator + name);
}
/**
* 擷取系統版本号
*
* @param context
* @return
*/
public static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), );
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return ;
}
/**
* MD5加密
*
* @param url
* @return
*/
public static String encode(String url) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] result = digest.digest(url.getBytes());
StringBuffer sb = new StringBuffer();
for (byte b : result) {
int num = b & ;
String str = Integer.toHexString(num);
if (str.length() == ) {
sb.append("0");
}
sb.append(str);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
// can't reach
return "";
}
}
/**
* 擷取可用空間
* @param path
* @return
*/
public static long getUsableSpace(File path) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
return path.getUsableSpace();
}
StatFs stats = new StatFs(path.getPath());
return stats.getBlockSizeLong() * stats.getAvailableBlocksLong();
}
/**
* 下載下傳圖檔
*
* @param urlString
* @param outputStream
* @return
*/
public static boolean downLoadImage(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), * );
out = new BufferedOutputStream(outputStream, * );
int b;
while ((b = in.read()) != -) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 下載下傳圖檔
* @param urlString
* @return bitmap
*/
public static Bitmap downLoadImage(String urlString) {
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
Bitmap bitmap = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), * );
BitmapFactory.decodeStream(in);
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
/**
* 加載一個圖檔放入DisLruCache
*
* @param imageUrl
* @param disLruCache
*/
public static void put2DisLruCache(String imageUrl, DiskLruCache disLruCache) {
String key = ImageUtil.encode(imageUrl);
DiskLruCache.Editor editor = null;
try {
editor = disLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream();
boolean success = ImageUtil.downLoadImage(imageUrl, outputStream);
if (success) {
editor.commit();
} else {
editor.abort();
}
}
disLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 從DiskLruCache中取bitmap
* @param imageUrl
* @param disLruCache
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap getFromDiskLruCache(String imageUrl, DiskLruCache disLruCache, int reqWidth, int reqHeight) {
try {
String key = encode(imageUrl);
DiskLruCache.Snapshot snapshot = disLruCache.get(key);
if (snapshot != null) {
FileInputStream inputStream = (FileInputStream) snapshot.getInputStream();
FileDescriptor fd = inputStream.getFD();
Bitmap bitmap = decodeSampledBitmapFromResource(fd, reqWidth, reqHeight);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}