当前位置: 代码迷 >> Android >> Android开发之图片处理课题(三):利用ThreadPoolExcutor线程池实现多图片的异步加载
  详细解决方案

Android开发之图片处理课题(三):利用ThreadPoolExcutor线程池实现多图片的异步加载

热度:26   发布时间:2016-04-28 02:28:43.0
Android开发之图片处理专题(三):利用ThreadPoolExcutor线程池实现多图片的异步加载

在上一篇专题Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩中我们实现了listView的图片的大量加载。今天,我们换一种方式,采用线程池的方式来实现。

我们需要准备两个东西:

1、图片下载任务类

2、线程池。


1、图片下载任务类。

图片下载任务类,将需要显示的iamgeView,线程通讯消息管理者handler进行了封装。当图片下载无论成功还是失败,handler发送对应的消息,传入的iamgeView显示对应的图片。这里就不在应用软引用技术,采用的bitmapManager是采用了weakHashMap来实现缓存。关于weakHashmap,是采用了弱引用技术,其实现原理大家可以自行去查阅,这里就不在详细描述了。
/** * @ClassName: AsyncImageTask * @author victor_freedom ([email protected]) * @createddate 2015-2-1 下午10:58:52 * @Description: 图片异步加载任务 */public class AsyncImageTask implements Runnable {	private final String TAG = "AsyncImageTask";	// 任务标识ID,其实就是图片缓存路径	private String taskId;	// 传入需要设定的ImageView	private ImageView imageView;	// 图片下载路径	private String destUrl;	// 下载失败显示图片的ID	private int failImage = -1;	// 默认图片ID	private int defaultImage = -1;	// 压缩比例	private int sampleSize = 1;	// 是否设置为background	private boolean forBackground = false;	private boolean canceled = false;	// 消息管理器	private AsyncHandler handler = null;	// 动画	private Animation animation = null;	public final static int OK = 1;	public final static int FAIL = 2;	public final static int EXSIT = 3;	private boolean forHome = false;	/**	 * 构造函数	 * 	 * @param imageView	 *            ImageView	 * @param url	 *            图片地址	 */	public AsyncImageTask(ImageView imageView, String url) {		this(null, imageView, url);	}	/**	 * 构造函数	 * 	 * @param taskId	 *            任务id	 * @param imageView	 *            ImageView	 * @param url	 *            图片地址	 */	public AsyncImageTask(String taskId, ImageView imageView, String url) {		if (null == taskId || taskId.trim().length() == 0) {			// 任务标识用图片缓存地址来标识唯一			String tid = CacheName.getCachePath(imageView.getContext(), url);			this.taskId = tid;		} else {			this.taskId = taskId;		}		if (null != imageView) {			this.imageView = imageView;			this.imageView.setTag(this.taskId);		}		this.destUrl = url;		this.handler = new AsyncHandler();	}	/**	 * taskId	 * 	 * @return the taskId	 */	public String getTaskId() {		return taskId;	}	/**	 * @param taskId	 *            the taskId to set	 */	public void setTaskId(String taskId) {		this.taskId = taskId;	}	/**	 * forHome	 * 	 * @return the forHome	 */	public boolean isForHome() {		return forHome;	}	/**	 * @param forHome	 *            the forHome to set	 */	public void setForHome(boolean forHome) {		this.forHome = forHome;	}	/**	 * defaultImage	 * 	 * @return the defaultImage	 */	public int getDefaultImage() {		return defaultImage;	}	/**	 * @param defaultImage	 *            the defaultImage to set	 */	public void setDefaultImage(int defaultImage) {		this.defaultImage = defaultImage;	}	/**	 * 获取下载失败显示的图片资源id	 * 	 * @return int	 */	public int getFailImage() {		return failImage;	}	/**	 * 设置下载失败后要显示图片资源id	 * 	 * @param failImage	 *            R.drawable.xxx	 */	public void setFailImage(int failImage) {		this.failImage = failImage;	}	/**	 * 获取图片的压缩比例	 * 	 * @return int	 */	public int getSampleSize() {		return sampleSize;	}	/**	 * 设置图片的采样率	 * 	 * @param sampleSize	 *            最好为2的指数倍	 */	public void setSampleSize(int sampleSize) {		if (sampleSize > 0) {			this.sampleSize = sampleSize;		}	}	/**	 * 下载图片后是否设置为背景图	 * 	 * @return 是背景图返回true,否则返回false	 */	public boolean isForBackground() {		return forBackground;	}	/**	 * 设置是否显示为背景图	 * 	 * @param forBackground	 *            默认为false	 */	public void setForBackground(boolean forBackground) {		this.forBackground = forBackground;	}	/**	 * canceled	 * 	 * @return the canceled	 */	public boolean isCanceled() {		return canceled;	}	/**	 * 取消当前任务	 * 	 * @param canceled	 *            the canceled to set	 */	public void setCanceled(boolean canceled) {		this.canceled = canceled;	}	/**	 * imageView	 * 	 * @return the imageView	 */	public ImageView getImageView() {		return imageView;	}	/**	 * @param imageView	 *            the imageView to set	 */	public void setImageView(ImageView imageView) {		this.imageView = imageView;	}	/**	 * destUrl	 * 	 * @return the destUrl	 */	public String getDestUrl() {		return destUrl;	}	/**	 * @param destUrl	 *            the destUrl to set	 */	public void setDestUrl(String destUrl) {		this.destUrl = destUrl;	}	/**	 * handler	 * 	 * @return the handler	 */	public AsyncHandler getHandler() {		return handler;	}	/**	 * @param handler	 *            the handler to set	 */	public void setHandler(AsyncHandler handler) {		this.handler = handler;	}	/**	 * animation	 * 	 * @return the animation	 */	public Animation getAnimation() {		return animation;	}	/**	 * 设置图片下载成功后的动画效果	 * 	 * @param animation	 *            the animation to set	 */	public void setAnimation(Animation animation) {		this.animation = animation;	}	@Override	public void run() {		// 拿到缓存地址		String destPath = CacheName.getCachePath(imageView.getContext(),				destUrl);		// 创建文件		File file = new File(destPath);		Message msg = handler.obtainMessage();		// 判断该文件是否已经下载过		if (!file.exists()) {			// 如果没有,则下载			if (download(destUrl, destPath + ".tmp")) {				// 创建临时缓存文件				File temp = new File(destPath + ".tmp");				// 按照下载文件命名				temp.renameTo(file);				msg.what = OK;				msg.obj = destPath;			} else {				// 如果下载失败,删除临时文件				msg.what = FAIL;				deleteTemp(destPath + ".tmp");			}		} else {			msg.what = EXSIT;			msg.obj = destPath;		}		if (!canceled) {			handler.sendMessage(msg);		} else {			// 如果中断了下载,则清除临时文件			deleteTemp(destPath + ".tmp");		}	}	private void deleteTemp(String path) {		File file = new File(path);		if (file.exists()) {			file.delete();		}	}	private boolean download(String imageUrl, String destPath) {		// 删除同名临时文件		deleteTemp(destPath);		boolean success = false;		URL url = null;		InputStream is = null;		OutputStream out = null;		HttpURLConnection conn = null;		// 下载 图片		try {			url = new URL(imageUrl);			conn = (HttpURLConnection) url.openConnection();			conn.setConnectTimeout(20000);			conn.setReadTimeout(5 * 60 * 1000);			conn.setDoInput(true);			conn.setRequestProperty("Accept-Language", "zh-cn");			conn.setRequestProperty(					"User-Agent",					"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");			conn.setRequestProperty("Connection", "Keep-Alive");			conn.connect();			if (conn.getResponseCode() == 200) {				is = conn.getInputStream();				int read = 0;				byte[] buffer = new byte[1024];				// 拿到缓存路径的输入流				out = new FileOutputStream(destPath);				while ((read = is.read(buffer)) != -1) {					// 写入数据					out.write(buffer, 0, read);				}				// 返回成功标识位				success = true;			} else {				Log.d(TAG, "the respond code is ---> " + conn.getResponseCode());				Log.d(TAG, "the url is:" + imageUrl);			}		} catch (MalformedURLException e) {			Log.d(TAG, "MalformedURLException ---> " + e.toString());		} catch (IOException e) {			Log.d(TAG, "IOException ---> " + e.toString());		} finally {			try {				if (out != null) {					out.flush();					out.close();				}				if (conn != null) {					conn.disconnect();				}			} catch (IOException e) {				Log.d(TAG, e.toString());			}		}		return success;	}// end download	/**	 * @ClassName: AsyncHandler	 * @author victor_freedom ([email protected])	 * @createddate 2015-2-1 下午11:16:28	 * @Description: 消息首发器	 */	final class AsyncHandler extends Handler {		@Override		public void handleMessage(Message msg) {			switch (msg.what) {			case FAIL:				doFail(msg);				break;			default:				if (forHome) {					imageView.setScaleType(ScaleType.FIT_XY);				}				doSuccess(msg);				break;			}		}		/**		 * @Title: doFail		 * @Description: 下载失败后,设定指定的失败图片		 * @param msg		 * @throws		 */		private void doFail(Message msg) {			if (forBackground && failImage != -1) {				imageView.setBackgroundResource(failImage);			} else if (!forBackground && failImage != -1) {				imageView.setImageResource(failImage);			}		}		/**		 * @Title: doSuccess		 * @Description: 下载成功后,显示		 * @param msg		 * @throws		 */		private void doSuccess(Message msg) {			//拿到路径			String path = (String) msg.obj;			//压缩存放图片			BitmapManager.getInstance().putBitmap(path, sampleSize);			//拿到图片			Bitmap bitmap = BitmapManager.getInstance().getBitmap(path);			String tag = (String) imageView.getTag();			//图片设定			if ((null != bitmap) && tag == null || tag.equals(taskId)) {				if (forBackground) {					imageView.setBackgroundDrawable(new BitmapDrawable(bitmap));				} else if (!forBackground) {					imageView.setImageBitmap(bitmap);				}				//是否执行动画				if (msg.what == OK && null != animation) {					imageView.setAnimation(animation);					animation.start();				}			}		}	}// end AsyncHandler}

2、线程池创建

这里我们采用自定义线程池的方式,有关线程池的概述,请参考: JAVA学习笔记之多线程专题(二):线程池概述
/** * @ClassName: TaskQueue * @author victor_freedom ([email protected]) * @createddate 2015-2-1 下午11:31:19 * @Description: 图片异步任务管理器 */public final class TaskQueue {	private final static String TAG = "TaskQueue";	private static TaskQueue taskQueue = null;	private static ThreadPoolExecutor threadPool = null;// 线程池	private final static int CORE_SIZE = 2; // 最小工作线程数量	private final static int MAX_SIZE = 3; // 最大共作线程数量	private final static int QUEUE_SIZE = 20; // 线程缓冲队列容量	private final static long ALIVE_TIME = 10; // 某线程允许的最大空闲时长	private final static TimeUnit T_Unit = TimeUnit.SECONDS; // 空闲时长单位:秒	private static BlockingQueue<Runnable> queue = null; // 线程缓冲队列	private static RejectedExecutionHandler rejectedHandler = new DiscardOldestPolicy(); // 线程池拒绝策略,当缓冲队列满时,工作队列头部的任务将被删除	//新建任务队列及线程池	private TaskQueue() {		queue = new LinkedBlockingQueue<Runnable>(QUEUE_SIZE);		threadPool = new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, ALIVE_TIME,				T_Unit, queue, rejectedHandler);	}	/**	 * 采用单例设计	 * 	 * @return TaskQueue	 */	public static TaskQueue getInstance() {		if (null == taskQueue) {			taskQueue = new TaskQueue();		}		return taskQueue;	}	/**	 * 添加一个任务	 * 	 * @param task	 *            AsyncImageTask	 */	public void addTask(AsyncImageTask task) {		//检查本地是否已经存在缓存文件		if (!hadLocal(task)) {			boolean had = false;			//判断任务队列里面是否已经存在该任务			for (int i = 0; i < queue.size(); i++) {				AsyncImageTask t = (AsyncImageTask) queue.element();				if (task.getTaskId().equals(t.getTaskId())) {					had = true;					Log.d(TAG, "the task id is:" + t.getTaskId());					break;				}			}			if (!had) {				//如果不存在,加入执行队列				if (task.getDefaultImage() != -1) {					if (!task.isForBackground()) {						task.getImageView().setImageResource(								task.getDefaultImage());					} else {						task.getImageView().setBackgroundResource(								task.getDefaultImage());					}				}				threadPool.execute(task);			} else {				//如果已经存在,则不做修改。等待执行				if (task.getDefaultImage() != -1) {					if (!task.isForBackground()) {						task.getImageView().setImageResource(								task.getDefaultImage());					} else {						task.getImageView().setBackgroundResource(								task.getDefaultImage());					}				}			}		} else {			//如果有缓存,则直接拿本地的图片来用			String destPath = CacheName.getCachePath(task.getImageView()					.getContext(), task.getDestUrl());			Message msg = new Message();			msg.what = AsyncImageTask.EXSIT;			msg.obj = destPath;			task.getHandler().sendMessage(msg);		}	}	/**	 * 检查本地是否已经存在缓存文件	 * 	 * @param task	 * @return 存在返回true,否则返回false	 */	private boolean hadLocal(AsyncImageTask task) {		String destPath = CacheName.getCachePath(task.getImageView()				.getContext(), task.getDestUrl());		File file = new File(destPath);		if (file.exists()) {			if (ImageCheck.isAvailable(destPath)) {				file.setLastModified(System.currentTimeMillis());				return true;			} else {				file.delete();			}		}		return false;	}	/**	 * 关闭所有工作线程	 */	public void shutDown() {		threadPool.shutdown();		taskQueue = null;		queue = null;	}}

3、adapter编写

在adpter处我们只需要这么写几句代码即可
		//拿到路径		String url = Constant.PIC_BASE_URL + news.PicUrl;		//创建任务		AsyncImageTask task = new AsyncImageTask(holder.iv, url);		//设定默认图片和下载失败图片		task.setDefaultImage(R.drawable.news_image_default);		task.setFailImage(R.drawable.news_image_default);		//加入到任务队列中执行		TaskQueue.getInstance().addTask(task);



以上就是如何使用线程池去实现图片的异步加载流程。相信看过此文之后大家对多线程的操作有了一定的认识。希望此文能够帮助到看到此文的人。

  相关解决方案