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

    项目的结构如下所示:

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

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

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

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

  1. package com.carrey.asyncloaddemo;
  2. import java.util.LinkedList;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. import android.util.Log;
  6. /**
  7. * 线程池管理类
  8. * @author carrey
  9. *
  10. */
  11. public class ThreadPoolManager {
  12. private static final String TAG = "ThreadPoolManager";
  13. /** 线程池的大小 */
  14. private int poolSize;
  15. private static final int MIN_POOL_SIZE = 1;
  16. private static final int MAX_POOL_SIZE = 10;
  17. /** 线程池 */
  18. private ExecutorService threadPool;
  19. /** 请求队列 */
  20. private LinkedList<ThreadPoolTask> asyncTasks;
  21. /** 工作方式 */
  22. private int type;
  23. public static final int TYPE_FIFO = 0;
  24. public static final int TYPE_LIFO = 1;
  25. /** 轮询线程 */
  26. private Thread poolThread;
  27. /** 轮询时间 */
  28. private static final int SLEEP_TIME = 200;
  29. public ThreadPoolManager(int type, int poolSize) {
  30. this.type = (type == TYPE_FIFO) ? TYPE_FIFO : TYPE_LIFO;
  31. if (poolSize < MIN_POOL_SIZE) poolSize = MIN_POOL_SIZE;
  32. if (poolSize > MAX_POOL_SIZE) poolSize = MAX_POOL_SIZE;
  33. this.poolSize = poolSize;
  34. threadPool = Executors.newFixedThreadPool(this.poolSize);
  35. asyncTasks = new LinkedList<ThreadPoolTask>();
  36. }
  37. /**
  38. * 向任务队列中添加任务
  39. * @param task
  40. */
  41. public void addAsyncTask(ThreadPoolTask task) {
  42. synchronized (asyncTasks) {
  43. Log.i(TAG, "add task: " + task.getURL());
  44. asyncTasks.addLast(task);
  45. }
  46. }
  47. /**
  48. * 从任务队列中提取任务
  49. * @return
  50. */
  51. private ThreadPoolTask getAsyncTask() {
  52. synchronized (asyncTasks) {
  53. if (asyncTasks.size() > 0) {
  54. ThreadPoolTask task = (this.type == TYPE_FIFO) ?
  55. asyncTasks.removeFirst() : asyncTasks.removeLast();
  56. Log.i(TAG, "remove task: " + task.getURL());
  57. return task;
  58. }
  59. }
  60. return null;
  61. }
  62. /**
  63. * 开启线程池轮询
  64. * @return
  65. */
  66. public void start() {
  67. if (poolThread == null) {
  68. poolThread = new Thread(new PoolRunnable());
  69. poolThread.start();
  70. }
  71. }
  72. /**
  73. * 结束轮询,关闭线程池
  74. */
  75. public void stop() {
  76. poolThread.interrupt();
  77. poolThread = null;
  78. }
  79. /**
  80. * 实现轮询的Runnable
  81. * @author carrey
  82. *
  83. */
  84. private class PoolRunnable implements Runnable {
  85. @Override
  86. public void run() {
  87. Log.i(TAG, "开始轮询");
  88. try {
  89. while (!Thread.currentThread().isInterrupted()) {
  90. ThreadPoolTask task = getAsyncTask();
  91. if (task == null) {
  92. try {
  93. Thread.sleep(SLEEP_TIME);
  94. } catch (InterruptedException e) {
  95. Thread.currentThread().interrupt();
  96. }
  97. continue;
  98. }
  99. threadPool.execute(task);
  100. }
  101. } finally {
  102. threadPool.shutdown();
  103. }
  104. Log.i(TAG, "结束轮询");
  105. }
  106. }
  107. }


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

    ThreadPoolTask.java:

  1. package com.carrey.asyncloaddemo;
  2. /**
  3. * 任务单元
  4. * @author carrey
  5. *
  6. */
  7. public abstract class ThreadPoolTask implements Runnable {
  8. protected String url;
  9. public ThreadPoolTask(String url) {
  10. this.url = url;
  11. }
  12. public abstract void run();
  13. public String getURL() {
  14. return this.url;
  15. }
  16. }


    ThreadPoolTaskBitmap.java:

  1. package com.carrey.asyncloaddemo;
  2. import com.carrey.customview.customview.CustomView;
  3. import android.graphics.Bitmap;
  4. import android.os.Process;
  5. import android.util.Log;
  6. /**
  7. * 图片加载的任务单元
  8. * @author carrey
  9. *
  10. */
  11. public class ThreadPoolTaskBitmap extends ThreadPoolTask {
  12. private static final String TAG = "ThreadPoolTaskBitmap";
  13. private CallBack callBack;
  14. private CustomView view;
  15. private int position;
  16. public ThreadPoolTaskBitmap(String url, CallBack callBack, int position, CustomView view) {
  17. super(url);
  18. this.callBack = callBack;
  19. this.position = position;
  20. this.view = view;
  21. }
  22. @Override
  23. public void run() {
  24. Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
  25. Bitmap bitmap = ImageHelper.loadBitmapFromNet(url);
  26. Log.i(TAG, "loaded: " + url);
  27. if (callBack != null) {
  28. callBack.onReady(url, bitmap, this.position, this.view);
  29. }
  30. }
  31. public interface CallBack {
  32. public void onReady(String url, Bitmap bitmap, int position, CustomView view);
  33. }
  34. }


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

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

  1. package com.carrey.asyncloaddemo;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.net.URL;
  5. import java.net.URLConnection;
  6. import android.graphics.Bitmap;
  7. import android.graphics.BitmapFactory;
  8. import android.util.Log;
  9. /**
  10. * 工具类,用于获得要加载的图片资源
  11. * @author carrey
  12. *
  13. */
  14. public class ImageHelper {
  15. private static final String TAG = "ImageHelper";
  16. public static String getImageUrl(String webServerStr, int position) {
  17. return "http://" + webServerStr + "/" + (position % 50) + ".jpg";
  18. }
  19. /**
  20. * 获得网络图片Bitmap
  21. * @param imageUrl
  22. * @return
  23. */
  24. public static Bitmap loadBitmapFromNet(String imageUrlStr) {
  25. Bitmap bitmap = null;
  26. URL imageUrl = null;
  27. if (imageUrlStr == null || imageUrlStr.length() == 0) {
  28. return null;
  29. }
  30. try {
  31. imageUrl = new URL(imageUrlStr);
  32. URLConnection conn = imageUrl.openConnection();
  33. conn.setDoInput(true);
  34. conn.connect();
  35. InputStream is = conn.getInputStream();
  36. int length = conn.getContentLength();
  37. if (length != -1) {
  38. byte[] imgData = new byte[length];
  39. byte[] temp = new byte[512];
  40. int readLen = 0;
  41. int destPos = 0;
  42. while ((readLen = is.read(temp)) != -1) {
  43. System.arraycopy(temp, 0, imgData, destPos, readLen);
  44. destPos += readLen;
  45. }
  46. bitmap = BitmapFactory.decodeByteArray(imgData, 0, imgData.length);
  47. }
  48. } catch (IOException e) {
  49. Log.e(TAG, e.toString());
  50. return null;
  51. }
  52. return bitmap;
  53. }
  54. }

    到这里准备的工作基本就完成了,接下来的工作就是启动线程池管理器并向任务队列添加我们的加载任务了,这部分工作我们放在GridView的Adapter的getView方法中来执行,其中关键的代码如下:

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


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

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

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

  1. package com.carrey.asyncloaddemo;
  2. import com.carrey.customview.customview.CustomView;
  3. import android.graphics.Bitmap;
  4. import android.os.AsyncTask;
  5. import android.util.Log;
  6. import android.util.Pair;
  7. public class AsyncLoadTask extends AsyncTask<Integer, Void, Pair<Integer, Bitmap>> {
  8. private static final String TAG = "AsyncLoadTask";
  9. /** 要刷新的view */
  10. private CustomView view;
  11. public AsyncLoadTask(CustomView view) {
  12. this.view = view;
  13. }
  14. @Override
  15. protected void onPreExecute() {
  16. super.onPreExecute();
  17. }
  18. @Override
  19. protected Pair<Integer, Bitmap> doInBackground(Integer... params) {
  20. int position = params[0];
  21. String imageUrl = ImageHelper.getImageUrl(MainActivity.webServerStr, position);
  22. Log.i(TAG, "AsyncLoad from NET :" + imageUrl);
  23. Bitmap bitmap = ImageHelper.loadBitmapFromNet(imageUrl);
  24. return new Pair<Integer, Bitmap>(position, bitmap);
  25. }
  26. @Override
  27. protected void onPostExecute(Pair<Integer, Bitmap> result) {
  28. if (result.first == view.position) {
  29. view.setImageBitmap(result.second);
  30. }
  31. }
  32. }

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

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

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

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

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

  1. package com.carrey.asyncloaddemo;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.os.Bundle;
  7. import android.util.Log;
  8. import android.view.LayoutInflater;
  9. import android.view.Menu;
  10. import android.view.View;
  11. import android.view.View.OnClickListener;
  12. import android.view.ViewGroup;
  13. import android.widget.BaseAdapter;
  14. import android.widget.Button;
  15. import android.widget.GridView;
  16. import com.carrey.customview.customview.CustomView;
  17. /**
  18. * 异步加载的两种方式:AsyncTask与ThreadPool
  19. * @author carrey
  20. *
  21. */
  22. public class MainActivity extends Activity implements ThreadPoolTaskBitmap.CallBack {
  23. /** 服务器地址 */
  24. public static String webServerStr;
  25. private static final String TAG = "MainActivity";
  26. private LayoutInflater inflater;
  27. private Button btnAsync;
  28. private Button btnPool;
  29. private GridView gridView;
  30. private GridAdapter adapter;
  31. /** 加载方式 */
  32. private int loadWay;
  33. private static final int LOAD_ASYNC = 1;
  34. private static final int LOAD_POOL = 2;
  35. private ThreadPoolManager poolManager;
  36. @Override
  37. protected void onCreate(Bundle savedInstanceState) {
  38. super.onCreate(savedInstanceState);
  39. setContentView(R.layout.activity_main);
  40. loadWay = LOAD_ASYNC;
  41. webServerStr = getResources().getString(R.string.web_server);
  42. inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  43. btnAsync = (Button) findViewById(R.id.btn_async);
  44. btnAsync.setOnClickListener(new AsyncButtonClick());
  45. btnPool = (Button) findViewById(R.id.btn_pool);
  46. btnPool.setOnClickListener(new PoolButtonClick());
  47. gridView = (GridView) findViewById(R.id.gridview);
  48. adapter = new GridAdapter();
  49. gridView.setAdapter(adapter);
  50. poolManager = new ThreadPoolManager(ThreadPoolManager.TYPE_FIFO, 5);
  51. }
  52. private class AsyncButtonClick implements OnClickListener {
  53. @Override
  54. public void onClick(View v) {
  55. loadWay = LOAD_ASYNC;
  56. adapter.notifyDataSetChanged();
  57. }
  58. }
  59. private class PoolButtonClick implements OnClickListener {
  60. @Override
  61. public void onClick(View v) {
  62. loadWay = LOAD_POOL;
  63. adapter.notifyDataSetChanged();
  64. }
  65. }
  66. private class GridAdapter extends BaseAdapter {
  67. private Bitmap mBackgroundBitmap;
  68. public GridAdapter() {
  69. mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.item_bg);
  70. }
  71. @Override
  72. public int getCount() {
  73. return 999;
  74. }
  75. @Override
  76. public Object getItem(int position) {
  77. return null;
  78. }
  79. @Override
  80. public long getItemId(int position) {
  81. return 0;
  82. }
  83. @Override
  84. public View getView(int position, View convertView, ViewGroup parent) {
  85. ViewHolder holder = null;
  86. if (convertView == null) {
  87. holder = new ViewHolder();
  88. convertView = inflater.inflate(R.layout.item, null);
  89. holder.customView = (CustomView) convertView.findViewById(R.id.customview);
  90. convertView.setTag(holder);
  91. } else {
  92. holder = (ViewHolder) convertView.getTag();
  93. }
  94. holder.customView.position = position;
  95. holder.customView.setImageBitmap(null);
  96. holder.customView.setBackgroundBitmap(mBackgroundBitmap);
  97. if (loadWay == LOAD_ASYNC) {
  98. holder.customView.setTitleText("AsyncTask");
  99. holder.customView.setSubTitleText("position: " + position);
  100. new AsyncLoadTask(holder.customView).execute(position);
  101. } else if (loadWay == LOAD_POOL) {
  102. holder.customView.setTitleText("ThreadPool");
  103. holder.customView.setSubTitleText("position: " + position);
  104. poolManager.start();
  105. String imageUrl = ImageHelper.getImageUrl(webServerStr, position);
  106. poolManager.addAsyncTask(new ThreadPoolTaskBitmap(imageUrl, MainActivity.this, position, holder.customView));
  107. }
  108. return convertView;
  109. }
  110. }
  111. static class ViewHolder {
  112. CustomView customView;
  113. }
  114. @Override
  115. protected void onDestroy() {
  116. poolManager.stop();
  117. super.onDestroy();
  118. }
  119. @Override
  120. public boolean onCreateOptionsMenu(Menu menu) {
  121. // Inflate the menu; this adds items to the action bar if it is present.
  122. getMenuInflater().inflate(R.menu.activity_main, menu);
  123. return true;
  124. }
  125. @Override
  126. public void onReady(String url, Bitmap bitmap, int position, CustomView view) {
  127. Log.i(TAG, "thread pool done task: " + url);
  128. if (view.position == position) {
  129. view.setImageBitmap(bitmap);
  130. }
  131. }
  132. }

    在文章开头我们提到过,我对CustomView做了一些修改,下面做一些说明:

    我在控件里添加了一个mDrawableBackground属性,这个Drawable会在渲染图片内容之前渲染,异步加载的时候我会首先将其设置为一个空白的背景,这样在图片加载完成之前我们就会先看到一个白色的背景(或者你自定义一个沙漏或者时钟之类的图片),给用户一种视觉上的延时效果。

    除此之外我添加了一个pisition属性,并在最终刷新UI的时候用来做判断,分别位于线程池实现方式中的任务单元执行到最后的回调实现和AsyncTask实现方式的onPostExecute中,如下的代码:

  1. @Override
  2. public void onReady(String url, Bitmap bitmap, int position, CustomView view) {
  3. Log.i(TAG, "thread pool done task: " + url);
  4. if (view.position == position) {
  5. view.setImageBitmap(bitmap);
  6. }
  7. }
  1. @Override
  2. protected void onPostExecute(Pair<Integer, Bitmap> result) {
  3. if (result.first == view.position) {
  4. view.setImageBitmap(result.second);
  5. }
  6. }

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

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

