Android异步加载图片详解之方式二(2)
FileCache.java如下:
package com.cn.loadImages;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import android.content.Context;import android.net.Uri;import android.util.Log;public class FileCache {private File cacheDir;public FileCache(Context context) {if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {cacheDir = new File(android.os.Environment.getExternalStorageDirectory(),"ltcImageCache");} else {cacheDir = context.getCacheDir();}if (cacheDir != null && !cacheDir.exists()) {Utils.doMkdir(cacheDir);}}//在SD卡上建立文件夹用来保存图片public FileCache(Context context, String path) {if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {cacheDir = new File(android.os.Environment.getExternalStorageDirectory()+File.separator+ path);Log.i("xx", "cacheDir="+cacheDir.toString());}if (cacheDir != null && !cacheDir.exists()) {Utils.doMkdir(cacheDir);}}//下载完成后将图片保存在文件(SD卡)中public boolean addToFileCache(String url, InputStream inputStream, long size) {boolean isReturnBitmap = true;OutputStream outputStream = null;try {if (!Utils.canSave(size)) {return false;}File file = getFromFileCache(url);if (file == null) {return false;}outputStream = new FileOutputStream(file);copyStream(inputStream, outputStream);} catch (FileNotFoundException e) {e.printStackTrace();isReturnBitmap = false;} catch (Exception e) {isReturnBitmap = false;} finally {if (outputStream != null) {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}return isReturnBitmap;}//每一张图片对应的Filepublic File getFromFileCache(String url) {String fileName = getImageNameFromUrl(url);if (cacheDir == null) {return null;}File file = new File(cacheDir, fileName);return file;}//删除所有的SD卡上的文件缓存public void clearCache() {if (cacheDir == null) {return;}File[] files = cacheDir.listFiles();if (files == null)return;for (File f : files)f.delete();}public void deleteIncompleteCache(String url) {File file = getFromFileCache(url);if (file != null && file.exists()) {file.delete();}}//从图片的url中截取出文件名private String getImageNameFromUrl(String url) { Uri uri=Uri.parse(url); String imageName=uri.getLastPathSegment(); return imageName;} //保存图片到SD卡时的流操作private void copyStream(InputStream inputStream, OutputStream outputStream) {final int buffer_size = 1024;try {byte[] bytes = new byte[buffer_size];for (;;) {int count = inputStream.read(bytes, 0, buffer_size);if (count == -1)break;outputStream.write(bytes, 0, count);}} catch (Exception ex) {}}}
ImageCache.java如下:
package com.cn.loadImages;import java.lang.ref.SoftReference;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.concurrent.ConcurrentHashMap;import android.graphics.Bitmap;import android.os.Handler;/** * Cache-related fields and methods. * * We use a hard and a soft cache. A soft reference cache is too aggressively * cleared by the Garbage Collector. * *///这是在内存中的缓存.//分为两级sHardBitmapCache和sSoftBitmapCachepublic class ImageCache {private static final int HARD_CACHE_CAPACITY = 30;private static final int DELAY_BEFORE_PURGE = 60 * 1000; // in milliseconds// Hard cache, with a fixed maximum capacity and a life duration@SuppressWarnings("serial")private final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {if (size() > HARD_CACHE_CAPACITY) {// Entries push-out of hard reference cache are transferred to soft reference cache//当sHardBitmapCache中的size大于额定容量HARD_CACHE_CAPACITY的时候//将sHardBitmapCache中最陈旧的那个对象放到了sSoftBitmapCache中//sSoftBitmapCache中的对象更容易被GC回收sSoftBitmapCache.put(eldest.getKey(),new SoftReference<Bitmap>(eldest.getValue()));return true;} elsereturn false;}};// Soft cache for bitmaps kicked out of hard cacheprivate final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);private final Handler purgeHandler = new Handler(); //用于清空(purger)sSoftBitmapCache和sHardBitmapCache的Runnableprivate final Runnable purger = new Runnable() {public void run() {//clearCache();}};//下载完成后将bitmap放在内存(sHardBitmapCache)中public void addBitmapToCache(String url, Bitmap bitmap) {if (bitmap != null) {synchronized (sHardBitmapCache) {sHardBitmapCache.put(url, bitmap);}}}//从imageCache中得到图片public Bitmap getBitmapFromCache(String url) {// First try the hard reference cache// 首先希望从sHardBitmapCache中得到图片synchronized (sHardBitmapCache) {final Bitmap bitmap = sHardBitmapCache.get(url);if (bitmap != null) {// Bitmap found in hard cache// Move element to first position, so that it is removed last// 既然现在要得到这个图片,那么这张图片就是最近被使用的了.// 在所有的对象中就是最新的对象.// 所以先将该对象从sHardBitmapCache中移除// 然后将其插入到sHardBitmapCache的最前面sHardBitmapCache.remove(url);sHardBitmapCache.put(url, bitmap);return bitmap;}else{}}//如果在sHardBitmapCache中没有,那么可能是因为该对象太陈旧//已经放到了sSoftBitmapCache中.//所以尝试从sSoftBitmapCache中获取对象// Then try the soft reference cacheSoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);if (bitmapReference != null) {final Bitmap bitmap = bitmapReference.get();if (bitmap != null) {// Bitmap found in soft cachereturn bitmap;} else {// Soft reference has been Garbage CollectedsSoftBitmapCache.remove(url);}}else{}return null;}/** * Clears the image cache used internally to improve performance. Note that * for memory efficiency reasons, the cache will automatically be cleared * after a certain inactivity delay. */private void clearCache() {sHardBitmapCache.clear();sSoftBitmapCache.clear();}/** * Allow a new delay before the automatic cache clear is done. */public void resetPurgeTimer() {purgeHandler.removeCallbacks(purger);purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);}public void removeFromCache(String url) {if (sHardBitmapCache != null) {sHardBitmapCache.remove(url);}if (sSoftBitmapCache != null) {sSoftBitmapCache.remove(url);}System.gc();}}
ImageDownloader.java如下:
package com.cn.loadImages;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FilterInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.ref.WeakReference;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.HttpStatus;import org.apache.http.client.HttpClient;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.DefaultHttpClient;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Color;import android.graphics.drawable.ColorDrawable;import android.graphics.drawable.Drawable;import android.net.http.AndroidHttpClient;import android.os.AsyncTask;import android.widget.ImageView;//参考资料://http://blog.csdn.net/icephone/article/details/7517865//http://www.cnblogs.com/shyang--TechBlogs/archive/2011/03/24/1994080.htmlpublic class ImageDownloader {private ImageCache imageCache;private FileCache fileCache;//构造方法public ImageDownloader(Context context, String localStoragePath) {imageCache = new ImageCache();//在SD卡上建立文件夹用来保存图片fileCache = new FileCache(context, localStoragePath);}/** * Download the specified image from the Internet and binds it to the * provided ImageView. The binding is immediate if the image is found in the * cache and will be done asynchronously otherwise. A null bitmap will be * associated to the ImageView if an error occurs. */public void download(String url, final ImageView imageView) {//purge 清除imageCache.resetPurgeTimer();//首先尝试从内存中获得图片Bitmap bitmap = imageCache.getBitmapFromCache(url);if (bitmap == null) {forceDownload(url, imageView);} else {//图片已经存在则取消该图片潜在的下载cancelPotentialDownload(url, imageView);imageView.setImageBitmap(bitmap);}}/** * Same as download but the image is always downloaded and the cache is not * used. Kept private at the moment as its interest is not clear. *///下载图片的方法private void forceDownload(String imageUrl, ImageView imageView) {if (imageUrl == null) {return;}if (cancelPotentialDownload(imageUrl, imageView)) {//1 建立一个BitmapDownloaderTask异步任务// 通过BitmapDownloaderTask的构造方法可知:// 该BitmapDownloaderTask对该iamgeView进行弱引用// 注意:!!!!!!!!!!!!!!!!!!!!!// 在BitmapDownloaderTask的构造方法中// 该BitmapDownloaderTask保持了对于imageView的弱引用 // 同时在DownloadedDrawable的构造方法中// DownloadedDrawable保持了对BitmapDownloaderTask的弱引用// 所以BitmapDownloaderTask和ImageView相互弱引用形成了绑定的关系!!!!BitmapDownloaderTask bitmapDownloaderTask = new BitmapDownloaderTask(imageView);//2 建立一个DownloadedDrawable// 通过DownloadedDrawable的构造方法可知:// 该DownloadedDrawable对此bitmapDownloaderTask进行弱引用DownloadedDrawable downloadedDrawable = new DownloadedDrawable(bitmapDownloaderTask);//3 imageView显示一个指定的颜色(Drawable)if (imageView != null) {//在图片下载未完成的时imageView加载该downloadedDrawable//即为DownloadedDrawable中super(Color.TRANSPARENT)指定的颜色imageView.setImageDrawable(downloadedDrawable);}bitmapDownloaderTask.setUrl(imageUrl);//4 开始异步任务bitmapDownloaderTask.execute(imageUrl);}}//取消潜在的下载private static boolean cancelPotentialDownload(String url,ImageView imageView) {BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);if (bitmapDownloaderTask != null) {String bitmapUrl = bitmapDownloaderTask.url;if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {bitmapDownloaderTask.cancel(true);} else {return false;}}return true;}/* * An InputStream that skips the exact number of bytes provided, unless it reaches EOF. */static class FlushedInputStream extends FilterInputStream {public FlushedInputStream(InputStream inputStream) {super(inputStream);}@Overridepublic long skip(long n) throws IOException {long totalBytesSkipped = 0L;while (totalBytesSkipped < n) {long bytesSkipped = in.skip(n - totalBytesSkipped);if (bytesSkipped == 0L) {int b = read();if (b < 0) {break; // we reached EOF} else {bytesSkipped = 1; // we read one byte}}totalBytesSkipped += bytesSkipped;}return totalBytesSkipped;}} //异步任务执行下载public class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {private String url;private WeakReference<ImageView> imageViewWeakReference;private Bitmap bitmap = null;private HttpClient httpClient;public String getUrl() {return this.url;}public void setUrl(String _url) {this.url = _url;}public BitmapDownloaderTask(ImageView imageView) {//在该BitmapDownloaderTask保持了对于imageView的弱引用!!!imageViewWeakReference = new WeakReference<ImageView>(imageView);}@Overrideprotected Bitmap doInBackground(String... params) {boolean download = false;// try to get image from file cache//再尝试从文件(SD卡)缓存中获得图片File file = fileCache.getFromFileCache(url);try {if (file.exists()) {bitmap = BitmapFactory.decodeStream(new FlushedInputStream(new FileInputStream(file)));}} catch (FileNotFoundException e1) {e1.printStackTrace();bitmap = null;} catch (Exception e) {e.printStackTrace();bitmap = null;}if (bitmap != null) {download = true;return bitmap;}// end of try//从文件(SD卡)还未能获得图片,那么开始真正的下载httpClient = new DefaultHttpClient();final HttpGet getRequest = new HttpGet(url);try {HttpResponse httpResponse = httpClient.execute(getRequest);final int statusCode = httpResponse.getStatusLine().getStatusCode();if (statusCode != HttpStatus.SC_OK) {return null;}final HttpEntity httpEntity = httpResponse.getEntity();if (httpEntity != null) {InputStream inputStream = null;try {long size = httpEntity.getContentLength();inputStream = httpEntity.getContent();// save file to file cache//下载完成后的操作1:将图片保存在文件(SD卡)中boolean addResult = fileCache.addToFileCache(url,inputStream, size);// end of save// TODOif (addResult) {download = true;return BitmapFactory.decodeStream(new FlushedInputStream(new FileInputStream(file)));} else {download = true;return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));}} catch (Exception e) {e.printStackTrace();} finally {if (inputStream != null) {inputStream.close();}httpEntity.consumeContent();}}} catch (Exception e) {e.printStackTrace();} finally {if (httpClient != null) {httpClient.getConnectionManager().shutdown();}if (!download) {fileCache.deleteIncompleteCache(url);}}return null;}@Overrideprotected void onPostExecute(Bitmap bitmap) {if (isCancelled()) {bitmap = null;}// add bitmap to cache//下载完成后的操作2:将图片保存在内存中imageCache.addBitmapToCache(url, bitmap);//下载完成后的操作3:在ImageView中显示图片//若引用可能会被系统回收,所以要先判断imageViewWeakReference是否为nullif (imageViewWeakReference != null) {//3.1获得任务引用的ImageView(对应于forceDownload中的1)ImageView imageView = imageViewWeakReference.get();//getBitmapDownloaderTask方法见下(core)//3.2获得该imageview所对应的任务(对应于forceDownload中的2)BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);//3.3若当前任务为该imageview所对应的任务,则设置此imageview的图片为下载的Bitmapif (this == bitmapDownloaderTask) {imageView.setImageBitmap(bitmap);}}}@Overrideprotected void onCancelled() {if ((httpClient instanceof AndroidHttpClient)) {((AndroidHttpClient) httpClient).close();}if (bitmap != null) {bitmap.recycle();bitmap = null;}super.onCancelled();}} /** * @param imageView Any imageView * @return Retrieve the currently active download task (if any) associated * with this imageView. null if there is no such task. */private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {if (imageView != null) {//在forceDownload的3中imageView只是显示了一个预先指定的颜色(Drawable)//在此得到预先指定的DrawableDrawable drawable = imageView.getDrawable();if (drawable instanceof DownloadedDrawable) {DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;//因为在forceDownload的2中该DownloadedDrawable保持了对//该bitmapDownloaderTask进行弱引用//所以当然可以通过该DownloadedDrawable得到bitmapDownloaderTask//getBitmapDownloaderTask方法见下(core)return downloadedDrawable.getBitmapDownloaderTask();}}return null;}/** * A fake Drawable that will be attached to the imageView while the download * is in progress. * <p> * Contains a reference to the actual download task, so that a download task * can be stopped if a new binding is required, and makes sure that only the * last started download process can bind its result, independently of the * download finish order. * </p> *///该类包含了一个对下载任务BitmapDownloaderTask的弱引用//注意://super(Color.TRANSPARENT);//该颜色就是图片还未加载时候ImageView所显示的颜色static class DownloadedDrawable extends ColorDrawable {private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskWeakReference;public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {super(Color.TRANSPARENT);bitmapDownloaderTaskWeakReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);} //从WeakReference中得到一个BitmapDownloaderTaskpublic BitmapDownloaderTask getBitmapDownloaderTask() {return bitmapDownloaderTaskWeakReference.get();}} }
Utils.java如下:
package com.cn.loadImages;import java.io.File;import android.os.Environment;import android.os.StatFs;public class Utils {private static final int ERROR = -1;public static int save_dir = 1;//判断是否已经安装SD卡public static boolean isSDCardExist() {return android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);}//内存剩余空间public static long getAvailableInternalMemorySize() {File path = Environment.getDataDirectory();StatFs stat = new StatFs(path.getPath());long blockSize = stat.getBlockSize();long availableBlocks = stat.getAvailableBlocks();return availableBlocks * blockSize;}//内存总空间public static long getTotalInternalMemorySize() {File path = Environment.getDataDirectory();StatFs stat = new StatFs(path.getPath());long blockSize = stat.getBlockSize();long totalBlocks = stat.getBlockCount();return totalBlocks * blockSize;}//SD卡剩余空间public static long getAvailableExternalMemorySize() {if (isSDCardExist()) {File path = Environment.getExternalStorageDirectory();StatFs stat = new StatFs(path.getPath());long blockSize = stat.getBlockSize();long availableBlocks = stat.getAvailableBlocks();return availableBlocks * blockSize;} else {return ERROR;}}//SD卡总空间public static long getTotalExternalMemorySize() {if (isSDCardExist()) {File path = Environment.getExternalStorageDirectory();StatFs stat = new StatFs(path.getPath());long blockSize = stat.getBlockSize();long totalBlocks = stat.getBlockCount();return totalBlocks * blockSize;} else {return ERROR;}}//创建目录public static boolean doMkdir(File dirFile) {try {boolean bFile = dirFile.exists();if (bFile == true) {return true;} else {bFile = dirFile.mkdirs();// create successif (bFile == true) {return true;} else {return false;}}} catch (Exception err) {err.printStackTrace();return false;}} //判断是否可以保存public static boolean canSave(long size) {return getAvailableExternalMemorySize() > size;}}