AsyncTask异步任务的使用(指定线程数)与线程池的使用
为了更加方便我们在子线程中对 UI 进行操作,Android 还提供了另外一些好用的工
具,AsyncTask(异步任务)就是其中之一。借助 AsyncTask,即使你对异步消息处理机制完全不了解,
也可以十分简单地从子线程切换到主线程。当然,AsyncTask 背后的实现原理也是基于异步
消息处理机制的,只是 Android 帮我们做了很好的封装而已。
首先来看一下 AsyncTask 的基本用法,由于 AsyncTask 是一个抽象类,所以如果我们想
使用它,就必须要创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型
参数,这三个参数的用途如下。
1. Params
在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
2. Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
3. Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
因此,一个最简单的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
这里我们把 AsyncTask 的第一个泛型参数指定为 Void,表示在执行 AsyncTask 的时候不
需要传入参数给后台任务。第二个泛型参数指定为 Integer,表示使用整型数据来作为进度显
示单位。第三个泛型参数指定为 Boolean,则表示使用布尔型数据来反馈执行结果。
当然,目前我们自定义的 DownloadTask 还是一个空任务,并不能进行任何实际的操作,
我们还需要去重写 AsyncTask 中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下四个。
1. onPreExecute() 准备执行
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比
如显示一个进度条对话框等。
2. doInBackground(Params...) 在后台执行
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任
务。任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask 的
第三个泛型参数指定的是 Void,就可以不返回任务执行结果。注意,在这个方法中是不
可以进行 UI 操作的,如果需要更新 UI元素,比如说反馈当前任务的执行进度,可以调
用 publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...) 进度更新
当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调
用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操
作,利用参数中的数值就可以对界面元素进行相应地更新。
注意:直接调用此方法是在子线程中运行,调用publishProgress(Progress...)执行此方法时是在主线程运行的
4. onPostExecute(Result) 主线程中执行的,处理后台返回的数据
当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返
回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如
说提醒任务执行的结果,以及关闭掉进度条对话框等。
因此,一个比较完整的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> { @Override protected void onPreExecute() { progressDialog.show(); // 显示进度对话框 } @Override protected Boolean doInBackground(Void... params) { try { while (true) { int downloadPercent = doDownload(); //这是一个虚构的方法,返回下载进度 publishProgress(downloadPercent);//将执行onProgressUpdate方法 if (downloadPercent >= 100) { break; } } } catch (Exception e) { return false; } return true; } @Override protected void onProgressUpdate(Integer... values) { // 在这里更新下载进度 progressDialog.setMessage("Downloaded " + values[0] + "%"); } @Override protected void onPostExecute(Boolean result) { progressDialog.dismiss(); // 关闭进度对话框 // 在这里提示下载结果 if (result) { Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT) .show(); } } }
在这个 DownloadTask 中,我们在 doInBackground()方法里去执行具体的下载任务。这个
方法里的代码都是在子线程中运行的,因而不会影响到主线程的运行。注意这里虚构了一个
doDownload()方法,这个方法用于计算当前的下载进度并返回,我们假设这个方法已经存在
了。在得到了当前的下载进度后,下面就该考虑如何把它显示到界面上了,由于
doInBackground()方法是在子线程中运行的,在这里肯定不能进行 UI 操作,所以我们可以调
用 publishProgress()方法并将当前的下载进度传进来,这样 onProgressUpdate()方法就会很快
被调用,在这里就可以进行 UI 操作了。
当下载完成后,doInBackground()方法会返回一个布尔型变量,这样 onPostExecute()方
法就会很快被调用,这个方法也是在主线程中运行的。然后在这里我们会根据下载的结果来
弹出相应的 Toast 提示,从而完成整个 DownloadTask 任务。
简单来说,使用 AsyncTask 的诀窍就是,在 doInBackground()方法中去执行具体的耗时
任务,在 onProgressUpdate()方法中进行 UI 操作,在 onPostExecute()方法中执行一些任务的
收尾工作。
如果想要启动这个任务,只需编写以下代码即可:
new DownloadTask().execute();
以上就是 AsyncTask 的基本用法,怎么样,是不是感觉简单方便了许多?我们并不需要
去考虑什么异步消息处理机制,也不需要专门使用一个 Handler 来发送和接收消息,只需要
调用一下 publishProgress()方法就可以轻松地从子线程切换到 UI 线程了。
例子:
public class MainActivity extends Activity { private ImageView image; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.activity_main); setProgressBarVisibility(true); image = (ImageView) findViewById(R.id.image); } //按钮的单击事件 public void download(View v) { //开始执行异步任务 new MyAsyncTask() .execute("http://a.hiphotos.baidu.com/image/h%3D200/sign=4da4ff1895ef76c6cfd2fc2bad16fdf6/f9dcd100baa1cd11daf25f19bc12c8fcc3ce2d46.jpg"); } // 子线程参数 进度 子线程返回结果给主线程 class MyAsyncTask extends AsyncTask<String, Integer, Bitmap> { private ProgressDialog progressDialog; /** * 主线程中运行的,做准备工作 */ @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("图片下载"); progressDialog.setMessage("正在拼命下载..."); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//设置进度条风格为水平的 progressDialog.show();//进度条的值需要在对话框显示之前显示才有效 } /** * 后台处理耗时操作 */ @Override protected Bitmap doInBackground(String... params) { try { HttpClient http = new DefaultHttpClient(); HttpGet get = new HttpGet(params[0]); HttpResponse response = http.execute(get); if (response.getStatusLine().getStatusCode() == 200) { int fileLength=(int) response.getEntity().getContentLength();//获取文件总大小 byte[] arr=new byte[128]; int length,downNum=0; InputStream is = response.getEntity().getContent(); ByteArrayOutputStream baos=new ByteArrayOutputStream();//存放下载的数据 while((length=is.read(arr))!=-1){ baos.write(arr, 0, length); downNum+=length;//下载的字节数 int progress=downNum*100/fileLength;//得到比例 publishProgress(progress);//更新进度 } byte[] byteArray = baos.toByteArray(); return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length); } } catch (IOException e) { e.printStackTrace(); } return null; } /** *通过publishProgress方法来回调此方法,该方法是在主线程运行 *如果是直接调用这个方法,该方法是在子线程中运行的 *一般用来更新UI界面 *注意:精度条可以在子线程更新,因为其内部是在主线程处理的,这里只是演示使用 */ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); progressDialog.setProgress(values[0]); } /** * 参数跟AsyncTask类的第三个泛型参数一致 */ @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); progressDialog.dismiss();//关闭对话框的显示 // 在这里提示下载结果 if (result != null) { image.setImageBitmap(result); } else { Toast.makeText(MainActivity.this, "下载错误", 0).show(); } } } }
效果:
在3.0之前的AsyncTask可以同时有5个任务在执行,而3.0之后的AsyncTask同时只能有1个任务在执行。为什么升级之后可以同时执行的任务数反而变少了呢?这是因为更新后的AsyncTask已变得更加灵活,如果不想使用默认的线程池,还可以自由地进行配置.
下面验证3.0后默认状态下AsyncTask同时只能有一个任务执行:
public class MainActivity extends AppCompatActivity { private ImageView img; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); img = (ImageView) MainActivity.this.findViewById(R.id.imageView); String imgUrl="http://img1.3lian.com/2015/w2/14/d/61.jpg"; new DownloadImgToNet().execute(imgUrl);//异步下载 new DownloadImgToNet().execute(imgUrl);//异步下载 new DownloadImgToNet().execute(imgUrl);//异步下载 } /** * 通过异步任务下载图片并保存到磁盘 */ class DownloadImgToNet extends AsyncTask<String,Void,Bitmap>{ @Override protected Bitmap doInBackground(String... params) { Log.i("tag","线程:"+Thread.currentThread().getName()+"开始下载"); try { String imgUrl=params[0];//获取网络地址 URL url=new URL(imgUrl); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); ByteArrayOutputStream bos=new ByteArrayOutputStream(); InputStream is = connection.getInputStream(); int b; while((b=is.read())!=-1){ bos.write(b); bos.flush();//为了耗时表现出效果,这里一个字节一个字节的读 } BitmapFactory.decodeByteArray(bos.toByteArray(),0,bos.toByteArray().length); } catch (IOException e) { e.printStackTrace(); } Log.i("tag","线程:"+Thread.currentThread().getName()+"下载完毕"); return null; } } }
这里很简单,开启三个异步任务下载同一个图片,效果如下:
出现这种情况的原因:默认情况下,在整个应用程序中的所有AsyncTask实例都会共用同一个SerialExecutor(管理线程池的),SerialExecutor模仿的是单一线程池的效果,如果我们快速地启动了很多任务,同一时刻只会有一个线程正在执行,其余的均处于等待状态。
解决方法:那么怎么解决同时可以开启多个异步任务下载呢? 肯定就是不用默认的线程池了,修改代码如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); img = (ImageView) MainActivity.this.findViewById(R.id.imageView); String imgUrl="http://img1.3lian.com/2015/w2/14/d/61.jpg"; /** * int corePoolSize,线程池维护线程的最少数量,如果正在下载的个数小于这个数,就会从线程池中获取等待的任务开始执行 * int maximumPoolSize,线程池维护线程的最大数量 * long keepAliveTime,线程池维护线程所允许的空闲时间,如果正在下载的任务超过这个时间还没执行完,就会添加新的任务开始下载 * TimeUnit unit,线程池维护线程所允许的空闲时间的单位,NANOSECONDS(纳秒)、MICROSECONDS(微妙)、MILLISECONDS(毫秒)、SECONDS(秒) * BlockingQueue<Runnable> workQueue 线程池所使用的缓冲队列 */ //创建执行者,指定线程池 Executor exec = new ThreadPoolExecutor(3, 200, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); new DownloadImgToNet().executeOnExecutor(exec,imgUrl);//开启六个线程下载 new DownloadImgToNet().executeOnExecutor(exec,imgUrl); new DownloadImgToNet().executeOnExecutor(exec,imgUrl); new DownloadImgToNet().executeOnExecutor(exec,imgUrl); new DownloadImgToNet().executeOnExecutor(exec,imgUrl); new DownloadImgToNet().executeOnExecutor(exec,imgUrl); }
效果如下:
这样就可以使用我们自定义的一个Executor来执行任务,而不是使用系统默认的SerialExecutor。上述代码的效果允许在同一时刻有3个任务正在执行,并且最多能够存储200个任务。
参数详细说明:
/** * 参数说明: * int corePoolSize,线程池维护线程的最少数量 * int maximumPoolSize,线程池维护线程的最大数量 * long keepAliveTime,线程池维护线程所允许的空闲时间 * TimeUnit unit,线程池维护线程所允许的空闲时间的单位,NANOSECONDS(纳秒)、MICROSECONDS(微妙)、MILLISECONDS(毫秒)、SECONDS(秒) * BlockingQueue<Runnable> workQueue 线程池所使用的缓冲队列,可以指定个数,这里使用的默认数Integer.MAX_VALUE * RejectedExecutionHandler handler,下面有说明 */ //创建执行者,指定线程池 Executor exec = new ThreadPoolExecutor(3, 200, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); /** * 系统的Executor默认使用的AbortPolicy,所以上面等价于下面这个 * Executor exec = new ThreadPoolExecutor(3, 200, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.AbortPolicy()); * handler有下面四个选择: ThreadPoolExecutor.AbortPolicy(),抛出java.util.concurrent.RejectedExecutionException异常 ThreadPoolExecutor.CallerRunsPolicy(),重试添加当前的任务,他会自动重复调用execute()方法 ThreadPoolExecutor.DiscardOldestPolicy(),抛弃旧的任务 ThreadPoolExecutor.DiscardPolicy(),抛弃当前的任务 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。 */
但是有时候我需要立刻执行某个任务,但是它需要等待到其他任务都完成时才得以执行而不是调用executor()之后马上执行。
那么解决方法其实很简单,要么直接使用Thread,要么创建一个单独的线程池(Executors.newCachedThreadPool())。或者最简单的解法就是使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),这样起码不用等到前面的都结束了再执行。
===========================================
线程池的使用:
public class MainActivity extends AppCompatActivity { private ImageView img; /** * 线程池的执行人对象 */ private ExecutorService threadPool; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); img = (ImageView) MainActivity.this.findViewById(R.id.imageView); final String imgUrl="http://img1.3lian.com/2015/w2/14/d/61.jpg"; //开启10个线程从网络下载数据 for (int i = 0; i < 10; i++) { //通过线程池对象来开启子线程 getThreadPool().execute(new Runnable() { @Override public void run() { downloadImg(imgUrl); } }); } //两种关闭线程池的比较: //关闭线程池,不能用threadPool对象执行新的任务,并把等待的任务移除掉,移到list中了,如果有正在执行的,尝试进行停止... List<Runnable> list = getThreadPool().shutdownNow(); Log.i("tag","关闭的任务有"+list.size()+"个"); //关闭线程池,不能用threadPool对象执行新的任务.但是对正在执行或者等待的线程无影响,并不会被终止 getThreadPool().shutdown(); if (threadPool.isShutdown()) { Log.i("tag","关闭了线程池"); threadPool=null; } } /** * 获取指定了线程数的线程池对象 */ public ExecutorService getThreadPool(){ if(threadPool==null){ //创建一个固定的线程池,4代表这个线程池最多有4个线程同时运行,超过的线程将等待下载 threadPool= Executors.newFixedThreadPool(4); } return threadPool; } /** *通过地址,下载图片 */ public Bitmap downloadImg(String imgUrl) { Log.i("tag","线程:"+Thread.currentThread().getName()+"开始下载"); try { URL url=new URL(imgUrl); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); ByteArrayOutputStream bos=new ByteArrayOutputStream(); InputStream is = connection.getInputStream(); int b; while((b=is.read())!=-1){ bos.write(b); bos.flush();//为了耗时表现出效果,这里一个字节一个字节的读 } Thread.currentThread().sleep(4*1000); Bitmap bitmap = BitmapFactory.decodeByteArray(bos.toByteArray(), 0, bos.toByteArray().length); Log.i("tag","线程:"+Thread.currentThread().getName()+"下载完毕"); return bitmap; } catch (IOException e) { e.printStackTrace(); Log.i("tag","异常"); } catch (InterruptedException e) { Log.i("tag","异常22"); e.printStackTrace(); } return null; } }
上面使用到的是execute方法来执行下载的,这种方式没有返回结果,所以下面介绍两种带返回结果的,但是返回了结果也不方便和主线程通信,所以还是异步任务好用
2.executorService.submit(Runnable) 第二种调用方式...这种方式与第一种的区别在于可以使用一个Future对象来判断当前的线程是否执行完毕...但是这种方法只能判断当前的线程是否执行完毕,无法返回数据信息...
Future future = executorService.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); //如果任务结束执行则返回 null System.out.println("future.get()=" + future.get());
3.executorService.submit(Callable)... 第三种调用方式...这种调用方式与前一种有所不同,传递的参数为Callable对象,Callable与Runnbale很相似,但是Callable的call()方法可以返回数据信息...通过Future就能够获取到其中的信息..而Runnbale.run()方法时无法获取数据信息的....Future应用于多线程...可以获取call()方法返回的数据信息...其实他是一种模式,是为了性能优化而提供的一种思想...这里我就不说Future...
uture future = executorService.submit(new Callable(){ public Object call() throws Exception { System.out.println("Asynchronous Callable"); return "Callable Result"; } }); System.out.println("future.get() = " + future.get()); //上述样例代码会输出如下结果: //Asynchronous Callable //future.get() = Callable Result