/**
* =====================================================================
*
* @file ImageCache.java
* @Module Name com.nj.common.utils.cache
* @author benz
* @OS version 1.0
* @Product type: JoySee
* @date 2013-12-5
* @brief This file is the http **** implementation.
* @This file is responsible by ANDROID TEAM.
* @Comments: ===================================================================== Revision
* History:
*
* Modification Tracking
*
* Author Date OS version Reason ---------- ------------ ------------- ----------- benz
* 2013-12-5 1.0 Check for NULL, 0 h/w
* =====================================================================
**/
//
package com.letv.commonjar.http;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Environment;
import android.os.StatFs;
import android.support.v4.util.LruCache;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Iterator;
import com.letv.commonjar.CLog;
class ImageCache {
private static final String TAG = CLog.makeTag(ImageCache.class);
private CacheParams mJCacheParams;
private DiskLruCache mDiskLruCache;
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_INDEX = 0;
private final Object mDiskCacheLock = new Object();
private final Object mHttpDiskCacheLock = new Object();
private LruCache<String, BitmapDrawable> mMemoryCache;
// 存放被回收的bitmap
private HashSet<SoftReference<Bitmap>> mReusableBitmaps;
private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
ImageCache(Context ctx, CacheParams params) {
this.mJCacheParams = params;
init(params);
}
private void init(CacheParams params) {
if (mJCacheParams.isUseMemoryCache()) {
mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
mMemoryCache = new LruCache<String, BitmapDrawable>(mJCacheParams.getMemorySize()) {
@Override
protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) {
/** 硬引用缓存区满,将一个最不经常使用的OldValue推入到软引用缓存区 */
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;
}
};
}
}
/**
* this should not be executed on the main/UI thread.
*/
public void initDiskCache() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = mJCacheParams.getDisCacheDirFile();
if (mJCacheParams.isUseDiskCache() && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mJCacheParams.getDiskSize()) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, mJCacheParams.getDiskSize());
} catch (IOException e) {
CLog.e(TAG, "DiskCache open Exception");
}
} else {
CLog.e(TAG, "Disk space is not enough");
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
}
public void addBitmapToCache(String data, BitmapDrawable value) {
if (data == null || value == null) {
return;
}
if (mMemoryCache != null) {
mMemoryCache.put(data, value);
}
synchronized (mDiskCacheLock) {
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(DEFAULT_COMPRESS_FORMAT, mJCacheParams.getJpgSaveQuality(), out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {}
}
}
}
}
public BitmapDrawable getBitmapFromMemCache(String key) {
BitmapDrawable memValue = null;
if (mMemoryCache != null) {
memValue = mMemoryCache.get(key);
}
return memValue;
}
public Bitmap getBitmapFromDiskCache(String data, int[] size) {
final String key = hashKeyForDisk(data);
Bitmap bitmap = null;
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException 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();
bitmap = decodeBitmapFromDescriptor(fd, size);
}
}
} catch (IOException e) {
CLog.e(TAG, "getBitmapFromDiskCache e=" + e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {}
}
}
return bitmap;
}
}
/**
* @param options - BitmapFactory.Options with out* options populated
* @return Bitmap that case be used for inBitmap
*/
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
Bitmap item = null;
while (iterator.hasNext()) {
SoftReference<Bitmap> softReference = iterator.next();
item = softReference.get();
if (null != item && item.isMutable()) {
/** Check to see it the item can be used for inBitmap */
if (canUseForInBitmap(item, options)) {
bitmap = item;
iterator.remove();
break;
}
} else {
iterator.remove();
}
}
}
return bitmap;
}
/**
* this should not be executed on the main/UI thread.
*/
public void clearCache() {
CLog.d(TAG, " - cache cleared - ");
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
} catch (IOException e) {}
mDiskLruCache = null;
// initDiskCache();
}
}
}
/**
* this should not be executed on the main/UI thread.
*/
public void flush() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
CLog.d(TAG, " - Disk cache flushed - flush");
} catch (IOException e) {
CLog.d(TAG, "flush - " + e);
}
}
}
}
/**
* this should not be executed on the main/UI thread.
*/
public void close() {
log(TAG, "Disk cache closed");
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.close();
mDiskLruCache = null;
} catch (IOException e) {}
}
}
}
private static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
return candidate.getWidth() == width && candidate.getHeight() == height;
}
public static File getDiskCacheDir(Context context, String uniqueName) {
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !isExternalStorageRemovable()
? getExternalCacheDir(context).getPath()
: context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable for using as a disk
* filename.
*/
public static 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 static String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* Get the size in bytes of a bitmap in a BitmapDrawable.
*
* @param value
* @return size in bytes
*/
public static int getBitmapSize(BitmapDrawable value) {
Bitmap bitmap = value.getBitmap();
if (CacheUtils.hasHoneycombMR1()) {
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**
* Check if external storage is built-in or removable.
*/
public static boolean isExternalStorageRemovable() {
if (CacheUtils.hasGingerbread()) {
return Environment.isExternalStorageRemovable();
}
return true;
}
/**
* Get the external app cache directory.
*
* @param context The context to use
* @return The external cache dir
*/
public static File getExternalCacheDir(Context context) {
if (CacheUtils.hasFroyo()) {
return context.getExternalCacheDir();
}
// Before Froyo we need to construct the external cache dir ourselves
final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
/**
* 检查可用空间
*/
public static long getUsableSpace(File path) {
if (CacheUtils.hasGingerbread()) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
protected Bitmap processBitmap(String bitmapUrl, int[] size) {
final String key = ImageCache.hashKeyForDisk(bitmapUrl);
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
Bitmap bitmap = null;
DiskLruCache.Snapshot snapshot;
synchronized (mHttpDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mHttpDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
try {
snapshot = mDiskLruCache.get(key);
if (null == snapshot) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
if (downloadUrlToStream(bitmapUrl, editor.newOutputStream(DISK_CACHE_INDEX))) {
editor.commit();
} else {
editor.abort();
}
}
snapshot = mDiskLruCache.get(key);
}
if (null != snapshot) {
fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
fileDescriptor = fileInputStream.getFD();
}
} catch (IOException e) {
CLog.e(TAG, "downBitmap - " + e);
} catch (IllegalStateException e) {
CLog.e(TAG, "downBitmap - " + e);
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {}
}
}
}
}
if (null != fileDescriptor) {
bitmap = decodeBitmapFromDescriptor(fileDescriptor, size);
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {}
}
return bitmap;
}
/**
* decode bitmap from FileDescriptor
*
* @param descriptor
*/
private Bitmap decodeBitmapFromDescriptor(FileDescriptor descriptor, int[] size) {
if (size == null) {
return BitmapFactory.decodeFileDescriptor(descriptor);
} else {
return decodeBitmapFromDescriptorByWH(descriptor, size[0], size[1]);
}
}
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param descriptor
* @param reqWidth
* @param reqHeight
* @return
*/
private Bitmap decodeBitmapFromDescriptorByWH(FileDescriptor descriptor, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(descriptor, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
addInBitmapOptions(options);
return zoomBitmap(BitmapFactory.decodeFileDescriptor(descriptor, null, options), reqWidth, reqHeight);
}
private void addInBitmapOptions(BitmapFactory.Options options) {
options.inMutable = true;
Bitmap inBitmap = getBitmapFromReusableSet(options);
if (null != inBitmap) {
options.inBitmap = inBitmap;
}
}
/**
* Calculate an inSampleSize
*/
public 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;
final float totalPixels = width * height;
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
private Bitmap zoomBitmap(Bitmap bitmap, int width, int height) {
if (bitmap == null) {
return null;
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
if (w <= 0 || h <= 0) {
return bitmap;
}
Matrix matrix = new Matrix();
float scaleWidth = ((float) width / w);
float scaleHeight = ((float) height / h);
matrix.postScale(scaleWidth, scaleHeight);
Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
bitmap.recycle();
return newbmp;
}
public boolean downloadUrlToStream(String imagePath, OutputStream outputStream) {
boolean ret = false;
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(imagePath);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b = -1;
while ((b = in.read()) != -1) {
out.write(b);
}
ret = true;
} catch (final IOException e) {
log(TAG, "- Error in download img - " + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {}
}
return ret;
}
private static void log(String tag, String msg) {
if (CLog.MANUAL_POWER) {
CLog.d(tag, msg);
}
}
}