/* * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ch_linghu.fanfoudroid.app; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.util.Log; import com.ch_linghu.fanfoudroid.AppParam; import com.ch_linghu.fanfoudroid.http.HttpException; import com.ch_linghu.fanfoudroid.http.Response; /** * Manages retrieval and storage of icon images. Use the put method to download * and store images. Use the get method to retrieve images from the manager. */ public class ImageManager implements ImageCache { private static final String TAG = "ImageManager"; // 饭否目前最大宽度支持596px, 超过则同比缩小 // 最大高度为1192px, 超过从中截取 public static final int DEFAULT_COMPRESS_QUALITY = 90; public static final int IMAGE_MAX_WIDTH = 596; public static final int IMAGE_MAX_HEIGHT = 1192; private Context mContext; // In memory cache. private Map<String, SoftReference<Bitmap>> mCache; // MD5 hasher. private MessageDigest mDigest; public static Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = Bitmap .createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } public ImageManager(Context context) { mContext = context; mCache = new HashMap<String, SoftReference<Bitmap>>(); try { mDigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { // This shouldn't happen. throw new RuntimeException("No MD5 algorithm."); } } public void setContext(Context context) { mContext = context; } private String getHashString(MessageDigest digest) { StringBuilder builder = new StringBuilder(); for (byte b : digest.digest()) { builder.append(Integer.toHexString((b >> 4) & 0xf)); builder.append(Integer.toHexString(b & 0xf)); } return builder.toString(); } // MD5 hases are used to generate filenames based off a URL. private String getMd5(String url) { mDigest.update(url.getBytes()); return getHashString(mDigest); } // Looks to see if an image is in the file system. private Bitmap lookupFile(String url) { String hashedUrl = getMd5(url); FileInputStream fis = null; try { fis = mContext.openFileInput(hashedUrl); return BitmapFactory.decodeStream(fis); } catch (FileNotFoundException e) { // Not there. return null; } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // Ignore. } } } } /** * Downloads a file * * @param url * @return * @throws HttpException */ public Bitmap downloadImage(String url) throws HttpException { Log.d(TAG, "Fetching image: " + url); Response res = AppParam.mApi.getHttpClient().get(url); return BitmapFactory.decodeStream(new BufferedInputStream(res .asStream())); } public Bitmap downloadImage2(String url) throws HttpException { Log.d(TAG, "[NEW]Fetching image: " + url); final Response res = AppParam.mApi.getHttpClient().get(url); String file = writeToFile(res.asStream(), getMd5(url)); return BitmapFactory.decodeFile(file); } /** * 下载远程图片 -> 转换为Bitmap -> 写入缓存器. * * @param url * @param quality * image quality 1~100 * @throws HttpException */ public void put(String url, int quality, boolean forceOverride) throws HttpException { if (!forceOverride && contains(url)) { // Image already exists. return; // TODO: write to file if not present. } Bitmap bitmap = downloadImage(url); if (bitmap != null) { put(url, bitmap, quality); // file cache } else { Log.w(TAG, "Retrieved bitmap is null."); } } /** * 重载 put(String url, int quality) * * @param url * @throws HttpException */ public void put(String url) throws HttpException { put(url, DEFAULT_COMPRESS_QUALITY, false); } /** * 将本地File -> 转换为Bitmap -> 写入缓存器. 如果图片大小超过MAX_WIDTH/MAX_HEIGHT, 则将会对图片缩放. * * @param file * @param quality * 图片质量(0~100) * @param forceOverride * @throws IOException */ public void put(File file, int quality, boolean forceOverride) throws IOException { if (!file.exists()) { Log.w(TAG, file.getName() + " is not exists."); return; } if (!forceOverride && contains(file.getPath())) { // Image already exists. Log.d(TAG, file.getName() + " is exists"); return; // TODO: write to file if not present. } Bitmap bitmap = BitmapFactory.decodeFile(file.getPath()); // bitmap = resizeBitmap(bitmap, MAX_WIDTH, MAX_HEIGHT); if (bitmap == null) { Log.w(TAG, "Retrieved bitmap is null."); } else { put(file.getPath(), bitmap, quality); } } /** * 将Bitmap写入缓存器. * * @param filePath * file path * @param bitmap * @param quality * 1~100 */ public void put(String file, Bitmap bitmap, int quality) { synchronized (this) { mCache.put(file, new SoftReference<Bitmap>(bitmap)); } writeFile(file, bitmap, quality); } /** * 重载 put(String file, Bitmap bitmap, int quality) * * @param filePath * file path * @param bitmap * @param quality * 1~100 */ @Override public void put(String file, Bitmap bitmap) { put(file, bitmap, DEFAULT_COMPRESS_QUALITY); } /** * 将Bitmap写入本地缓存文件. * * @param file * URL/PATH * @param bitmap * @param quality */ private void writeFile(String file, Bitmap bitmap, int quality) { if (bitmap == null) { Log.w(TAG, "Can't write file. Bitmap is null."); return; } BufferedOutputStream bos = null; try { String hashedUrl = getMd5(file); bos = new BufferedOutputStream(mContext.openFileOutput(hashedUrl, Context.MODE_PRIVATE)); bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos); // PNG Log.d(TAG, "Writing file: " + file); } catch (IOException ioe) { Log.e(TAG, ioe.getMessage()); } finally { try { if (bos != null) { bitmap.recycle(); bos.flush(); bos.close(); } // bitmap.recycle(); } catch (IOException e) { Log.e(TAG, "Could not close file."); } } } private String writeToFile(InputStream is, String filename) { Log.d("LDS", "new write to file"); BufferedInputStream in = null; BufferedOutputStream out = null; try { in = new BufferedInputStream(is); out = new BufferedOutputStream(mContext.openFileOutput(filename, Context.MODE_PRIVATE)); byte[] buffer = new byte[1024]; int l; while ((l = in.read(buffer)) != -1) { out.write(buffer, 0, l); } } catch (IOException ioe) { ioe.printStackTrace(); } finally { try { if (in != null) in.close(); if (out != null) { Log.d("LDS", "new write to file to -> " + filename); out.flush(); out.close(); } } catch (IOException ioe) { ioe.printStackTrace(); } } return mContext.getFilesDir() + "/" + filename; } public Bitmap get(File file) { return get(file.getPath()); } /** * 判断缓存着中是否存在该文件对应的bitmap */ public boolean isContains(String file) { return mCache.containsKey(file); } /** * 获得指定file/URL对应的Bitmap,首先找本地文件,如果有直接使用,否则去网上获取 * * @param file * file URL/file PATH * @param bitmap * @param quality * @throws HttpException */ public Bitmap safeGet(String file) throws HttpException { Bitmap bitmap = lookupFile(file); // first try file. if (bitmap != null) { synchronized (this) { // memory cache mCache.put(file, new SoftReference<Bitmap>(bitmap)); } return bitmap; } else { // get from web String url = file; bitmap = downloadImage2(url); // 注释掉以测试新的写入文件方法 // put(file, bitmap); // file Cache return bitmap; } } /** * 从缓存器中读取文件 * * @param file * file URL/file PATH * @param bitmap * @param quality */ public Bitmap get(String file) { SoftReference<Bitmap> ref; Bitmap bitmap; // Look in memory first. synchronized (this) { ref = mCache.get(file); } if (ref != null) { bitmap = ref.get(); if (bitmap != null) { return bitmap; } } // Now try file. bitmap = lookupFile(file); if (bitmap != null) { synchronized (this) { mCache.put(file, new SoftReference<Bitmap>(bitmap)); } return bitmap; } // TODO: why? // upload: see profileImageCacheManager line 96 Log.w(TAG, "Image is missing: " + file); // return the default photo return mDefaultBitmap; } public boolean contains(String url) { return get(url) != mDefaultBitmap; } public void clear() { String[] files = mContext.fileList(); for (String file : files) { mContext.deleteFile(file); } synchronized (this) { mCache.clear(); } } public void cleanup(HashSet<String> keepers) { String[] files = mContext.fileList(); HashSet<String> hashedUrls = new HashSet<String>(); for (String imageUrl : keepers) { hashedUrls.add(getMd5(imageUrl)); } for (String file : files) { if (!hashedUrls.contains(file)) { Log.d(TAG, "Deleting unused file: " + file); mContext.deleteFile(file); } } } /** * Compress and resize the Image * * <br /> * 因为不论图片大小和尺寸如何, 饭否都会对图片进行一次有损压缩, 所以本地压缩应该 考虑图片将会被二次压缩所造成的图片质量损耗 * * @param targetFile * @param quality * , 0~100, recommend 100 * @return * @throws IOException */ public File compressImage(File targetFile, int quality) throws IOException { String filepath = targetFile.getAbsolutePath(); // 1. Calculate scale int scale = 1; BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeFile(filepath, o); if (o.outWidth > IMAGE_MAX_WIDTH || o.outHeight > IMAGE_MAX_HEIGHT) { scale = (int) Math.pow( 2.0, (int) Math.round(Math.log(IMAGE_MAX_WIDTH / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5))); // scale = 2; } Log.d(TAG, scale + " scale"); // 2. File -> Bitmap (Returning a smaller image) o.inJustDecodeBounds = false; o.inSampleSize = scale; Bitmap bitmap = BitmapFactory.decodeFile(filepath, o); // 2.1. Resize Bitmap // bitmap = resizeBitmap(bitmap, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT); // 3. Bitmap -> File writeFile(filepath, bitmap, quality); // 4. Get resized Image File String filePath = getMd5(targetFile.getPath()); File compressedImage = mContext.getFileStreamPath(filePath); return compressedImage; } /** * 保持长宽比缩小Bitmap * * @param bitmap * @param maxWidth * @param maxHeight * @param quality * 1~100 * @return */ public Bitmap resizeBitmap(Bitmap bitmap, int maxWidth, int maxHeight) { int originWidth = bitmap.getWidth(); int originHeight = bitmap.getHeight(); // no need to resize if (originWidth < maxWidth && originHeight < maxHeight) { return bitmap; } int newWidth = originWidth; int newHeight = originHeight; // 若图片过宽, 则保持长宽比缩放图片 if (originWidth > maxWidth) { newWidth = maxWidth; double i = originWidth * 1.0 / maxWidth; newHeight = (int) Math.floor(originHeight / i); bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); } // 若图片过长, 则从中部截取 if (newHeight > maxHeight) { newHeight = maxHeight; int half_diff = (int) ((originHeight - maxHeight) / 2.0); bitmap = Bitmap.createBitmap(bitmap, 0, half_diff, newWidth, newHeight); } Log.d(TAG, newWidth + " width"); Log.d(TAG, newHeight + " height"); return bitmap; } }