前言:最近的目标就是跳槽,跳槽完再搭个站,这样的话生活就充实一点点了吧。
今天说的AsyncTask也是项目中常常会用到的,面试中常常会问到的。可是我一般都不用这个的,但是新项目中也需要网络请求框架啊,而且asynctask这种不需要new thread和handler就能实现异步更新,我是非常赞赏的,因为很多网络框架其实都是基于这样的消息通知机制,把网络请求封装在底层,用户只需要重写回调接口的方法即可!
Enum枚举类
经过我长久以来积攒的经验,当分析源码的时候,最好先找一下类中的枚举常量,知道他们大概是代表什么意思,在什么情景下会使用到
在任务的生命周期,任务的每一个的状态只会被指定一次,如果任务还没有被执行,那么状态为PENDING,如果任务正在运行,那么指定为RUNNING,如果任务已经执行完毕了,那么状态指定为FINISHED。一个整体上的了解,AsyncTask有三个状态,分别为运行前,中,后。
AsyncTask
AsyncTask能够在UI线程使用,它不需要操作Thread和Handler就可以实现进行后台操作和在UI线程显示结果。
AsyncTask并不是一个通用的线程框架,而是作为一个在Thread和Handler之间的工具类。AsyncTask理论上用来执行少量的操作(最多花费几秒)。如果需要保持线程运行很长一段时间,强烈建议你使用java.util.concurrent提供的其他的API,比如Executor,ThreadPoolExecutor和FutureTask(有关线程池部分的内容)。
完成一次异步任务,我们在定义的时候可以根据需要定义三种类型的变量Params、Progress、Result,四个步骤onPreExecute、doInBackground、onProgressUpdate和onPostExecute即可。派生一个子类,这个子类必须重写doInBackground方法,通常还会重写的第二个方法是onPostExecute来展示运行的结果。
异步任务中的三种类型的参数分别如下:
Params:在任务执行之前,发送给任务的参数类型
Progress:后台线程计算的时候,输出的进度单元
Result:后台线程计算的结果的类型
哲学参数都是可选的,为了标记一个类型没有被使用,简单的使用Void类型即可,要是什么参数都不需要设定的话,那么使用<Void,Void,Void>即可。
AsyncTask创建——执行的过程:
AsyncTask的创建
AsyncTask是一个抽象类,所以要实例化必须要创建一个继承自AsyncTask的子类,我们在创建这个子类的时候就会指定各种参数的类型,在AsyncTask的构造函数中,就做了两样事情,一个是创建一个Callable对象mWorker,一个是根据mWorker创建一个FutureTask,记住,AsyncTask是一次性的买卖
关于设置线程优先级,setPriority是JDK提供的方法,而Process.setThreadPriority是Android的方法。我们都知道任何线程他们执行的都是Runnable方法,不管是继承Thread,或者是实现Runnable接口,实际上都是实现的run方法,而Callable我们可以理解成的是一个有返回值的run方法,这个Callable到最后也会适配成一个Runnable,适配的工作就是交由FutureTask来完成。
FutureTask是一个管理Callable的类,有关Future相关的字样总是离不开Callable的,在AsyncTask的构造方法内,通过mWorker来创建了一个FutureTask对象
这里先简单介绍一下FutureTask,FutureTask实现了RunnableFuture接口,这个RunnableFuture接口继承了Runnable和Future接口,Future接口里面其实是一些针对Callback的操作,比如cancel,done,get等,用于获取call的结果,取消call等。而FutureTask内部有一个Callable<V>的成员变量callable
再看FutureTask的两个构造函数,可以从这两个构造函数中看出,FutureTask的构造其实就是要初始化callable对象,如果传入的是一个Runnable对象,就同Executors的callable方法适配成callable对象(Executors其实是一个封装好的工具类)
至此我们就获得一个FutureTask,它才是异步任务的大功臣,AsyncTask只是发言代表而已。AsyncTask还添加了一些类似于get,cancel等方法,其实是AsyncTask提供给外部,对FutureTask进行直接控制的方法。get方法分阻塞和超时非阻塞两种,一般使用情况不多。
AsyncTask的运行
当一个异步任务执行的时候,我们会调用execute这个方法,并且任务的执行过程会经历四个步骤:
以下四个方法都是需要默认空实现需要我们重写的方法。
1、onPreExecute(战前动员)
在任务执行之前,在UI线程中调用,这一步骤通常用来安装任务,比如显示一个进度条在用户界面,很多时候我们都没有重写这个方法的。
2、doInBackground
在onPreExecute执行完毕之后就会调用doInBackground,而doInBackground是mWorker中的call回调中被调用,这一步被用来执行耗时的后台计算,AsyncTask的Params参数会传递到这一步骤,计算的结果必须在这一步被返回,当我们调用execute的时候,就会调用起这个方法,执行耗时操作了。
当我们调用AsyncTask的execute时,就会往任务池里面添加一个runnable任务,并且自动执行任务池里的任务,执行完一个的时候执行下一个,正常情况下其实只会有一个任务,那就时构造函数内实现的mFuture对象
3、onProgressUpdate
execute的时候,可以在doInBackground中主动调用publishProgress来输出一个或者多个进度值(Progresser类型值),这些值都可以在在onProgressUpdate这一步直接在UI线程通过进度条等方式显示出来。即使后台计算仍在运行,这个方法可以在UI进程中显示任何形式的进度。比如,可以通过移动一个进度条或者显示一些logs在文本域。
4、onPostExecute
在后台计算结束后,就会在UI线程调用onPostExecute,后台计算的结果会作为Result类型的参数传递到这一步。
在构造AsyncTask的时候,再mWorker中调用postResult来传递任务执行的结果,这个结果是最后的结果了。
容错操作:在FutureTask中方Callable执行完毕的时候调用done方法,在done方法里面当任务执行出错,超时什么的,任务还没执行就down了的话,就会调用起这个方法,要是postResult成功调用了,那么这个方法就不会被调起。反正就是无论如何都要执行postResult,代表这个任务已经结束了。
关于Handler消息机制,大家可以去查看我之前的Handler消息机制小酌。这么就不细说了
5、取消一个任务
可以在任意时间取消一个任务通过调用cancel(boolean state),调用这个方法将会导致后续调用isCanceled()来判断当前任务状态的时候返回true。
调用这个方法之后,任务将不会正常结束,onCanceled(Object)会代替onPostExecute被执行,在doInBackground方法返回的时候。为了确保任务可以尽可能快地取消,应该总是周期性地在doInBackground中检查isCanceled()的返回值,如果可能的话设置一个循环在里面。
使用规则
为了使这个类能正常地使用和运行,有几个规则必须要遵守的:
AsyncTask类必须在UI线程中加载,在API 16中这是自动完成的,任务实例必须在UI线程创建,并且在UI线程调用execute方法,不要主动调用onPreExecute,doInBackground,onProgressUpdate,任务只能够被执行一次,如果试图执行第二次,那么就会抛出异常。
内存可观测
AsyncTask保证所有回调调用在这种方式下都是同步的,所以每一个操作都是安全的,不会出现多线程同时访问的问题,不需要指定为同步:
在构造器中或者在onPreExecute中设置成员变量,并且在doInBackground中引用他们。
在doInBackground中设置成员变量,并且在onProgressUpdate和onPostExecute中引用他们
执行的次序
AsyncTask第一次被介绍的时候,是被连续执行在一个单一后台线程中的。从API4开始,改成使用一个线程池来允许多任务同步执行。自从API 11开始,任务被执行在一个单一的线程来避免同步执行带来的普通应用错误,如果你真的需要同步执行,可以通过threadPoolExecutor调用executeOnExecutor(java.util.concurrent.Executor, Object[])来实现。
线程池部分
私有静态常量(外部不可直接访问)
CPU_COUNT:获取当前可用的cpu的计数。
CORE_POOL_SIZE:线程池中的核心线程数等于CPU总数加1。
MAXIMUN_POOL_SIZE:线程池中最大线程数等于CPU总数的两倍加1。
KEEP_ALIVE:线程执行完毕后是否维持线程。
以上的都是常量部分,用于设置默认的线程池实现。用于创建一个ThreadPoolExecutor对象,对于这些静态常量,同一个子类所创建的AsyncTask实例是共用这些对象的。就好像一开始说的,AsyncTask对象是一次性买卖。上面提到的任务池的作用就是当我们提交多个AsyncTask实例的时候,就会进入队列等待了。
相对于Handler,AsyncTask反而没那么复杂了,当然了,要是加上了线程池部分的,就复杂多了啊,不过线程池也不是一时半刻可以理完了,毕竟有了Thread才有ThreadPool嘛