源码下载

    本文转载地址http://blog.csdn.net/carrey1989/article/details/12002033

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

  1. Android开发之使用sqlite3工具操作数据库的两种方式

    使用 sqlite3 工具操作数据库的两种方式 请尊重他人的劳动成果,转载请注明出处:Android开发之使用sqlite3工具操作数据库的两种方式 http://blog.csdn.net/feng ...

  2. Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)

    Android ScrollView监听滑动到顶部和底部,虽然网上很多资料都有说,但是不全,而且有些细节没说清楚 使用场景: 1. 做一些复杂动画的时候,需要动态判断当前的ScrollView是否滚动 ...

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

    加载图片属于比较耗时的工作,我们需要异步进行加载,异步加载有两种方式:1.通过AsyncTask类进行:2.通过Handler来实现,下面我们就来看一下如何通过这两种方式实现网络图片的异步加载. 一. ...

  4. Android开发--异步加载

    因为移动端软件开发思维模式或者说是开发的架构其实是不分平台和编程语言的,就拿安卓和IOS来说,他们都是移动前端app开发展示数据和用户交互数据的数据终端,移动架构的几个大模块:UI界面展示.本地数据可 ...

  5. Android学习笔记_36_ListView数据异步加载与AsyncTask

    一.界面布局文件: 1.加入sdcard写入和网络权限: <!-- 访问internet权限 --> <uses-permission android:name="andr ...

  6. Android异步加载

    一.为什么要使用异步加载? 1.Android是单线程模型 2.耗时操作阻碍UI线程 二.异步加载最常用的两种方式 1.多线程.线程池 2.AsyncTask 三.实现ListView图文混排 3-1 ...

  7. 实例演示Android异步加载图片

    本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...

  8. 实例演示Android异步加载图片(转)

    本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...

  9. 演化理解 Android 异步加载图片

    原文:http://www.cnblogs.com/ghj1976/archive/2011/05/06/2038738.html#3018499 在学习"Android异步加载图像小结&q ...

