在上一篇专题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);
以上就是如何使用线程池去实现图片的异步加载流程。相信看过此文之后大家对多线程的操作有了一定的认识。希望此文能够帮助到看到此文的人。