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 ...
随机推荐
- 1148 Werewolf - Simple Version (20 分)
Werewolf(狼人杀) is a game in which the players are partitioned into two parties: the werewolves and th ...
- Win32 进程间通信的分析与比较
1 进程与进程通信 进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码.数据以及它可利用的系统资源(如文件.管道等)组成.多进程/多线 程是Windows操作系统的一个基本特征.M ...
- Spark Shell启动时遇到<console>:14: error: not found: value spark import spark.implicits._ <console>:14: error: not found: value spark import spark.sql错误的解决办法(图文详解)
不多说,直接上干货! 最近,开始,进一步学习spark的最新版本.由原来经常使用的spark-1.6.1,现在来使用spark-2.2.0-bin-hadoop2.6.tgz. 前期博客 Spark ...
- Steamworks and Unity – P2P多人游戏
之前我们讨论过“如何把Steamworks.Net和Unity整合起来”,这是一个很好的开始,现在我们研究深一点,谈一谈Steam中的多人游戏.这不是教程,但是可以指导你在你的游戏中如何使用Steam ...
- 迪米特法則 Law of Demeter
又稱為"最小知識"原則, 若對Law of Demeter做一個簡單總結: 任何對象的任何方法只能調用以下對象中的方法: (1) 該對象本身 (2) 所傳入的參數對象 (3) 它所 ...
- 《腾讯游戏人生》微信小程序开发总结
为打通游戏人生擂台赛与线下商家的O2O衔接,同时响应时下日臻火热的微信小程序,项目团队决定也开发一款针对性的微信小程序,以此方便商家在我们平台入驻并进行擂台赛事的创建和奖励的核销,进一步推广擂台赛的玩 ...
- React.js 小书 Lesson12 - state vs props
作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson12 转载请注明出处,保留原文链接和作者信息. 我们来一个关于 state 和 props 的 ...
- 使用pygame开发一个弹幕射击游戏(一)
本文作为开发过程记录用. 目前游戏画面: 下一个添加的功能:敌机可以进行射击. 弹幕类 from pygame.sprite import Sprite from pygame import tran ...
- vim 配置文件——部分配置
//vim 相关 set nu set showmatch set autoindent set smartindent set ruler set incsearch set tabstop=4 s ...
- nyoj 1208——水题系列——————【dp】
水题系列 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 给你一个有向图,每条边都有一定的权值,现在让你从图中的任意一点出发,每次走的边的权值必须必上一次的权 ...