读书人

Android 应用开发 之经过AsyncTask与T

发布时间: 2013-10-08 17:02:59 作者: rapoo

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

在加载大量数据的时候,经常会用到异步加载,所谓异步加载,就是把耗时的工作放到子线程里执行,当数据加载完毕的时候再到主线程进行UI刷新。在数据量非常大的情况下,我们通常会使用两种技术来进行异步加载,一是通过AsyncTask来实现,另一种方式则是通过ThreadPool来实现,今天我们就通过一个例子来讲解和对比这两种实现方式。

本文原创,如需转载,请注明转载地址http://blog.csdn.net/carrey1989/article/details/12002033

项目的结构如下所示:

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

在今天这个例子里,我们用到了之前一篇文章中写过的一个自定义控件,如果有同学感兴趣的话可以点击这里来先研究下这个控件的实现,为了配合异步加载的效果,我针对这个控件做了一点修改,下面会对修改的地方进行解释。

接下来我们就分别针对ThreadPool和AsyncTask两种实现方式进行讲解,我会顺着实现的思路贴出关键的代码,在文章最后会贴出实现效果和源码下载,感兴趣的同学可以下载下来对比来看。

首先来讲解ThreadPool(线程池)的实现方式。

我们首先需要来实现一个线程池管理器,这个管理器内部包含一个独立的轮询子线程,它的工作是不时的检查工作队列,如果队列中有未执行的任务,就将任务交给线程池来执行。此外,线程池管理器还负责管理线程池和维护任务队列。具体实现代码如下:

package com.carrey.asyncloaddemo;import java.util.LinkedList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import android.util.Log;/** * 线程池管理类 * @author carrey * */public class ThreadPoolManager {private static final String TAG = "ThreadPoolManager";/** 线程池的大小 */private int poolSize;private static final int MIN_POOL_SIZE = 1;private static final int MAX_POOL_SIZE = 10;/** 线程池 */private ExecutorService threadPool;/** 请求队列 */private LinkedList<ThreadPoolTask> asyncTasks;/** 工作方式 */private int type;public static final int TYPE_FIFO = 0;public static final int TYPE_LIFO = 1;/** 轮询线程 */private Thread poolThread;/** 轮询时间 */private static final int SLEEP_TIME = 200;public ThreadPoolManager(int type, int poolSize) {this.type = (type == TYPE_FIFO) ? TYPE_FIFO : TYPE_LIFO;if (poolSize < MIN_POOL_SIZE) poolSize = MIN_POOL_SIZE;if (poolSize > MAX_POOL_SIZE) poolSize = MAX_POOL_SIZE;this.poolSize = poolSize;threadPool = Executors.newFixedThreadPool(this.poolSize);asyncTasks = new LinkedList<ThreadPoolTask>();}/** * 向任务队列中添加任务 * @param task */public void addAsyncTask(ThreadPoolTask task) {synchronized (asyncTasks) {Log.i(TAG, "add task: " + task.getURL());asyncTasks.addLast(task);}}/** * 从任务队列中提取任务 * @return */private ThreadPoolTask getAsyncTask() {synchronized (asyncTasks) {if (asyncTasks.size() > 0) {ThreadPoolTask task = (this.type == TYPE_FIFO) ? asyncTasks.removeFirst() : asyncTasks.removeLast();Log.i(TAG, "remove task: " + task.getURL());return task;}}return null;}/** * 开启线程池轮询 * @return */public void start() {if (poolThread == null) {poolThread = new Thread(new PoolRunnable());poolThread.start();}}/** * 结束轮询,关闭线程池 */public void stop() {poolThread.interrupt();poolThread = null;}/** * 实现轮询的Runnable * @author carrey * */private class PoolRunnable implements Runnable {@Overridepublic void run() {Log.i(TAG, "开始轮询");try {while (!Thread.currentThread().isInterrupted()) {ThreadPoolTask task = getAsyncTask();if (task == null) {try {Thread.sleep(SLEEP_TIME);} catch (InterruptedException e) {Thread.currentThread().interrupt();}continue;}threadPool.execute(task);}} finally {threadPool.shutdown();}Log.i(TAG, "结束轮询");}}}

注意在上面的代码中,我们自定义了任务单元的实现,任务单元是一系列的Runnable对象,最终都将交给线程池来执行,任务单元的实现代码如下:

ThreadPoolTask.java:

package com.carrey.asyncloaddemo;/** * 任务单元 * @author carrey * */public abstract class ThreadPoolTask implements Runnable {protected String url;public ThreadPoolTask(String url) {this.url = url;}public abstract void run();public String getURL() {return this.url;}}

ThreadPoolTaskBitmap.java:

package com.carrey.asyncloaddemo;import com.carrey.customview.customview.CustomView;import android.graphics.Bitmap;import android.os.Process;import android.util.Log;/** * 图片加载的任务单元 * @author carrey * */public class ThreadPoolTaskBitmap extends ThreadPoolTask {private static final String TAG = "ThreadPoolTaskBitmap";private CallBack callBack;private CustomView view;private int position;public ThreadPoolTaskBitmap(String url, CallBack callBack, int position, CustomView view) {super(url);this.callBack = callBack;this.position = position;this.view = view;}@Overridepublic void run() {Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);Bitmap bitmap = ImageHelper.loadBitmapFromNet(url);Log.i(TAG, "loaded: " + url);if (callBack != null) {callBack.onReady(url, bitmap, this.position, this.view);}}public interface CallBack {public void onReady(String url, Bitmap bitmap, int position, CustomView view);}}

