大话Android多线程(六) AsyncTask知识扫盲

版权声明:本文为博主原创文章,未经博主容许不得转载
源码:github.com/AnliaLee
你们要是看到有错误的地方或者有啥好的建议,欢迎留言评论html

前言

本章咱们将结合以前几篇博客,来研究研究多线程知识综合应用程度很高的AsyncTask类(Android 7.0版本)java

往期回顾
大话Android多线程(一) Thread和Runnable的联系和区别
大话Android多线程(二) synchronized使用解析
大话Android多线程(三) 线程间的通讯机制之Handler
大话Android多线程(四) Callable、Future和FutureTask
大话Android多线程(五) 线程池ThreadPoolExecutor详解android


AsyncTask简介

经过以前几篇博客的学习和研究,咱们知道了要将耗时的任务放到子线程中执行,而后使用Handler机制通知UI线程任务的结果并执行更新UI的操做。若是这些步骤都由咱们本身动手去写,势必会让代码显得很是臃肿git

Android给咱们提供了一种轻量级的异步任务类AsyncTask,该类实现了异步操做,并提供相应的接口反馈异步任务执行结果及进度,实现了从子线程执行任务到通知主线程更新UI的一条龙服务,大大减小了咱们的开发工做。下面咱们将从如何使用开始逐步揭开AsyncTask的神秘面纱github


如何使用AsyncTask

咱们以去快餐店点餐为例。咱们将顾客点餐与取餐的行为放在主线程中(更新UI界面等操做),而服务人员在厨房配餐的行为放在子线程中进行(在后台执行耗时操做多线程

顾客不会关心服务人员在厨房是如何工做的,他们只关心点了什么(配置参数并调用AsyncTask.execute进行提交)以及什么时候收到通知去取餐(在AsyncTask.onPostExecute回调方法中接收后台任务返回的结果,并执行相应的操做)。服务人员在配餐以前能够帮助顾客准备餐盘、纸巾等等,固然这些工做对顾客来讲是可见的(经过重写AsyncTask.onPreExecute回调方法执行一些耗时任务以前的准备工做,该方法运行在主线程中)。准备工做完毕后,服务人员会通知厨房进行配餐,这部分工做对于顾客来讲是不可见的(在AsyncTask.doInBackground方法中编写执行耗时任务的代码,这些耗时任务运行在子线程中)。配餐完毕后,通知顾客来取餐(AsyncTask.doInBackground结束时会返回一个值,该值会传递到AsyncTask.onPostExecute中,证实耗时任务已经执行完毕)并发

整个流程简单总结一下就是 开始任务execute) → 任务准备onPreExecute) → 执行任务doInBackground) → 反馈任务结果回到主线程执行相应操做onPostExecuteapp

下面咱们来看具体的代码(关于使用AsyncTask会致使内存泄漏的问题请看文末的补充,这里的代码只是简单实现就很少赘述了)异步

public class AsyncTaskTestActivity extends AppCompatActivity {
    TextView textShow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task_test);
        textShow = (TextView) findViewById(R.id.text_show);
    }

    public void clickEvent(View view) {
    	switch (view.getId()) {
            case R.id.btn_start:
                List<String> list = new ArrayList<>();
                list.add("薯条");
                list.add("汉堡");
                list.add("可乐");
                new MyAsyncTask().execute(list);
                break;
    	}
    }

    public class MyAsyncTask extends AsyncTask<List<String>,String,String>{
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            textShow.setText("餐盘准备好了,开始配餐...");
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            textShow.setText("配餐完毕," + s);
        }

        @Override
        protected String doInBackground(List<String>... params) {
            String foods = "已经配好的食物 —— ";
            try {
                for (String str : params[0]){
                    Thread.sleep(1000);//模拟配餐的时间
                    foods = foods + str + " ";
                    Log.e("白胡子快餐店",foods);
                }
            }catch (Exception e){}
            return foods;
        }
    }
}
复制代码

运行效果如图所示async

咱们来分析一下上述代码中的细节

使用AsyncTask首先得实现它的子类,咱们先来看下抽象类AsyncTask的部分源码

public abstract class AsyncTask<Params, Progress, Result> 复制代码

