须菩提。于意云何。如来有所说法不。须菩提白佛言。世尊。如来无所说。
-----佛说
一 AsyncTask 使用
第一步: 首先我们会写一个自己的类继承自AsyncTask ,然后重写doInBackground方法,在继承AsyncTask的时候我们需要传递三个泛型参数,分别是Params, Progress, Result,首先我们详细说明一下这三个参数是什么意思。
Params :这个参数可以传递任意类型,换言之,也就是只要你在doInBackground方法中执行耗时任务的时候需要用到外部对象来调用该对象的某个耗时方法或者当前异步操作需要依赖某个对象的值,这个时候Params 参数就传递你要用到对象的所属类型,因为doInBackground 可变长类型参数里面的类型是由Params 所决定的,所以Params 泛型参数你传递什么类型doInBackground 方法的参数就是什么类型,以上只说明了doInBackground形参的类型,那实参是怎么传递进去的呢?Params类型对应的实际参数是在执行.execute()方法以后传递进去的,后面会用一个示例说明, 参数的传递不仅仅只传递一个参数, 因为doInBackground里面的是个可边长参数,具体类型为Params... params .
Progress:这个参数也可以传递任意类型,什么时候会用到这个参数呢?当子线程执行异步操作的时候,主线程需要实时的知道子线程的执行情况,这个时候就要用到Progress参数,典型应用场景网络文件下载或者文件上传进度显示,下载或者长传文件是耗时操作需要在子线程执行,刷新页面UI需要在主线程执行,这个时候在doInBackground方法中调用publishProgress方法将进度值转递进去,然后会回调onProgressUpdate方法,在onProgressUpdate进行UI操作,为什么在子线程中调用publishProgress后回调的onProgressUpdate却运行在主线程中,后面源码分析我会详细说明。publishProgress和onProgressUpdate方法共享一个类型的参数,换言之就是Progress类型,这个泛型你指定什么如上两个方法的参数类型就是什么。实参可以传递多个因为是可变长参数类型。
Result: 这个参数也可以传递任意类型,但和其他两个参数不同的是Result不是可变参数,只能返回一个结果,使用时机,就是当我们doInBackground的方法的耗时任务执行完毕以后,需要拿到子线程的执行结果的时候,这个时候需要在doInBackground 方法中return 一个Result。这个Result要么成功,要么失败,分别由会调用不同的回调,执行成功return 的Result可以在onPostExecute方法中拿到结果,执行失败直接调用onCancelled,失败了没有结果,如果非想要个结果,那就重写带参数的onCancelled(Result result)方法。onPostExecute调用时机,onCancelled调用时机以及为什么在主线程调用。源码分析会做详细说明。
第二步:针对Params, Progress, Result这三个参数我们用一个例子来演示,例子很简单,就三个学生和一个老师,具体流程是,老师布置作业让学生去做,在学生做作业的过程中,老师需要实时的知道当前是哪个同学在做题目以及这位同学做到了哪个题目,等所有学生做完作业以后,反馈老师学生做题这件事情完成。
创建学生类Student
创建老师类 Teacher
创建任务执行类 WorkAsyncTask
示例运行结果
使用总结
通过以上一个简单的小案例演示了AsyncTask的基本使用流程,以及doInBackground方法执行完毕以后对执行结果的返回,本案例中通过调用isCancelled()方法判断了在doInBackground方法中所执行的任务有没有执行成功,执行成功返回true调用onPostExecute方法,执行失败返回false调用onCancelled()的方法,以上案例只演示了成功的情况,未成功的情况没有演示,那什么时候会执行未成功呢?当在执行doInBackground方法的过程中如果执行的耗时任务抛出了异常,这个时候会执行onCancelled方法。
二 AsyncTask 源码分析
1 构建AsyncTask实例的时候做了哪些事情?
通过第一小部分对AsyncTask的使用,我们知道在使用AsyncTask的时候首先要自己定义一个类然后继承AsyncTask,重写doInBackground,那当我们自己定义一个类在使用的时候也就是new的时候,AsyncTask构造函数都做了那些事情呢?看下面源码
分析:
通过源码可以看出我们在new AsyncTask的时候有三个构造函数,第一个无参,后两个有参,但是我们在使用的时候只能使用无参的构造,因为其他两个构造函数是@hide标注的所以我们无法调用,如果非要调用我们可以通过反射机制去调用(API级别28之前,隐藏的方法仍然可以通过java反射访问,但是没那个必要,真没必要!!),new AsyncTask 以后的执行流程:
第一步创建主线程唯一一个mHandler
AsyncTask()==========>this((Looper) null)====AsyncTask(@Nullable Looper callbackLooper),然后通过mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper);这句构建一个主线程的mHandler ,这个主线程的mHandler 在后续回调执行的onProgressUpdate,onPostExecute,onCancelled发挥了重要的作用。这个mHandler=getMainHandler() ,紧接着我们看getMainHandler() 的源码
在getMainHandler() 里面创建了一个sHandler, sHandler=new InternalHandler(Looper.getMainLooper());接下来我们继续跟踪代码看InternalHandler时什么
由上面的代码可以看出InternalHandler继承自Handler 并且重写了handleMessage方法,在handleMessage方法里面处理子UI相关的操纵。至于handleMessage方法什么时候执行调用下面我会详细说明
第二步创建WorkerRunnable 实例
WorkerRunnable 是什么?WorkerRunnable是一个抽象类但是实现了Callable接口,在异步操作过程中如果我们需要获取到某个线程的执行结果,这个时候我们需要使用Callable + FutureTask来完成,接下来我们看看Callable的call方法做了什么首先会执行mTaskInvoked.set(true);、mTaskInvoked是什么?mTaskInvoked是一个AtomicBoolean的实例对象(全局唯一,用final修饰仅此一个),关于AtomicBoolean由于篇幅原因后期会单独拉一篇来详细说明,这里不做过多解释,mTaskInvoked.set(true)以后会将AtomicBoolean的 value属性设置成1 这个value属性是volatile的保证了线程的可见性,紧接着 Result result =null; 这个Result就是我们传递进去作为返回结果的泛型参数,关于这个参数的说明在AsyncTask 使用第一部分已经做了详细的说明,接下来又执行了Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);这句是做什么的?首先普及一个概念.线程调度机制,线程的调度机制有两种,
1 分时调度模型:所有的线程轮流获取CPU的使用权,平均分配每个线程占用的CPU时间。
2 抢占式调度模型:优先让可运行池中优先级高的线程占用CPU,优先级相同的随机选择一个线程。
Android线程调用模型
1 Adroid里面采用抢占式调度模型,可以通过android.os.Process.setThreadPriority(int)设置线程优先级,参数范围-20----24,数值越小优先级越高,0为默认优先级。
2 线程优先级 默认情况下,新创建的线程与母线程的优先级保持一致。
举例:比如我当前有AB两个线程,A线程为主线程,B线程为在主线程创建的子线程,因为安卓是抢占式调度模型,为了保证UI能够及时响应,这个时候可以把子线程的优先级降低。
通过以上说明,call方法里面的这句Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);就是为了设置线程的优先级。优先级设置完毕以后紧接着就开始执行我们都很熟悉的doInBackground();方法并将doInBackground方法执行的结果用result接收。最后调用了Binder.flushPendingCommands();这句的作用将当前线程中所有待处理的Binder命令刷新到内核驱动程序。 在执行可能长时间阻塞的操作之前调用此方法很有用,以确保释放了所有待处理的对象引用,以防止该过程将对象保留的时间超过所需时间。如果在执行doInBackground的过程中出现异常会怎么办?我们紧接着看看catch里面做了啥,首先会将mCancelled.set(true);mCancelled也是AtomicBoolean类型的,在前面的第一部分说AsyncTask 使用的过程中在 doInBackground方法中调用了 if (isCancelled())return false else return true;来判断当先任务是否执行成功,主要的一句就是根据mCancelled.get()方法,get到的是false 还是ture来进行判断当前任务有没执行成功,如果get到的是true则说明任务执行失败反之则成功,最火在finally代码块里面调用了 postResult(result);方法将doInBackground方法执行的结果传递进去, 从而更新UI,接下来我们看看postResult()的源码
可以看到postResult中出现了我们熟悉的异步消息机制,传递了一个消息message, message.what为MESSAGE_POST_RESULT;message.object= new AsyncTaskResult(this,result); getHandler=mHandler前面讲过。紧接着我们看看AsyncTaskResult时什么
AsyncTaskResult就是一个简单的携带参数的对象。这里的泛型Data=我们使用AsyncTask的时候第三个参数Result 也就是doInBackground方法里面返回的Result。那么当postResult方法执行以后通过mHandler将消息发出去以后会做什么事情呢?收先看图11的代码也就是说当postResult方法执行以后图11 handleMessage方法会执行,然后会执行case语句里面的MESSAGE_POST_RESULT,也就是会执行这句话result.mTask.finish(result.mData[0]);,首先result.mTask拿到AsyncTask对象,然后调用AsyncTask的finish方法并将doInBackground的返回结果传递进去,紧接着我们看看在finish方法里面做了什么。
由上面的代码我们可以看出,当finish方法执行以后,首先会调用isCancelled()进行任务执行校验,如果在执行doInBackground的过程中抛出了异常,会将mCancelled.set(true);这个前面已经说过,isCancelled()任务校验完毕以后,如果返回false则说明任务执行成功,然后回调onPostExecute方法,这下终于明白了onPostExecute方法为什么运行在主线程,为什么在doInBackground方法里面的返回结果会在onPostExecute方法里面拿到了吧,如过isCancelled()返回true则说明任务执行失败回调onCancelled方法。至此整个new WorkerRunnable 的初始化工作以及执行流程分析已接近尾声,但是我们貌似还遗漏了一个东西,那就是当我们在doInBackground方法里面手动调用publishProgress()方法以后,onProgressUpdate方法会回调,并且能实时的和主线程交互,接下来我们分析一下调用publishProgress后做了那些事情,请看源码
通过源码我们可以看出也是通过mHandler发送一个消息,然后回调InternalHandler 的handleMessage方法并且会执行case分支里面的MESSAGE_POST_PROGRESS分支,然后回调AsyncTask的onProgressUpdate方法并将从publishProgress传递过来的值传递过去。到此整个WorkerRunnable的call方法执行流程分析完毕。
mWoker看完了,应该到我们的mFuture了,依然是在构造方法中完成mFuture的初始化,将mWorker作为参数,复写了其done方法。看源码
可以看到当mWorker的call方法执行完毕以后,会回调FutureTask的done方法,done方法执行后、会调用:postResultIfNotInvoked(get());get()表示获取mWorker的call的返回值,即Result.然后看postResultIfNotInvoked方法
如果mTaskInvoked不为true,则执行postResult(执行postResult和上面分析的在call中调用的postResult的流程是一模一样的,因为调用的是同一个方法,这里再不啰嗦);但是在mWorker初始化时就已经将mTaskInvoked为true,所以一般这个postResult执行不到。好了,到了这里,整个AsyncTask的初始化做的事情已经全部分析完毕,不过这里一直是初始化这两个对象的代码,并没有真正的执行。下面我们看真正调用执行的地方。
2 .execute()的时候做了哪些事情?看源码
分析 :当我们.execute()以后首先会执行executeOnExecutor()方法,执行的时候需要一个Executor,也就是sDefaultExecutor,而sDefaultExecutor = SERIAL_EXECUTOR,那么SERIAL_EXECUTOR是谁?由代码可以看出来SERIAL_EXECUTOR就是SerialExecutor,紧接着看SerialExecutor里面是什么,跟随代码我们可以看到首先里面有一个ArrayDeque的队列,然后有个Runnable,紧接我们会发现首先会offer一个任务进去,然后调用scheduleNext();开始执行任务,因为ArrayDeque 是Java里面的一个双端队列的线性实现,由此可以得出AsyncTask只使用于执行立即需要启动并且异步执行的生命周期短暂的使用场景。那么线程池时在哪里被开启的?紧接着我们看executeOnExecutor()内部实现源码。
当execute()调用以后紧接着调用executeOnExecutor()
1当 executeOnExecutor()执行以后首先 设置当前AsyncTask的状态为RUNNING,上面的switch也可以看出,每个异步任务在完成前只能执行一次。
2执行了onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
3 将我们传入的参数赋值给了mWorker.mParams
4 exec.execute(mFuture) ,当这句被调用以后,图24里面的run方法会执行,run方法执行以后,r.run()开始执行,这里的r.run其实调用的就是FutureTask里面的run方法,当FutureTask里面的run方法开始执行以后,Callable的call方法就会执行,因为FutureTask实现了 RunnableFuture接口,而RunnableFuture接口继承了Runnable和Future接口,再FutureTask里面的run方法里面调用了Callable的call方法,call方法执行以后,我们整个AsyncTask就开始运转起来了。到此整个AsyncTask的分析流程全部结束。