package com.emop.client.cache;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.support.v4.util.LruCache;
import android.util.Log;
import com.emop.client.Constants;
import com.jakewharton.DiskLruCache;
import com.jakewharton.MD5;
public class ImageCache {
public static DiskLruCache diskCache = null;
private File cacheRoot = null;
private LruCache<String, Bitmap> memCache;
private LruCache<String, Lock> lockCache;
public ImageCache(File root, final int cacheSize){
this.cacheRoot = root;
memCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
int size = bitmap.getHeight() * bitmap.getWidth() * 4;
return size;
}
};
lockCache = new LruCache<String, Lock>(200) {
@Override
protected int sizeOf(String key, Lock obj) {
return 1;
}
};
if(diskCache == null && root != null){
try {
Log.d(Constants.TAG_EMOP, "create LRU cache in:" + root.getAbsolutePath());
diskCache = DiskLruCache.open(root, 1, 1, 1024 * 1024 * 64);
} catch (IOException e) {
Log.w(Constants.TAG_EMOP, "open disk cache error:" + e.toString(), e);
}
}
}
public void cleanUpDiskCache(){
if(diskCache != null){
File f = diskCache.getDirectory();
try {
memCache.evictAll();
diskCache.delete();
diskCache = DiskLruCache.open(f, 1, 1, 1024 * 1024 * 64);
} catch (IOException e) {
Log.w(Constants.TAG_EMOP, "open disk cache error:" + e.toString(), e);
}
}
}
//public ImageCache(Context )
public Bitmap get(String url, int minWdith, boolean scaled){
return get(url, minWdith, scaled, true);
}
public Bitmap get(String url, int minWdith, boolean scaled, boolean autoLoad){
if(scaled){
url = getScaledUrl(url, minWdith);
}
try {
return get(new URL(url), autoLoad, minWdith);
} catch (MalformedURLException e) {
Log.e("tag", "error url:" + url.toString(), e);
}
return null;
}
public Bitmap get(URL url, boolean autoLoad, int minWidth){
String ck = url.toString() + "!" + minWidth;
//避免多个线程重复的加载一个文件。
Lock o = getLock(ck);
Bitmap img = null;
img = memCache.get(ck);
if(img != null && !img.isRecycled()){
//Log.d("xxx", "load from memcache:" + ck);
}else if(autoLoad){
synchronized(o){
img = memCache.get(ck);
if(img == null || img.isRecycled()){
img = loadFormURL(url, minWidth);
if(img != null){
memCache.put(ck, img);
}
}
}
}else {
img = null;
}
return img;
}
public Bitmap loadFormURL(URL url, int minWidth){
Bitmap img = null;
try{
img = readFromDiskCache(url);
if(img == null){
File cacheFile = getCached(url, minWidth);
if(cacheFile != null && cacheFile.isFile()){
//Log.d("tag", "load image from disk cache:" + cacheFile.getAbsolutePath());
img = BitmapFactory.decodeFile(cacheFile.getAbsolutePath());
}else {
//Log.d("tag", "from web:" + url.toString());
img = loadUrl(url, minWidth);
boolean isWrited = false;
if(img != null){
isWrited = writeToDiskCache(url, img);
}
if(!isWrited && cacheFile != null && img != null){
saveAsFile(img, cacheFile);
}
}
}
}catch(OutOfMemoryError e){
Log.w("error", "OutOfMemoryError in load image:" + url.toString());
memCache.evictAll();
if(diskCache != null){
try {
diskCache.flush();
} catch (IOException e1) {
}
}
}
return img;
}
protected Bitmap readFromDiskCache(URL url){
Bitmap bm = null;
if(diskCache != null){
//diskCache.flush()
String cacheKey = MD5.encode(url.toString());
try {
DiskLruCache.Snapshot snapshot = diskCache.get(cacheKey);
if(snapshot != null){
InputStream in = snapshot.getInputStream(0);
bm = BitmapFactory.decodeStream(in);
in.close();
snapshot.close();
snapshot = null;
//Log.d(Constants.TAG_EMOP, "read from disk key:" + cacheKey + ", url:" + url.toString());
}
} catch (IOException e) {
Log.d(Constants.TAG_EMOP, "read disk lru cache error:" + e.toString(), e);
}
}else {
Log.w(Constants.TAG_EMOP, "read disk lru cache is null");
}
return bm;
}
protected boolean writeToDiskCache(URL url, Bitmap bitmap){
boolean isOk = false;
if(diskCache != null){
String cacheKey = MD5.encode(url.toString());
try {
DiskLruCache.Editor editor = diskCache.edit(cacheKey);
if(editor != null){
OutputStream out = editor.newOutputStream(0);
bitmap.compress(CompressFormat.PNG, 95, out);
out.close();
editor.commit();
//Log.d(Constants.TAG_EMOP, "write to disk key:" + cacheKey + ", url:" + url.toString());
isOk = true;
editor = null;
}
} catch (IOException e) {
isOk = false;
Log.d(Constants.TAG_EMOP, "write disk lru cache error:" + e.toString(), e);
}
}else {
Log.w(Constants.TAG_EMOP, "read disk lru cache is null");
}
return isOk;
}
public File getCachedFile(String picPath, int minWidth){
Bitmap img = null;
File cacheFile = null;
String path = getScaledUrl(picPath, minWidth);
try{
URL url = new URL(path);
cacheFile = getCached(url, minWidth);
if(cacheFile != null && cacheFile.isFile()){
}else {
Log.d("tag", "from web:" + url.toString());
img = loadUrl(url, minWidth);
if(cacheFile != null && img != null){
saveAsFile(img, cacheFile);
}
}
}catch(Exception e){
Log.e("error", "Error in load image:" + picPath, e);
}
return cacheFile;
}
public synchronized Lock getLock(String url){
Lock o = lockCache.get(url);
if(o == null){
o = new ReentrantLock();
lockCache.put(url, o);
}
return o;
}
protected File getCached(URL url, int minWidth){
File cache = null;
if(url.getProtocol().toLowerCase().startsWith("http")){
cache = new File(cacheRoot, url.getPath() + "_" + minWidth);
}
return cache;
}
protected void saveAsFile(Bitmap bitmap, File path){
File dir = path.getParentFile();
if(!dir.isDirectory()){
dir.mkdirs();
}
FileOutputStream fOut = null;
try{
Log.d("tag", "save to cache:" + path.getAbsolutePath());
if(path.createNewFile()){
fOut = new FileOutputStream(path);
bitmap.compress(CompressFormat.PNG, 95, fOut);
}
}catch (IOException e) {
Log.e("tag", e.toString());
}finally{
if(fOut != null){
try {
fOut.close();
} catch (IOException e) {
Log.e("tag", e.toString());
}
}
}
}
protected Bitmap loadFromFile(String path, int width){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
options.inSampleSize = computeSampleSize(options, width,
width * width * 3);
Log.i("tag", "load picture, orgWidth:" + options.outWidth + ", orgHeight:" + options.outHeight + ", sample:" + options.inSampleSize);
options.inJustDecodeBounds = false;
options.inInputShareable = true;
options.inPurgeable = true;
bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
protected Bitmap loadUrl(URL url, int width){
Bitmap bitmap = null;
try{
byte[] buffer = loadUrlRaw(url);
if(buffer != null && buffer.length > 100){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
options.inSampleSize = computeSampleSize(options, width,
width * width * 3);
Log.i("tag", "load picture, orgWidth:" + options.outWidth + ", orgHeight:" + options.outHeight + ", sample:" + options.inSampleSize);
options.inJustDecodeBounds = false;
options.inInputShareable = true;
options.inPurgeable = true;
bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
}
}catch(Throwable e){
Log.e("tag", "error log image:" + url.toString(), e);
}finally{
}
return bitmap;
}
protected byte[] loadUrlRaw(URL url){
ByteArrayOutputStream out = new ByteArrayOutputStream(1024 * 100);
BufferedInputStream bis = null;
byte[] isBuffer = new byte[1024 * 4];
int length = 0;
int contentLength = 0;
URLConnection conn = null;
try{
conn = url.openConnection();
contentLength = conn.getContentLength();
conn.setReadTimeout(1000 * 5);
bis = new BufferedInputStream(conn.getInputStream(), 1024 * 4);
for(int len = bis.read(isBuffer); len != -1; len = bis.read(isBuffer)){
out.write(isBuffer, 0, len);
length += len;
if(length > 1024 * 512){
Log.w("tag", "The file is too large, It be dropped. url:" + url.toString());
break;
}
}
if(contentLength <= 0){
Log.w("tag", "The URL have not content length header");
}
if(contentLength <= 0 || contentLength == length){
isBuffer = out.toByteArray();
}else {
isBuffer = null;
}
}catch(Exception e){
Log.e("tag", "load url error:" + url.toString(), e);
}finally{
if(bis != null){
try {
bis.close();
} catch (IOException e) {
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
}
}
}
return isBuffer;
}
/**
* 根据需要显示的图片大小,选择一个宽度对接近的缩放尺寸。节约网络带宽。
* @param u
* @param minWidth
* @return
*/
private String getScaledUrl(String u, int minWidth){
if(u.indexOf('!')> 0){
return u;
}
String scaleType = "";
if(u.indexOf("mobile01") > 0){
scaleType = getMobile01Scale(minWidth);
}else if(u.indexOf("tdcms") > 0){
scaleType = getTdcmsScale(minWidth);
}
if(scaleType != null && scaleType.length() > 0){
return u + "!" + scaleType;
}
return u;
}
private String getTdcmsScale(int w){
if(w <= 165){
return "small";
}else if(w <= 250){
return "190";
}else if(w <= 440){
return "weibo";
}
return "weibo";
}
private String getMobile01Scale(int w){
if(w <= 120){
return "90";
}else if(w <= 165){
return "small";
}else if(w <= 230){
return "190";
}else if(w <= 540){
return "weibo";
}else if(w <= 640){
return "weibo2";
}
return "xhdpi";
}
public static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength,
maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math
.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(
Math.floor(w / minSideLength), Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
}