这里告诉咱们若是要继承AsyncTask,需配置3个泛型参数的具体类型,这3个参数的介绍以下

  • Params开始执行异步任务时需传入的参数类型,即AsyncTask.execute方法中要传递的参数类型。例如咱们将Params设为String类型,那么将调用 execute(String s) 方法开始任务,提交的参数s类型为String。另外此参数类型一样和传入AsyncTask.doInBackground方法的参数相对应

    ps:若是不须要传递任何参数,则能够将参数类型设为Void,那么开始任务时只须要调用 execute() 便可,下同(返回参数同理)

  • Progress执行异步任务过程当中主线程传递的进度值的类型。咱们能够在AsyncTask.doInBackground方法中调用publishProgress方法告知主线程当前耗时任务的执行进度,咱们设置的进度值类型publishProgress方法要传递参数的类型
  • Result任务执行完毕后,返回的结果类型,即AsyncTask.doInBackground方法返回值的类型,也是AsyncTask.onPostExecute方法传入参数的类型

结合以前的代码,在咱们实现的AsyncTask子类中,Params设为List<String>类型,Progress设为String类型,Result设为String类型

public class MyAsyncTask extends AsyncTask<List<String>,String,String> 复制代码

那么对应的咱们在提交参数开始执行任务时,就须要传入List<String>类型的参数了

List<String> list = new ArrayList<>();
list.add("薯条");
list.add("汉堡");
list.add("可乐");
new MyAsyncTask().execute(list);//这里若传入多个list,在doInBackground中按顺序取参便可
复制代码

doInBackground中获取咱们提交的参数

protected String doInBackground(List<String>... params) {
	for (String str : params[0])//params[0]就是咱们提交的第一个list
	...
	
	return foods;//String foods
}
复制代码

任务执行完毕后,返回一个String类型的值,该值即为onPostExecute方法的传入参数

protected void onPostExecute(String s)//s就是咱们须要的返回值 复制代码

好了,代码的细节分析完毕,下面咱们来看看如何实现任务的进度更新功能

首先咱们须要重写AsyncTask.onProgressUpdate回调方法,将更新进度UI的操做放在这里面

public class MyAsyncTask extends AsyncTask<List<String>,String,String>{
	//省略部分代码...
	@Override
	protected void onProgressUpdate(String... values) {
		super.onProgressUpdate(values);
		textShow.setText(values[0]);
	}
}
复制代码

而后在doInBackground方法中(子线程)调用publishProgress方法就能够把当前任务进度传递到onProgressUpdate中(主线程)了

@Override
protected String doInBackground(List<String>... params) {
	String foods = "已经配好的食物 —— ";
	try {
		for (String str : params[0]){
			Thread.sleep(1000);//模拟配餐的时间
			foods = foods + str + " ";
			publishProgress(foods);//一样这里能够传递多个String类型的参数
		}
		Thread.sleep(500);
	}catch (Exception e){}
	return foods;
}
复制代码

运行结果以下

除了以上这些方法外,AsyncTask还提供了onCancelled回调方法。当咱们调用 cancel(true) 时,doInBackground方法将强制中断任务并调用onCancelledonCancelled被调用时onPostExecute不会被调用),所以咱们能够将取消任务的操做放在onCancelled中,例如

public class MyAsyncTask extends AsyncTask<List<String>,String,String>{
	//省略部分代码...
	@Override
	protected void onCancelled(String s) {
		super.onCancelled(s);
		textShow.setText("未完成配餐," + s);
	}

	@Override
	protected String doInBackground(List<String>... params) {
		String foods = "已经配好的食物 —— ";
		try {
			for (String str : params[0]){
				if(str.equals("可乐")){
					Thread.sleep(500);
					cancel(true);
					while (true){
						/** * cancel方法只是简单把标志位改成true * 最后使用Thread.interrupt去中断线程执行 * 但这不能保证能够立刻中止任务 * 因此需使用isCancelled来判断任务是否被取消 */
						if(isCancelled()){
							return foods;
						}
					}
				}
				...
			}
			Thread.sleep(500);
		}catch (Exception e){}
		return foods;
	}
}
复制代码

运行结果以下

简单总结一下这些AsyncTask中经常使用的方法

  • execute(Params... params):提交参数,开始任务
  • onPreExecute():执行任务以前的准备操做
  • doInBackground(Params... params):在子线程中执行任务,返回任务结果
  • onPostExecute(Result result):接收任务结果,在UI线程主线程)中执行相应操做
  • onProgressUpdate(Progress... values):在UI线程中执行更新进度的操做,配套的提交任务进度的方法为 publishProgress(Progress... values)
  • onCancelled(Result result):接收取消任务时的结果并执行相应的操做,配套的取消中断任务的方法为 cancel(boolean mayInterruptIfRunning)

