Android异步加载图片详解之方式二(2)

简介: FileCache.java如下: package com.cn.loadImages; import java.io.File; import java.

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;
	}

	//每一张图片对应的File
	public 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和sSoftBitmapCache
public 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) {
		@Override
		protected 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;
			} else
				return false;
		}
	};
	
	// Soft cache for bitmaps kicked out of hard cache
	private 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的Runnable
	private 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 cache
		SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
		if (bitmapReference != null) {
			final Bitmap bitmap = bitmapReference.get();
			if (bitmap != null) {
				// Bitmap found in soft cache
				return bitmap;
			} else {
				// Soft reference has been Garbage Collected
				sSoftBitmapCache.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.html
public 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);
		}

		@Override
		public 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);
		}

		@Override
		protected 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
						// TODO
						if (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;
		}

		@Override
		protected void onPostExecute(Bitmap bitmap) {			
			if (isCancelled()) {
				bitmap = null;
			}
			// add bitmap to cache
			//下载完成后的操作2:将图片保存在内存中
			imageCache.addBitmapToCache(url, bitmap);
			//下载完成后的操作3:在ImageView中显示图片
			//若引用可能会被系统回收,所以要先判断imageViewWeakReference是否为null
			if (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的图片为下载的Bitmap
				if (this == bitmapDownloaderTask) {
					imageView.setImageBitmap(bitmap);
				}
			}
		}

		@Override
		protected 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)
			//在此得到预先指定的Drawable
			Drawable 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中得到一个BitmapDownloaderTask
		public 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 success
					if (bFile == true) {
						return true;
					} else {
						return false;
					}
				}
			} catch (Exception err) {
				err.printStackTrace();
				return false;
			}
		}
	    //判断是否可以保存
		public static boolean canSave(long size) {
			return getAvailableExternalMemorySize() > size;
		}
	
}


 

相关文章
|
4月前
|
XML Java Android开发
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
45 0
|
4月前
|
XML Java Android开发
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
171 0
|
7月前
|
存储 编解码 Android开发
Android关于图片方向问题
Android关于图片方向问题
41 0
|
4月前
|
XML JSON Java
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
69 0
|
22天前
|
Android开发
Android保存图片到相册(适配android 10以下及以上)
Android保存图片到相册(适配android 10以下及以上)
21 1
|
6月前
|
SQL 人工智能 移动开发
Android etc1tool之png图片转换pkm 和 zipalign简介
etc1tool 是一种命令行实用程序,可用于将 PNG 图片编码为 ETC1 压缩标准格式(PKM),并将 ETC1 压缩图片解码回 PNG。
|
8月前
|
Java Android开发
Android 保存资源图片到相册最新写法适用于Android10.0及以上
Android 保存资源图片到相册最新写法适用于Android10.0及以上
583 0
|
8月前
|
SQL 数据库 Android开发
Android 访问系统相册选中图片,并返回该图片的路径
Android 访问系统相册选中图片,并返回该图片的路径
99 0
|
4月前
|
API Android开发
[Android]图片加载库Glide
[Android]图片加载库Glide
54 0
|
4月前
|
Android开发
[Android]制作9-Patch图片
[Android]制作9-Patch图片
42 0