上面代码中的回调实现位于MainActivity.java中,在任务单元的run方法中主要做的事情就是从服务器得到要加载的图片的Bitmap,然后调用回调,在回调中会将得到的图片加载到UI界面中。

在加载服务器图片的时候,用到了ImageHelper这个工具类,这个类主要的功能就是提供获得服务器图片地址和解析图片的方法,具体代码如下:

package com.carrey.asyncloaddemo;import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.net.URLConnection;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.util.Log;/** * 工具类,用于获得要加载的图片资源 * @author carrey * */public class ImageHelper {private static final String TAG = "ImageHelper";public static String getImageUrl(String webServerStr, int position) {return "http://" + webServerStr + "/" + (position % 50) + ".jpg";}/** * 获得网络图片Bitmap * @param imageUrl * @return */public static Bitmap loadBitmapFromNet(String imageUrlStr) {Bitmap bitmap = null;URL imageUrl = null;if (imageUrlStr == null || imageUrlStr.length() == 0) {return null;}try {imageUrl = new URL(imageUrlStr);URLConnection conn = imageUrl.openConnection();conn.setDoInput(true);conn.connect();InputStream is = conn.getInputStream();int length = conn.getContentLength();if (length != -1) {byte[] imgData = new byte[length];byte[] temp = new byte[512];int readLen = 0;int destPos = 0;while ((readLen = is.read(temp)) != -1) {System.arraycopy(temp, 0, imgData, destPos, readLen);destPos += readLen;}bitmap = BitmapFactory.decodeByteArray(imgData, 0, imgData.length);}} catch (IOException e) {Log.e(TAG, e.toString());return null;}return bitmap;}}
到这里准备的工作基本就完成了,接下来的工作就是启动线程池管理器并向任务队列添加我们的加载任务了,这部分工作我们放在GridView的Adapter的getView方法中来执行,其中关键的代码如下:

holder.customView.setTitleText("ThreadPool");holder.customView.setSubTitleText("position: " + position);poolManager.start();String imageUrl = ImageHelper.getImageUrl(webServerStr, position);poolManager.addAsyncTask(new ThreadPoolTaskBitmap(imageUrl, MainActivity.this, position, holder.customView));

下面我们来接着讲解AsyncTask的实现方式。

相对线程池的实现方式,AsyncTask的实现方式要简单一些

我们首先来定义好我们的AsyncTask子类,在其中我们将在doInBackground中加载图片数据,在onPostExecute中来刷新UI。代码如下:

package com.carrey.asyncloaddemo;import com.carrey.customview.customview.CustomView;import android.graphics.Bitmap;import android.os.AsyncTask;import android.util.Log;import android.util.Pair;public class AsyncLoadTask extends AsyncTask<Integer, Void, Pair<Integer, Bitmap>> {private static final String TAG = "AsyncLoadTask";/** 要刷新的view */private CustomView view;public AsyncLoadTask(CustomView view) {this.view = view;}@Overrideprotected void onPreExecute() {super.onPreExecute();}@Overrideprotected Pair<Integer, Bitmap> doInBackground(Integer... params) {int position = params[0];String imageUrl = ImageHelper.getImageUrl(MainActivity.webServerStr, position);Log.i(TAG, "AsyncLoad from NET :" + imageUrl);Bitmap bitmap = ImageHelper.loadBitmapFromNet(imageUrl);return new Pair<Integer, Bitmap>(position, bitmap);}@Overrideprotected void onPostExecute(Pair<Integer, Bitmap> result) {if (result.first == view.position) {view.setImageBitmap(result.second);}}}

在Adapter中调用AsyncTask异步加载的代码如下:

holder.customView.setTitleText("AsyncTask");holder.customView.setSubTitleText("position: " + position);new AsyncLoadTask(holder.customView).execute(position);

写到这里,关键的代码基本都讲完了,我们不妨先来看一下两种实现方式的效果:

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

通过对比可以发现,ThreadPool相比AsyncTask,并发能力更强,加载的速度也更快,AsyncTask在加载过程中明显变现出顺序性,加载的速度要慢一些。

下面是调用两种加载方式的MainActivity的所有代码:

@Overrideprotected void onPostExecute(Pair<Integer, Bitmap> result) {if (result.first == view.position) {view.setImageBitmap(result.second);}}

为什么要做这样一个判断呢?这是因为BaseAdapter的convertView是不停复用的,如果我们的滑动非常快,那就会存在这样一种情况,有一个convertView还没有加载完,就会第二次复用了,如果这个时候第二次加载慢于第一次,那结果就会被第一次覆盖,这样就不准确了,所以我们要加一个判断,以确保刷新的准确。

最后贴出源码下载,感兴趣的同学可以下载对比,欢迎留言交流。

源码下载


1楼zoozooll前天 14:33
AsyncTask的方式本身也是一个thread pool,不知道你有没有跟踪到原代码里面去
Re: carrey1989前天 14:34
回复zoozoolln嗯,多谢指点,有时间一定研究

读书人网 >Android

热点推荐