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

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

    项目的结构如下所示:

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

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

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

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

[java] view plaincopy

  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:

[java] view plaincopy

  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:

[java] view plaincopy

  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这个工具类,这个类主要的功能就是提供获得服务器图片地址和解析图片的方法,具体代码如下:

[java] view plaincopy

  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方法中来执行,其中关键的代码如下:

[java] view plaincopy

  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。代码如下:

[java] view plaincopy

  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异步加载的代码如下:

[java] view plaincopy

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

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

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

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

[java] view plaincopy

  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中,如下的代码:

[java] view plaincopy

  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. }

[java] view plaincopy

  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

时间: 2024-10-24 16:43:20

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

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

使用 sqlite3 工具操作数据库的两种方式 请尊重他人的劳动成果,转载请注明出处:Android开发之使用sqlite3工具操作数据库的两种方式 http://blog.csdn.net/fengyuzhengfan/article/details/40193123 在Android SDK的tools目录下提供了一"sqlite3.exe工具,它是一个简单的SQLite数据库管理工具,类似于MySQL提供的命令行窗口在有些时候,开发者利用该工具来査询. 管理数据库. 下面介绍两种方式: 第

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

加载图片属于比较耗时的工作,我们需要异步进行加载,异步加载有两种方式:1.通过AsyncTask类进行:2.通过Handler来实现,下面我们就来看一下如何通过这两种方式实现网络图片的异步加载. 一.AsyncTask方式 1.main.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.co

自学iOS开发小功能之三:弹框的两种方式(iOS8.3之后新的方式,之前的已经弃用)

1.弹框出现在屏幕中间位置 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"是否退出" preferredStyle: UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActio

浅谈android中的异步加载一

1.为什么需要异步加载. 因为我们都知道在Android中的是单线程模型,不允许其他的子线程来更新UI,只允许UI线程(主线程更新UI),否则会多个线程都去更新UI会造成UI的一个混乱有些耗时的操纵(例如网络请求等),如果直接放到主线程中去请求的话则会造成主线程阻塞,而我们系统有规定的响应时间,当响应的时间超过了了阻塞的时间就会造成"Application No Response",也就是我们熟知的ANR错误解决上述问题的时候:我们一般使用的是线程或者线程池+Handler机制如果线程

Android异步加载

Android异步加载 一.为什么要使用异步加载? 1.Android是单线程模型 2.耗时操作阻碍UI线程 二.异步加载最常用的两种方式 1.多线程.线程池 2.AsyncTask 三.实现ListView图文混排 3-1 实现读取网页中的json数据到ListView中 (图片首先为默认图片) 3.1.1:主布局只有一个ListView和一个listView_item的布局 3.1.2:网页json数据的链接(http://www.imooc.com/api/teacher?type=4&n

OpenCV4Android开发环境搭建两种方式加上图像二值化

1.环境搭建 进行android开发所需要的环境一般为:eclipse + android sdk + ADT,而OpenCV的开发由于需要编写本地代码(C/C++),因此还需要安装以下工具:NDK,Cygwin,CDT.网上都有大量详细的安装讲解,本文只描述下其中关键步骤. 1.1 NDK的安装 (1) NDK下载后解压到固定目录即可,无需安装.本文解压到D盘根目录下,其路径为:D:\android-ndk-r8d: (2) 添加环境变量,将其安装路径添加到系统path变量中,并添加系统变量N

Android 异步加载

Android的Lazy Load主要体现在网络数据(图片)异步加载.数据库查询.复杂业务逻辑处理以及费时任务操作导致的异步处理等方面.在介绍Android开发过程中,异步处理这个常见的技术问题之前,我们简单回顾下Android开发过程中需要注意的几个地方. Android应用开发过程中必须遵循单线程模型(Single Thread Model)的原则.因为Android的UI操作并不是线程安全的,所以涉及UI的操作必须在UI线程中完成.但是并非所有的操作都能在主线程中进行,Google工程师在

演化理解 Android 异步加载图片

图片加载的几种方法如下: 下面测试使用的layout文件: 简单来说就是 LinearLayout 布局,其下放了5个ImageView. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical

android图片截取的两种方式

    文章主要提供android系统上拍照图片截取和图片 选择截取 两种方式,适用于app头像选择等环境. 拍照截取 打开相机 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent,"自定义"); 截图 Intent intent = getCropImageIntent(data); startActivityForResult(intent, "