package xxx.xxx.xxx.xxx;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.LruCache;
import android.widget.ImageView;
import com.android.volley.toolbox.ImageLoader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class BitmapCache implements ImageLoader.ImageCache {
private static final int DISK_CACHE_SIZE = 1024 *1024 * 50;
private static final int DISK_CACHE_INDEX = 0;
private LruCache<String, Bitmap> lruCache;
private DiskLruCache diskLruCache;
public BitmapCache(Context context) {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
lruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
File diskCacheDir = getDiskCacheDir(context, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
diskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public Bitmap getBitmap(String key) {
Bitmap bitmap = lruCache.get(key);
if (bitmap == null) {
try {
DiskLruCache.Snapshot snapshot = diskLruCache.get(hashKeyForDisk(key));
if (snapshot != null) {
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
if (bitmap != null) {
lruCache.put(key, bitmap);
}
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
public void putBitmap(String key, Bitmap bitmap) {
lruCache.put(key, bitmap);
try {
DiskLruCache.Editor editor = diskLruCache.edit(hashKeyForDisk(key));
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if (bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
editor.commit();
} else {
editor.abort();
}
diskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private File getDiskCacheDir(Context context, String uniqueName) {
final String cachePath;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private long getUsableSpace(File path) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return stats.getBlockSizeLong() * stats.getAvailableBlocksLong();
}
private String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
public static String getCacheKey(String url, ImageView imageView) {
return "#W" + imageView.getWidth()
+ "#H" + imageView.getHeight()
+ "#S" + imageView.getScaleType().ordinal()
+ url;
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
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);
}
}
至於我們爲什麼需要圖片緩存,以及什麼是內存緩存,什麼是磁盤緩存,什麼是Volley這些東西我就不想寫了,我附上幾篇郭神的博客,可以用作參考。
Android Volley完全解析(二),使用Volley加載網絡圖片
Android高效加載大圖、多圖解決方案,有效避免程序OOM
Android DiskLruCache完全解析,硬盤緩存的最佳方案
除了這些博客外,《Android開發藝術探索》一書第十二章對圖片緩存這一塊也講的比較詳細。
還有題外話就是以上的博客和書中所給的DiskLruCache源碼的下載地址,下載下來的源碼是不能直接編譯成功的,還需要使用者自己做一些修改,比較麻煩。但是Android Developer官網給出的源碼是直接可用的,我這裏直接給出地址(需要科學上網,大家都懂的):
然後簡單說一下我的代碼,如果使用的話,直接複製粘貼就行了,不過最上面類的包名自己要寫下,再一個就是如果DiskLruCache和本類如果不在一個包,還需要import一下。
本類實現了Volley的ImageCache接口,絕大部分實現細節都來自以上教程的整合。
重寫接口的getBitmap()方法,大致邏輯爲,首先從內存緩存中取出bitmap,如果沒有,即當返回null的時候,從磁盤緩存讀取,如果從硬盤成功讀取,根據LRU算法的近期最少使用的原則,我們應該吧從磁盤讀取的bitmap重新加入內存緩存。
重寫接口的putBitmap()方法,大致邏輯爲,拿到從網絡加載來的bitmap以後,先寫入內存緩存,再寫入磁盤緩存。與以上教程不同的地方爲,教程中是把網絡請求來的數據流直接寫入輸出流,但是由於我們這個是Volley的擴展,所以我們要考慮使用Volley的情況,NetworkImageView會自動壓縮bitmap,所以我們在使用的時候不必考慮壓縮的問題,其次,由於Volley已經做過處理,所以我們直接會從此回調方法中得到bitmap,因此就不是直接從網絡數據流寫入輸出流了,而是把bitmap寫入輸出流,這裏使用了bitmap的compress()方法。
關於緩存使用的鍵值問題:Volley的鍵值不僅包含url,還包含寬,高,ScaleType三個信息。原本的鍵值在內存緩存中使用時沒有任何問題的,但是使用在磁盤緩存中會出現問題,因爲URL中包含的 “ / ”這個斜槓符號會對存儲路徑造成影響,導致目錄出現問題,所以即使Volley有自己的鍵值生成的方法我們在使用磁盤緩存時仍需把原鍵MD5化。關於Volley的鍵是怎麼生成的,我在代碼中的最下面三個靜態方法中的第一個已經給出。後面兩個靜態方法是從Resource中使用圖片的時候進行壓縮的兩個工具方法,上面給出的博客中有介紹。
此外,bytesToHexString()方法中的for循環改爲了更有逼格的foreach循環(好吧,這沒什麼值得說的)。