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

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

时间: 02-01

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

Android 应用开发 之通过AsyncTask与ThreadPool(线程池)两种方式异步加载大量数据的分析与对比--转载

 在加载大量数据的时候,经常会用到异步加载,所谓异步加载,就是把耗时的工作放到子线程里执行,当数据加载完毕的时候再到主线程进行UI刷新.在数据量非常大的情况下,我们通常会使用两种技术来进行异步加载,一是通过AsyncTask来实现,另一种方式则是通过ThreadPool来实现,今天我们就通过一个例子来讲解和对比这两种实现方式.     项目的结构如下所示:     在今天这个例子里,我们用到了之前一篇文章中写过的一个自定义控件,如果有同学感兴趣的话可以点击这里来先研究下这个控件的实现,为了配合异

Android实战简易教程-第四十九枪(两种方式实现网络图片异步加载)

加载图片属于比较耗时的工作,我们需要异步进行加载,异步加载有两种方式:1.通过AsyncTask类进行:2.通过Handler来实现,下面我们就来看一下如何通过这两种方式实现网络图片的异步加载. 一.AsyncTask方式 1.main.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.co

Android ListView异步加载图片乱序问题,原因分析及解决方案

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android所有系统自带的控件当中,ListView这个控件算是用法比较复杂的了,关键是用法复杂也就算了,它还经常会出现一些稀奇古怪的问题,让人非常头疼.比如说在ListView中加载图片,如果是同步加载图片倒还好,但是一旦使用异步加载图片那么问题就来了,这个问题我相信很多Android开发者都曾经遇到过,就是异步加载图片会出现错位乱序的情况.遇到这个问题时,不

Android之ListView异步加载图片且仅显示可见子项中的图片

折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整实例都没看到,只有自己一点点研究了,总体感觉 android 下面要显示个图片真不容易啊. 项目主要实现的功能: 异步加载图片图片内存缓存.异步磁盘文件缓存解决使用 viewHolder 后出现的图片错位问题优化列表滚动性能,仅显示可见子项中的图片无需固定图片显示高度,对高度进行缓存使列表滚动时不会

基于jQuery的图片异步加载和预加载实例

如今的网页中有很多图片,比如相册列表,那么如果一次性读取图片将会瞬间加重服务器的负担,所以我们用jQuery来实现图片的异步加载和预加载功能,这样在页面的可视范围内才会加载图片,当拖动页面至可视界面时,其他图片才会加载,改插件很好地实现了图片异步加载功能. 在线预览   源码下载 html代码部分: <div id="content"> <div id="button"> <ul> <li>小图</li>

滚屏异步加载图片

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> </style> </head> <body> <div id="image"> </div> <script src=&qu

Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩

在上一篇专题Android开发之图片处理专题(一):利用软引用构建图片高速缓存中我们讲述了如何利用软引用技术构建高速缓存.那么想要用到图片,首先得有图片的来源.一般而言,一个应用的图片资源都是从服务器处获得的.今天,我们利用Android开发之网络请求通信专题(二):基于HttpClient的文件上传下载里面封装好的httpUtils来实现图片的下载,然后加载到本地配合软引用缓存使用,以一个listView为例子来说明. 一.准备工作 我们需要准备以下几个类(图片对象和软引用缓存类请参考上一篇专

Android利用Volley异步加载数据(JSON和图片)完整示例

Android利用Volley异步加载数据(JSON和图片)完整示例 MainActivity.java package cc.testvolley; import org.json.JSONObject; import android.app.Activity; import android.app.ProgressDialog; import android.graphics.Bitmap; import android.os.Bundle; import android.support.v

android开发干货:实现listview异步加载图片

针对listview异步加载图片这个问题,麦子学院android开发老师讲了一种非常实用的方法,麦子学院android开发老师说凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.