随机推荐

  1. asp 文章内容里的图片宽度过大 撑爆页面布局 解决办法

    有时候帮朋友做做企业网站,还是asp+access来的快,也经济(不用开数据库空间),fck做的后台内容编辑功能,但是他们传图片的时候不靠谱,图片不管有多宽都直接up上来,把前台页面撑的是面目全非! ...

  2. C++_类入门1-对象和类的介绍

    面向对象是(OOP)是特殊的.设计程序的概念性方法:包含以下特性: 抽象: 封装和数据隐藏: 多态: 继承: 代码的可重用性: 为了实现这些特性并且将这些特性组合在一起,C++所做的最重要的改进是提供 ...

  3. SQL 随手记

    SQL 学习片段: 建立一个简单的联系数据表, mobile_number char(11).mobile_province nvarchar(50).mobile_area nvarchar(200 ...

  4. windows cmd 切换磁盘

    抛砖引玉 切换到D盘根目录——cd /d D: 切换到D:\dev目录——cd  /d  D:\dev

  5. codeforces 1072D Minimum path bfs+剪枝 好题

    题目传送门 题目大意: 给出一幅n*n的字符,从1,1位置走到n,n,会得到一个字符串,你有k次机会改变某一个字符(变成a),求字典序最小的路径. 题解: (先吐槽一句,cf 标签是dfs题????) ...

  6. UVA - 10780 唯一分解定理

    白书P171 对m,n!分解,质因子指数取min #include<iostream> #include<algorithm> #include<cstdio> # ...

  7. vim的多窗口功能与环境参数设置

    Vim的多窗口功能 多窗口情况下按键功能 :sp [filename] 打开一个新的窗口,如果有加filename,表示在新窗口打开一个新文件,否则表示两个窗口为同一文件内容 :[ctrl]+w+j( ...

  8. linux 运维基础之VM中安装centos6.X

    VM中安装centos详细教程 图片讲解:

  9. zabbix 报警程序

    一,报警程序 本次使用的为onealert http://c.onealert.com/console/ucid/login.jsp 二,服务端安转 下面为他教的怎么安装这个东西 第一步: 找到脚本目 ...

  10. Beam概念学习系列之Pipeline 数据处理流水线

    不多说,直接上干货! Pipeline 数据处理流水线 Pipeline将Source PCollection ParDo.Sink组织在一起形成了一个完整的数据处理的过程. Beam概念学习系列之P ...