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


 

相关文章
|
7月前
|
XML Java Android开发
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
156 0
|
7月前
|
XML Java Android开发
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
972 0
|
3月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
75 20
Android经典面试题之图片Bitmap怎么做优化
|
7月前
|
Android开发
Android通过手势(多点)缩放和拖拽图片
Android通过手势(多点)缩放和拖拽图片
61 4
|
7月前
|
Java Android开发
android 下载图片的问题
android 下载图片的问题
51 3
|
4月前
|
数据处理 开发工具 数据安全/隐私保护
Android平台RTMP推送|轻量级RTSP服务|GB28181接入之文字、png图片水印的精进之路
本文探讨了Android平台上推流模块中添加文字与PNG水印的技术演进。自2015年起,为了满足应急指挥及安防领域的需求,逐步发展出三代水印技术:第一代为静态文字与图像水印;第二代实现了动态更新水印内容的能力,例如实时位置与时间信息;至第三代,则优化了数据传输效率,直接使用Bitmap对象传递水印数据至JNI层,减少了内存拷贝次数。这些迭代不仅提升了用户体验和技术效率,也体现了开发者追求极致与不断创新的精神。
|
4月前
|
自然语言处理 定位技术 API
Android经典实战之如何获取图片的经纬度以及如何根据经纬度获取对应的地点名称
本文介绍如何在Android中从图片提取地理位置信息并转换为地址。首先利用`ExifInterface`获取图片内的经纬度,然后通过`Geocoder`将经纬度转为地址。注意操作需在子线程进行且考虑多语言支持。
274 4
|
4月前
|
XML 前端开发 Android开发
Android经典实战之Kotlin中实现圆角图片和圆形图片
本文介绍两种实现圆角图像视图的方法。第一种是通过自定义Kotlin `AppCompatImageView`,重写`onDraw`方法使用`Canvas`和`Path`进行圆角剪裁。第二种利用Android Material库中的`ShapeableImageView`,简单配置即可实现圆角效果。两种方法均易于实现且提供动态调整圆角半径的功能。
95 0
|
6月前
|
JSON 编解码 Apache
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
70 1
|
6月前
|
Java Android开发
18. 【Android教程】图片控件 ImageView
18. 【Android教程】图片控件 ImageView
103 4