Android 应用开发 之通过AsyncTask与ThreadPool(线程池)两种方式异步加载大量数据的分析与对比--转载
在加载大量数据的时候,经常会用到异步加载,所谓异步加载,就是把耗时的工作放到子线程里执行,当数据加载完毕的时候再到主线程进行UI刷新。在数据量非常大的情况下,我们通常会使用两种技术来进行异步加载,一是通过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 {
- @Override
- public 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;
- }
- @Override
- public 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;
- }
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- }
- @Override
- protected 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);
- }
- @Override
- protected 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);
写到这里,关键的代码基本都讲完了,我们不妨先来看一下两种实现方式的效果:

通过对比可以发现,ThreadPool相比AsyncTask,并发能力更强,加载的速度也更快,AsyncTask在加载过程中明显变现出顺序性,加载的速度要慢一些。
下面是调用两种加载方式的MainActivity的所有代码:
- package com.carrey.asyncloaddemo;
- import android.app.Activity;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.Button;
- import android.widget.GridView;
- import com.carrey.customview.customview.CustomView;
- /**
- * 异步加载的两种方式:AsyncTask与ThreadPool
- * @author carrey
- *
- */
- public class MainActivity extends Activity implements ThreadPoolTaskBitmap.CallBack {
- /** 服务器地址 */
- public static String webServerStr;
- private static final String TAG = "MainActivity";
- private LayoutInflater inflater;
- private Button btnAsync;
- private Button btnPool;
- private GridView gridView;
- private GridAdapter adapter;
- /** 加载方式 */
- private int loadWay;
- private static final int LOAD_ASYNC = 1;
- private static final int LOAD_POOL = 2;
- private ThreadPoolManager poolManager;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- loadWay = LOAD_ASYNC;
- webServerStr = getResources().getString(R.string.web_server);
- inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- btnAsync = (Button) findViewById(R.id.btn_async);
- btnAsync.setOnClickListener(new AsyncButtonClick());
- btnPool = (Button) findViewById(R.id.btn_pool);
- btnPool.setOnClickListener(new PoolButtonClick());
- gridView = (GridView) findViewById(R.id.gridview);
- adapter = new GridAdapter();
- gridView.setAdapter(adapter);
- poolManager = new ThreadPoolManager(ThreadPoolManager.TYPE_FIFO, 5);
- }
- private class AsyncButtonClick implements OnClickListener {
- @Override
- public void onClick(View v) {
- loadWay = LOAD_ASYNC;
- adapter.notifyDataSetChanged();
- }
- }
- private class PoolButtonClick implements OnClickListener {
- @Override
- public void onClick(View v) {
- loadWay = LOAD_POOL;
- adapter.notifyDataSetChanged();
- }
- }
- private class GridAdapter extends BaseAdapter {
- private Bitmap mBackgroundBitmap;
- public GridAdapter() {
- mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.item_bg);
- }
- @Override
- public int getCount() {
- return 999;
- }
- @Override
- public Object getItem(int position) {
- return null;
- }
- @Override
- public long getItemId(int position) {
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder = null;
- if (convertView == null) {
- holder = new ViewHolder();
- convertView = inflater.inflate(R.layout.item, null);
- holder.customView = (CustomView) convertView.findViewById(R.id.customview);
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder) convertView.getTag();
- }
- holder.customView.position = position;
- holder.customView.setImageBitmap(null);
- holder.customView.setBackgroundBitmap(mBackgroundBitmap);
- if (loadWay == LOAD_ASYNC) {
- holder.customView.setTitleText("AsyncTask");
- holder.customView.setSubTitleText("position: " + position);
- new AsyncLoadTask(holder.customView).execute(position);
- } else if (loadWay == LOAD_POOL) {
- 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));
- }
- return convertView;
- }
- }
- static class ViewHolder {
- CustomView customView;
- }
- @Override
- protected void onDestroy() {
- poolManager.stop();
- super.onDestroy();
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.activity_main, menu);
- return true;
- }
- @Override
- public void onReady(String url, Bitmap bitmap, int position, CustomView view) {
- Log.i(TAG, "thread pool done task: " + url);
- if (view.position == position) {
- view.setImageBitmap(bitmap);
- }
- }
- }
在文章开头我们提到过,我对CustomView做了一些修改,下面做一些说明:
我在控件里添加了一个mDrawableBackground属性,这个Drawable会在渲染图片内容之前渲染,异步加载的时候我会首先将其设置为一个空白的背景,这样在图片加载完成之前我们就会先看到一个白色的背景(或者你自定义一个沙漏或者时钟之类的图片),给用户一种视觉上的延时效果。
除此之外我添加了一个pisition属性,并在最终刷新UI的时候用来做判断,分别位于线程池实现方式中的任务单元执行到最后的回调实现和AsyncTask实现方式的onPostExecute中,如下的代码:
- @Override
- public void onReady(String url, Bitmap bitmap, int position, CustomView view) {
- Log.i(TAG, "thread pool done task: " + url);
- if (view.position == position) {
- view.setImageBitmap(bitmap);
- }
- }
- @Override
- protected void onPostExecute(Pair<Integer, Bitmap> result) {
- if (result.first == view.position) {
- view.setImageBitmap(result.second);
- }
- }
为什么要做这样一个判断呢?这是因为BaseAdapter的convertView是不停复用的,如果我们的滑动非常快,那就会存在这样一种情况,有一个convertView还没有加载完,就会第二次复用了,如果这个时候第二次加载慢于第一次,那结果就会被第一次覆盖,这样就不准确了,所以我们要加一个判断,以确保刷新的准确。
最后贴出源码下载,感兴趣的同学可以下载对比,欢迎留言交流。
本文转载地址http://blog.csdn.net/carrey1989/article/details/12002033
Android 应用开发 之通过AsyncTask与ThreadPool(线程池)两种方式异步加载大量数据的分析与对比--转载的更多相关文章
- Android开发之使用sqlite3工具操作数据库的两种方式
使用 sqlite3 工具操作数据库的两种方式 请尊重他人的劳动成果,转载请注明出处:Android开发之使用sqlite3工具操作数据库的两种方式 http://blog.csdn.net/feng ...
- Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)
Android ScrollView监听滑动到顶部和底部,虽然网上很多资料都有说,但是不全,而且有些细节没说清楚 使用场景: 1. 做一些复杂动画的时候,需要动态判断当前的ScrollView是否滚动 ...
- Android实战简易教程-第四十九枪(两种方式实现网络图片异步加载)
加载图片属于比较耗时的工作,我们需要异步进行加载,异步加载有两种方式:1.通过AsyncTask类进行:2.通过Handler来实现,下面我们就来看一下如何通过这两种方式实现网络图片的异步加载. 一. ...
- Android开发--异步加载
因为移动端软件开发思维模式或者说是开发的架构其实是不分平台和编程语言的,就拿安卓和IOS来说,他们都是移动前端app开发展示数据和用户交互数据的数据终端,移动架构的几个大模块:UI界面展示.本地数据可 ...
- Android学习笔记_36_ListView数据异步加载与AsyncTask
一.界面布局文件: 1.加入sdcard写入和网络权限: <!-- 访问internet权限 --> <uses-permission android:name="andr ...
- Android异步加载
一.为什么要使用异步加载? 1.Android是单线程模型 2.耗时操作阻碍UI线程 二.异步加载最常用的两种方式 1.多线程.线程池 2.AsyncTask 三.实现ListView图文混排 3-1 ...
- 实例演示Android异步加载图片
本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...
- 实例演示Android异步加载图片(转)
本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...
- 演化理解 Android 异步加载图片
原文:http://www.cnblogs.com/ghj1976/archive/2011/05/06/2038738.html#3018499 在学习"Android异步加载图像小结&q ...
随机推荐
- scrapy的 安装 及 流程 转
安装 linux 和 mac 直接 pip install scrapy 就行 windows 安装步骤 a. pip3 install wheel b. 下载twist ...
- matlab中的linkage和cluster函数
Linkage: Agglomerative hierarchical cluster tree(凝聚成层次聚类树) 语法: 解释: Z=linkage(x),返回Z,是一个X矩阵中行的分层聚类树(用 ...
- C - 思考使用差分简化区间操作
FJ's N (1 ≤ N ≤ 10,000) cows conveniently indexed 1..N are standing in a line. Each cow has a positi ...
- 【KMP】【矩阵加速】【递推】洛谷 P3193 [HNOI2008]GT考试 题解
看出来矩阵加速也没看出来KMP…… 题目描述 阿申准备报名参加 GT 考试,准考证号为\(N\)位数\(X_1,X_2…X_n(0\le X_i\le9)\),他不希望准考证号上出现不吉利的数 ...
- no git binary found in $path(已解决,但是还有疑问)
跟同行研究个项目代码,他把代码打包发我后,我解压到本地,路径和我本地个人项目路径基本相同, 但是当执行npm install时,就报了 no git binary found in $path ,这个 ...
- get 与 post 区别
Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP ...
- superobject 设定排序方式
(* * Super Object Toolkit * * Usage allowed under the restrictions of the Lesser GNU General Public ...
- es6 Null 传导运算符
Null 传导运算符 程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在.比如,要读取message.body.user.firstName,安全的写法是写成下面这样. const ...
- mysql大数据表删除操作锁表,导致其他线程等待锁超时(Lock wait timeout exceeded; try restarting transaction;)
背景: 1.有一个定时任务,每10分钟入一批统计数据: 2.另一个定时任务,每天定时清理7天前数据,此定时任务每天01:18:00执行: 现象: 每天01:20:00的统计数据入库失败,异常信息如下, ...
- oracle 集群RAC搭建(四)--grid部署
安装教程: