PS:本文中的例子來源於官網地址:Caching Bitmaps,原始碼地址(自備梯子):Caching Bitmaps Demo,並在其基礎上稍微改變了一下。
PPS:本文僅用於學習利用LruCache、DiskLruCache圖片快取策略、實現瀑布流和Matix檢視大圖縮放移動等功能,如果想用到專案中,建議用更成熟的框架,如glide、picasso 等。
在開始本文之前,請先了解下LruCache和DiskLruCache的用法,不瞭解的可以先看下這兩篇: 1、Android使用磁碟快取DiskLruCache 2、Android記憶體快取LruCache原始碼解析
先上效果圖:
嗯,效果還是不錯的~程式碼已上傳Github:LruCache、DiskLruCache實現圖片快取
###圖片瀑布流 這個用RecycleView來實現已經很簡單了,直接上程式碼:
recycler_view = (RecyclerView) findViewById(R.id.recycler_view);
recycler_view.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
複製程式碼
首先初始化StaggeredGridLayoutManager,這裡設定顯示方式是豎直方向3列,然後通過recycleView的setLayoutManager設定好,接著看RecyclerView.Adapter中的處理:
public class WaterFallAdapter extends RecyclerView.Adapter<WaterFallAdapter.MyHolder> {
private int DATA_SIZE = Constant.imageUrls.length;
private List<Integer> hList;//定義一個List來存放圖片的height
public WaterFallAdapter(Context mContext) {
this.mContext = mContext;
hList = new ArrayList<>();
for (int i = 0; i < DATA_SIZE; i++) {
//每次隨機一個高度並新增到hList中
int height = new Random().nextInt(200) + 300;//[100,500)的隨機數
hList.add(height);
}
}
@Override
public void onBindViewHolder(final MyHolder holder, int position) {
//通過setLayoutParams(params)來設定圖片的寬高資訊
int width = DpUtil.getScreenSizeWidth(mContext) / 3;
RecyclerView.LayoutParams params = new RecyclerView.LayoutParams(width, hList.get(position));
holder.imageView.setLayoutParams(params);
}
}
複製程式碼
在WaterFallAdapter構造方法中將隨機高度新增到存放圖片height的hList中,並在onBindViewHolder()中取出圖片的寬高並通過setLayoutParams(params)來設定圖片的寬高資訊,經過上面的程式碼,一個圖片的瀑布流效果就出來了,so easy~
###圖片快取 下面接著看本文的重點,實現圖片的快取策略:
先看怎麼使用的:
private static final String IMAGE_CACHE_DIR = "thumbs";//圖片快取目錄
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
ImageCache.ImageCacheParams cacheParams = new ImageCache.ImageCacheParams(this, IMAGE_CACHE_DIR);
cacheParams.setMemCacheSizePercent(0.25f);// Set memory cache to 25% of app memory
// The ImageFetcher takes care of loading images into our ImageView children asynchronously
mImageFetcher = new ImageFetcher(this, (int) DpUtil.dp2px(this, 100));
mImageFetcher.setLoadingImage(R.mipmap.img_default_bg);
mImageFetcher.addImageCache(cacheParams);
}
複製程式碼
上面初始化了快取大小和下載時ImageView載入預設的圖片等操作,然後在RecyclerView的onBindViewHolder()開始載入圖片:
@Override
public void onBindViewHolder(final MyHolder holder, int position) {
.............其他操作.............
mImageFetcher.loadImage(Constant.imageUrls[position], holder.imageView);
}
複製程式碼
用起來還是挺簡單的,裡面主要的幾個核心類:
接下來就依次分析一下各個類的作用:
#####ImageCache.java:
public class ImageCache {
// Default memory cache size in kilobytes
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5KB
// Default disk cache size in bytes
private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
//記憶體快取核心類,用於快取已經下載好的圖片
private DiskLruCache mDiskLruCache;
//磁碟快取核心類,用於快取圖片到外部儲存中
private LruCache<String, BitmapDrawable> mMemoryCache;
//ImageCacheParams 是ImageCache 的內部類,用於設定圖片快取快取的各個引數
private ImageCacheParams mCacheParams;
//使用Set儲存資料可以確保沒有重複元素,使用軟引用SoftReference關聯Bitmap,當記憶體不足時Bitmap會被回收
private Set<SoftReference<Bitmap>> mReusableBitmaps;
//實現一個單例模式
private volatile static ImageCache imageCache;
public static ImageCache getInstance(ImageCacheParams cacheParams) {
if (imageCache == null) {
synchronized (ImageCache.class) {
if (imageCache == null) {
imageCache = new ImageCache(cacheParams);
}
}
}
return imageCache;
}
private ImageCache(ImageCacheParams cacheParams) {
init(cacheParams);
}
private void init(ImageCacheParams cacheParams) {
mCacheParams = cacheParams;
if (Utils.hasHoneycomb()) {
mReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
//初始化LruCache並覆寫entryRemoved()和sizeOf()方法
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
@Override
protected int sizeOf(String key, BitmapDrawable value) {
final int bitmapSize = getBitmapSize(value) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}
};
// By default the disk cache is not initialized here as it should be initialized
// on a separate thread due to disk access.
if (cacheParams.initDiskCacheOnCreate) {
// Set up disk cache
initDiskCache();
}
}
//初始化DiskLruCache,因為操作外部儲存比較耗時,所以這部分最好放在子執行緒中執行
public void initDiskCache() {
// Set up disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = mCacheParams.diskCacheDir;
if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
try {
mDiskLruCache = DiskLruCache.open(
diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
} catch (final IOException e) {
mCacheParams.diskCacheDir = null;
Log.e(TAG, "initDiskCache - " + e);
}
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
}
//將圖片新增到記憶體快取和外部快取中
public void addBitmapToCache(String data, BitmapDrawable value) {
if (data == null || value == null) {
return;
}
// Add to memory cache
if (mMemoryCache != null) {
if (RecyclingBitmapDrawable.class.isInstance(value)) {
// The removed entry is a recycling drawable, so notify it
// that it has been added into the memory cache
((RecyclingBitmapDrawable) value).setIsCached(true);
}
mMemoryCache.put(data, value);
}
synchronized (mDiskCacheLock) {
//Add to disk cache
if (mDiskLruCache != null) {
final String key = hashKeyForDisk(data);
OutputStream out = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
out = editor.newOutputStream(DISK_CACHE_INDEX);
value.getBitmap().compress(
mCacheParams.compressFormat, mCacheParams.compressQuality, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (Exception e) {
Log.e(TAG, "addBitmapToCache - " + e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
}
}
}
}
}
//清除快取,該方法也應該在子執行緒中呼叫
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache cleared");
}
}
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
} catch (Exception e) {
Log.e(TAG, "clearCache - " + e);
}
mDiskLruCache = null;
initDiskCache();
}
}
}
//從記憶體快取中取出圖片
public BitmapDrawable getBitmapFromMemCache(String data) {
BitmapDrawable memValue = null;
if (mMemoryCache != null) {
memValue = mMemoryCache.get(data);
}
return memValue;
}
//從外部儲存中取出圖片
public Bitmap getBitmapFromDiskCache(String data) {
final String key = hashKeyForDisk(data);
Bitmap bitmap = null;
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (Exception e) {
}
}
if (mDiskLruCache != null) {
InputStream inputStream = null;
try {
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
// Decode bitmap, but we don't want to sample so give
// MAX_VALUE as the target dimensions
bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
}
}
} catch (Exception e) {
Log.e(TAG, "getBitmapFromDiskCache - " + e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
}
}
}
}
return bitmap;
}
public static class ImageCacheParams {
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
public File diskCacheDir;
public Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
public int compressQuality = DEFAULT_COMPRESS_QUALITY;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
public ImageCacheParams(Context context, String diskCacheDirectoryName) {
//外存快取目錄
diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
}
//設定圖片記憶體快取和應用最大記憶體的比例
public void setMemCacheSizePercent(float percent) {
memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
}
}
}
複製程式碼
ImageCache這個類主要是初始化了LruCache和DiskLruCache用來快取圖片到記憶體和外存中去,並從記憶體外存中取出圖片及清除快取等操作
#####ImageWorker.java:
public abstract class ImageWorker {
//初始化ImageCache和外存快取
public void addImageCache(ImageCache.ImageCacheParams cacheParams) {
mImageCacheParams = cacheParams;
mImageCache = ImageCache.getInstance(mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
//載入本地圖片,如果沒有去伺服器下載該圖片
public void loadImage(Object data, ImageView imageView, OnImageLoadedListener listener) {
if (data == null) return;
BitmapDrawable bitmapDrawable = null;
if (mImageCache != null) {
bitmapDrawable = mImageCache.getBitmapFromMemCache(String.valueOf(data));
}
if (bitmapDrawable != null) {
// Bitmap found in memory cache
imageView.setImageDrawable(bitmapDrawable);
if (listener != null) {
listener.onImageLoaded(true);
}
} else if (cancelPotentialWork(data, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView, listener);
final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, mLoadingBitmap, task);
imageView.setImageDrawable(asyncDrawable);
// NOTE: This uses a custom version of AsyncTask that has been pulled from the
// framework and slightly modified. Refer to the docs at the top of the class
// for more info on what was changed.
task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR);
}
}
}
複製程式碼
ImageWorker處理ImageView載入Bitmap時的耗時操作,如處理記憶體快取和外存快取的使用,ImageWorker中的兩個主要方法:addImageCache() 和 loadImage() 方法。
1、addImageCache()中獲得了ImageCache的單例物件,並開啟CacheAsyncTask在新執行緒中初始化快取:
protected class CacheAsyncTask extends AsyncTask<Object, Void, Void> {
@Override
protected Void doInBackground(Object... params) {
int param = (int) params[0];
switch (param) {
case MESSAGE_CLEAR:
clearCacheInternal();
break;
case MESSAGE_INIT_DISK_CACHE:
initDiskCacheInternal();
break;
case MESSAGE_FLUSH:
flushCacheInternal();
break;
case MESSAGE_CLOSE:
closeCacheInternal();
break;
}
return null;
}
}
//清除快取
protected void clearCacheInternal() {
if (mImageCache != null) {
mImageCache.clearCache();
}
}
//初始化快取
protected void initDiskCacheInternal() {
if (mImageCache != null) {
mImageCache.initDiskCache();
}
}
//強制將快取重新整理到檔案系統中
protected void flushCacheInternal() {
if (mImageCache != null) {
mImageCache.flush();
}
}
//關閉快取
protected void closeCacheInternal() {
if (mImageCache != null) {
mImageCache.close();
mImageCache = null;
}
}
複製程式碼
2、loadImage()方法中,首先通過mImageCache.getBitmapFromMemCache嘗試從記憶體中取出圖片,如果記憶體中有,直接取出來顯示,流程結束;如果記憶體中沒有,就呼叫到了cancelPotentialWork(),來看這個方法:
public static boolean cancelPotentialWork(Object data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final Object bitmapData = bitmapWorkerTask.mData;
if (bitmapData == null || !bitmapData.equals(data)) {
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress.
return false;
}
}
return true;
}
//獲得與ImageView關聯的BitmapWorkerTask ,如果沒有返回Null
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
複製程式碼
首先通過getBitmapWorkerTask()獲得與ImageView關聯的BitmapWorkerTask(以下簡稱task),如果作用在ImageView上的task存在並且task.mData(圖片的URL)沒有改變,那麼task接著執行,直到下載完圖片;如果task.mData變化了,那麼取消之前作用在ImageView上的task,重新去new一個Task下載新的圖片。其實是這樣:**當我們不滑動介面的時候,task.mData(圖片的URL)是不會改變的,但是當我們滑動介面的時候,比如現在RecycleView一屏顯示5個ItemView,往上滑動時,第一個ItemView不可見並且此時第一個ItemView的圖片還沒有下載完成,第6個ItemView會複用之前第一個ItemView,如果此時繼續執行第一個ItemView對應的task,那麼這時候第6個ItemView顯示的就是第一個ItemView應該顯示的圖片了,這就是我們經常遇到的顯示錯位問題。所以這裡要判斷task.mData(圖片的URL)是否一致,如果不一致,需要取消之前的task,重啟一個新的task,並把新的task關聯給ImageView。**看看task裡面都幹了些什麼:
private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
private Object mData;
private final WeakReference<ImageView> imageViewWeakReference;
private final OnImageLoadedListener mOnImageLoadedListener;
/**
* Background processing.
*/
public BitmapWorkerTask(Object data, ImageView imageView) {
mData = data;
imageViewWeakReference = new WeakReference<ImageView>(imageView);
mOnImageLoadedListener = null;
}
public BitmapWorkerTask(Object data, ImageView imageView, OnImageLoadedListener listener) {
mData = data;
//弱引用關聯一個ImageView
imageViewWeakReference = new WeakReference<>(imageView);
mOnImageLoadedListener = listener;
}
@Override
protected BitmapDrawable doInBackground(Void... params) {
final String dataString = String.valueOf(mData);
Bitmap bitmap = null;
BitmapDrawable drawable = null;
// Wait here if work is paused and the task is not cancelled
synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (Exception e) {
}
}
}
if (mImageCache != null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
//從外部儲存去取圖片
bitmap = mImageCache.getBitmapFromDiskCache(dataString);
}
if (bitmap == null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
//如果外存中也沒有圖片,那麼就去伺服器上下載
bitmap = processBitmap(mData);
}
if (bitmap != null) {
if (Utils.hasHoneycomb()) {
// Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
drawable = new BitmapDrawable(mResources, bitmap);
} else {
// Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
// which will recycle automagically
drawable = new RecyclingBitmapDrawable(mResources, bitmap);
}
if (mImageCache != null) {
//把下載的圖片快取到記憶體和外存中去
mImageCache.addBitmapToCache(dataString, drawable);
}
}
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable value) {
boolean success = false;
// if cancel was called on this task or the "exit early" flag is set then we're done
if (isCancelled() || mExitTasksEarly) {
value = null;
}
final ImageView imageView = getAttachedImageView();
if (value != null && imageView != null) {
success = true;
//給ImageView設定圖片
setImageDrawable(imageView, value);
}
if (mOnImageLoadedListener != null) {
//執行回撥
mOnImageLoadedListener.onImageLoaded(success);
}
}
@Override
protected void onCancelled(BitmapDrawable value) {
super.onCancelled(value);
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();
}
}
private ImageView getAttachedImageView() {
final ImageView imageView = imageViewWeakReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask) {
return imageView;
}
return null;
}
}
複製程式碼
BitmapWorkerTask 的工作很明確,首先開啟一個新執行緒,先確認外存中是否有相應快取圖片,如果有,直接拿出來使用並返回結果;如果沒有,去伺服器上下載該圖片,並將該圖片快取到記憶體和外存中去,最後onPostExecute()中載入圖片並執行結果回撥。
#####ImageResizer.java (extends ImageWorker):
public class ImageResizer extends ImageWorker {
protected int mImageWidth;
protected int mImageHeight;
protected ImageResizer(Context context, int imageWidth, int imageHeight) {
super(context);
setImageSize(imageWidth, imageHeight);
}
//設定圖片的寬高
private void setImageSize(int imageWidth, int imageHeight) {
this.mImageWidth = imageWidth;
this.mImageHeight = imageHeight;
}
@Override
protected Bitmap processBitmap(Object data) {
//覆寫父類的方法,載入本地圖片
return processBitmap(Integer.parseInt(String.valueOf(data)));
}
private Bitmap processBitmap(int resId) {
return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
mImageHeight, getImageCache());
}
//從本地resource中解碼bitmap並取樣源bitmap至指定大小的寬高
public static Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// If we're running on Honeycomb or newer, try to use inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
//從本地檔案中解碼bitmap並取樣源bitmap至指定大小的寬高
public static Bitmap decodeSampledBitmapFromFile(String filename,
int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filename, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// If we're running on Honeycomb or newer, try to use inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filename, options);
}
//從檔案輸入流中解碼bitmap並取樣源bitmap至指定大小的寬高
public static Bitmap decodeSampledBitmapFromDescriptor(
FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// If we're running on Honeycomb or newer, try to use inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
//計算取樣率大小
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
//取樣率
int inSampleSize = 1;
//判斷圖片的寬高之一是否大於所需寬高
if (height > reqHeight || width > reqWidth) {
//圖片寬高之一大於所需寬高,那麼寬高都除以2
final int halfHeight = height / 2;
final int halfWidth = width / 2;
//迴圈寬高除以取樣率的值,如果大於所需寬高,取樣率inSampleSize翻倍
//PS:取樣率每變大2倍,圖片大小縮小至原來大小的1/4
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
//下面邏輯是為了處理一些不規則圖片,如一張圖片的height特別大,遠大於width,此時圖片的大小很大,
//此時加到記憶體中去依然不合適,算出總此時圖片總畫素大小totalPixels
long totalPixels = width * height / inSampleSize;
//如果圖片總畫素大於所需總畫素的2倍,繼續擴大采樣率
final long totalReqPixelsCap = reqHeight * reqWidth * 2;
while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
//返回最終的取樣率inSampleSize
return inSampleSize;
}
//如果SDK>11,通過設定options.inBitmap嘗試複用ImageCache中mReusableBitmaps的快取bitmap,
//這樣可以避免頻繁的申請記憶體和銷燬記憶體
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
// inBitmap only works with mutable bitmaps(可變的點陣圖) so force the decoder to
// return mutable bitmaps.
options.inMutable = true;
if (cache != null) {
// Try and find a bitmap to use for inBitmap
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}
}
}
複製程式碼
ImageResizer繼承自ImageWorker,ImageResizer類可以調整bitmap到指定寬高,主要用到了BitmapFactory.Options這個內部類來處理載入圖片(後面介紹),當本地圖片很大時,不能直接載入到記憶體中去,需要經過ImageResizer處理一下。下面介紹一下BitmapFactory.Options的常用方法:
BitmapFactory用來解碼建立一個Bitmap,Options是BitmapFactory的一個靜態內部類,是解碼Bitmap時的各種引數控制:
public class BitmapFactory {
private static final int DECODE_BUFFER_SIZE = 16 * 1024;
public static class Options {
public boolean inJustDecodeBounds;
public int inSampleSize;
public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
public int outWidth;
public int outHeight;
public String outMimeType;
public boolean inMutable;
public Bitmap inBitmap;
............其他引數............
}
}
複製程式碼
-
inJustDecodeBounds:如果設定為true,則不會把bitmap載入到記憶體中,但是依然可以得到bitmap的寬高,當需要知道bitmap的寬高而又不想把bitmap載入到記憶體中去的時候,就可以通過設定這個屬性來實現。
-
inSampleSize:取樣率,如果value > 1,解碼器將會按(1/inSampleSize)的比例減小寬高,然後返回一個壓縮後的圖片到記憶體中,例如: inSampleSize == 4,返回一個寬高都是原圖片1/4的壓縮圖片,圖片被壓縮到原來的1/16,;如果inSampleSize的value <= 1,都會被當成1處理,即保持原樣。
-
inPreferredConfig :設定色彩模式,預設值是ARGB_8888,在這個模式下,一個畫素點佔用4bytes空間;如果對透明度不做要求的話,可以採用RGB_565模式,這個模式下一個畫素點佔用2bytes。
-
outWidth:bitmap的寬度,如果inJustDecodeBounds=true,outWidth將是原圖的寬度;如果inJustDecodeBounds=false,outWidth將是經過縮放後的bitmap的寬度。如果解碼過程中發生錯誤,將返回-1。
-
outHeight:bitmap的高度,如果inJustDecodeBounds=true,outHeight將是原圖的高度;如果inJustDecodeBounds=false,outHeight將是經過縮放後的bitmap的高度。如果解碼過程中發生錯誤,將返回-1。
-
outMimeType:獲取圖片的型別,如果獲取不到圖片型別或者解碼發生錯誤,outMimeType將會被設定成null。
-
inMutable:設定Bitmap是否可更改,如在Bitmap上進行額外操作
-
inBitmap:解析Bitmap的時候重用其他Bitmap的記憶體,避免大塊記憶體的申請和銷燬,使用inBitmap時inMutable必須設定為true
#####ImageResizer .java:
public class ImageFetcher extends ImageResizer {
..............省略部分程式碼..............
private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
private static final String HTTP_CACHE_DIR = "http";
private static final int IO_BUFFER_SIZE = 8 * 1024;
private DiskLruCache mHttpDiskCache;
private File mHttpCacheDir;
public ImageFetcher(Context context, int imageSize) {
super(context, imageSize);
init(context);
}
//初始化外存快取
private void init(Context context) {
checkConnection(context);
mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
}
//檢查網路狀態
private void checkConnection(Context context) {
final ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
Toast.makeText(context, "no_network_connection_toast", Toast.LENGTH_LONG).show();
Log.e(TAG, "checkConnection - no connection found");
}
}
@Override
protected Bitmap processBitmap(Object data) {
return processBitmap(String.valueOf(data));
}
private Bitmap processBitmap(String data) {
final String key = ImageCache.hashKeyForDisk(data);
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapshot;
synchronized (mHttpDiskCacheLock) {
// Wait for disk cache to initialize
while (mHttpDiskCacheStarting) {
try {
mHttpDiskCacheLock.wait();
} catch (InterruptedException e) {
}
}
if (mHttpDiskCache != null) {
try {
//在外存中讀取圖片
snapshot = mHttpDiskCache.get(key);
if (snapshot == null) {
//外存中沒有,則開始去伺服器上下載
DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
if (editor != null) {
if (downloadUrlToStream(data,
editor.newOutputStream(DISK_CACHE_INDEX))) {
//寫入外存快取,這裡儲存的是原始圖
editor.commit();
} else {
editor.abort();
}
}
//從外存中讀取出該圖片
snapshot = mHttpDiskCache.get(key);
}
if (snapshot != null) {
fileInputStream =
(FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
fileDescriptor = fileInputStream.getFD();
}
} catch (IOException e) {
Log.e(TAG, "processBitmap - " + e);
} catch (IllegalStateException e) {
Log.e(TAG, "processBitmap - " + e);
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
}
}
Bitmap bitmap = null;
if (fileDescriptor != null) {
//將取出來的圖片壓縮到指定大小並在後面的邏輯中儲存,這裡儲存的是縮圖
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
mImageHeight, getImageCache());
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
return bitmap;
}
//根據URL去下載圖片
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
disableConnectionReuseIfNecessary();
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(), IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (Exception e) {
Log.e(TAG, "Error in downloadBitmap - " + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
}
}
return false;
}
}
複製程式碼
ImageFetcher繼承自ImageResizer ,ImageFetcher根據URL從伺服器中得到圖片並分別把原始圖和縮圖快取到外存中,這裡設定的兩個快取目錄: /storage/emulated/0/Android/data/package_name/http (用來存放原始圖) /storage/emulated/0/Android/data/package_name/thumbs (用來存放縮圖) 看下面兩張圖:
原始圖大小基本都在100Kb以上,而縮圖基本都在10Kb以內~最後再貼下原始碼地址:LruCache、DiskLruCache實現圖片快取
好了,本文暫時到這裡,文章有點長了,後續會加上檢視大圖功能~