那么这些方法在AsyncTask内部具体是怎么运做的呢?下面咱们就继续深刻探寻一番吧


AsyncTask内部工做流程

以前咱们简单地总结过一次流程:

开始任务execute) → 任务准备onPreExecute) → 执行任务doInBackground) → 反馈任务结果回到主线程执行相应操做onPostExecute

若是将这个流程继续细化,则以下图所示

从图中咱们能够看到线程池ThreadPoolExecutor,负责线程间通讯的Handler等等。若是有看过我以前几篇博客或者了解过相关知识的童鞋应该很快就能在脑中描绘出AsyncTask整个工做流程的蓝图了。咱们这里就不一行行地分析每一个方法的源码了,只是对照着上图帮你们理清思路,这样你们去看一些源码分析的博客时就没那么头疼了

首先从实现AsyncTask的子类提及,AsyncTask内部有3个状态,它们封装在Status枚举类中,分别是

  • Status.PENDING:在AsyncTask对象建立时就设置了状态为PENDING,表示AsyncTask等待被使用,还没有开始执行任务
  • Status.RUNNING:提交参数开始任务后,状态设置为RUNNING,表示AsyncTask处于执行任务的状态,任务正在运行中
  • Status.FINISHED:任务完成后,状态会设置成FINISHED

须要补充的是,这些状态在整个任务生命周期中只会设置一次,什么时候设置状态已在上图用虚线标出

咱们调用execute方法后,AsyncTask会继续调用executeOnExecutor方法(在此方法中调用了onPreExecute,所以在建立子线程执行任务前就完成了准备操做)并传入默认的任务执行者SERIAL_EXECUTORSerialExecutor),在SerialExecutor中维护着一个任务队列并限制了任务必须单一依次执行。不少博客将SerialExecutor说成是一个线程池,我我的并不赞同这一说法,由于实际上在SerialExecutor中完成建立线程、维护线程这些工做的是一个真正意义上的线程池(THREAD_POOL_EXECUTOR),所以最终提交任务的操做仍是回到了线程池的老路子,调用ThreadPoolExecutor.execute方法将任务入列


Android 7.0版本的AsyncTask默认串行执行任务,那有什么方法能够突破这一限制呢?答案是调用咱们以前提到的executeOnExecutor方法

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) 复制代码

咱们能够跳过execute方法直接调用executeOnExecutor并传入咱们自定义的线程池,这样就能够并发地执行多线程任务了


回到咱们的工做流程,以前讲到调用ThreadPoolExecutor.execute方法提交任务,提交的任务类型为CallableWorkerRunnable),AsyncTask在其call方法中调用doInBackground方法。也就是说,提交任务后,ThreadPoolExecutor建立了子线程,而子线程执行了doInBackground中的耗时任务

任务执行完毕后,按套路使用Handler发送Message通知主线程耗时任务已经完成了(或调用publishProgress方法同样可让Handler发送消息通知主线程执行更新进度的操做),以后的事件就是根据发送Message的内容决定是执行onPostExecute(若设置了任务取消则执行onCancelled)仍是onProgressUpdate方法了(上图因为位置有限没法体现更新进度这一过程,原理其实是同样的)

那么到这AsyncTask的内部工做流程咱们已经基本过了一遍,若是想要更深刻地了解源码实现的过程,这里向你们推荐几位前辈的博客(因为他们博客更新的时间不一样,你们需注意比对各版本AsyncTask的差别)

Android AsyncTask 源码解析
Android 7.0 AsyncTask分析
以及 Android进阶之光 一书中关于AsyncTask的讲解


一些额外的补充

  1. AsyncTask不一样版本线程池的区别
  2. AsyncTask的缺陷

    相关博客推荐
    AsyncTask的缺陷和问题
    Android多线程-AsyncTask的使用和问题(取消,并行和串行,屏幕切换)

  3. Android实现弱引用AsyncTask,将内存泄漏置之度外

大话Android多线程系列到这就暂告一段落了,不知道你们看完这个系列以后收获如何呢?若是以为还行那就多多点赞,而后再买点橘子给博主吃。固然,我就吃两个,剩下的都留给大家哈哈~

最后祝你们新春快乐"狗"到最后事事都旺旺~

相关文章
相关标签/搜索