Android线程(一) AsyncTask

线程在Android中是一个很重要的概念,从用途来说,线程主要分为主线程和子线程,主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。由于Android的特性,如果在主线程中执行耗时操作那么就会导致程序无法及时的响应,因此耗时操作必须放在子线程中执行,除了Thread本身以外,在Android中可以扮演线程角色的还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。尽管AsyncTask,IntentService和HandlerThread的表现形式都有别于传统的线程,但是它们的本质仍然是传统的线程,对于AsyncTask来水,它的底层用到了线程池,对于IntentService和HandlerThread来说,它们的底层则直接使用了线程。

不同形式的线程虽然都是线程,但是它们仍然具有不同的特性和使用场景。AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI,HandlerThread是一个具有消息循环的线程,在它的内部可以使用Hanlder。IntentService是一个服务,系统对其进行了封装使其可以更方便的执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务完毕后IntentService会自动退出,从任务执行的角度来看,IntentService的作用很像一个后台线程,但是IntentService是一种服务,它不容易被系统杀死从而可以保证任务的执行,而如果是一个后台线程,由于这个时候进程中没有活动的四大组件,那么这个进程的优先级就会非常低,会很容易被系统杀死,这就是IntentService的特点。

在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销,当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行,除非线程数量小于等于CPU的核心数,一般来说这是不可能的,如果在一个进程中频繁的创建和销毁线程,这显然不是高效的做法,正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁消除所带来的系统开销,Android 中的线程池来源于Java,主要是通过Executor来派生特定类型的线程池,不同种类的线程池又具有各自的特性

主线程和子线程


主线程是指进程所拥有的线程,在java中默认情况下以进程只有一个线程,这个线程就是主线程,主线程主要处理界面交互的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,否则就会产生一种界面卡顿的感觉,为了保持较高的响应速度,这就要求主线程中不能执行耗时任务,这个时候子线程就派上用场了,子线程也叫工作线程,除了主线程以外都是子线程

Android 沿用了Java的线程模型,其中的线程也分为主线程和子线程,其中主线程也叫UI线程,主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求,IO操作等 ,从Android 3.0开始系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出异常,这样做是为了避免主线程由于被耗时操作所阻塞而出现ANR现象

Android 中的线程状态


Android中的线程状态除了传统的Thread以外,还包含AsyncTask,HandlerThread以及IntentService,这三者的底层实现也是线程,但是它们具有特殊的表现形式,同时在使用上也各有优缺点,为了简化在子线程中访问UI的过程,系统提供了AsyncTask,AsyncTask经过几次修改,导致了对于不同的API版本AsyncTask具有不同的表现,尤其是多任务的并发执行上,由于这个原因,很多开发者对AsyncTask的使用上存在误区。

AsyncTask是一个抽象的泛型类,它提供了Params,Progress和Result这三个泛型参数,其中Params表示参数的类型,Progress表示后台任务的执行进度的类型,而Result则表示后台任务的返回结果的类型,如果AsyncTask确实不需要传递具体的参数,那么这三个泛型参数可以用Void来代替,AsyncTask这个类的声明如下所示。

8656692-e23385115ae2034f.png

AsyncTask提供了四个核心方法,它们的含义如下所示

1 onPreExcute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作

2 doInBackground(params...params),在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数,在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法,另外此方法需要返回计算结果给onPostExcute方法

3 onProgressUpdate(Progress..values)在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用

4 onPostExecute(Result result),在主线程中执行,接着是doInBackground,最后才是onPostExecute,除了上述四个方法以外,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务被取消时,onCancelled()方法会被调用时,这个时候onPostExecute则不会被调用

8656692-427f3aa050d3b973.png

在上面的代码中,实现了一个具体的AsyncTask类,这个类主要用于模拟文件的下载过程,它的输入参数类型为URL,后台任务的进程参数为Integer,而后台任务的返回结果为Long类型,注意到doInBackground和onProgressUpdate方法,它们的参数中均包含...的字样,在Java中...表示参数的数量不定,它是一种数组型参数,当要执行上述下载任务时,可以通过如下方式来完成

8656692-39ccf89dcb0ebbee.png

在DownloadFilesTask中,doInBackgrounf用来执行具体的下载任务并通过publishProgress方法来更新下载的进度,同时还要判断下载任务是否被外界取消了,当下载任务完成后,doInBackground会返回结果,即下载的总字节数,需要注意的是,doInBackground是在线程池中执行的。onProgressUpdate用于更新界面中下载的进度,它运行在主线程,当publishProgress被调用时,此方法就会被调用,当下载任务完成后,onPostExecute方法就会被调用,它也是运行在主线程中,这个时候我们可以在界面上做出一些提示。

AsyncTask在具体的使用过程中也是有一些条件限制的,主要有如下几点;

1 AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程,当然这个过程在Android 4.1及以上版本中已经被系统自动完成,在Android5.0的源码中,可以查看 ActivityThread的main方法,它会调用AsyncTask的init方法,它就满足了AsyncTask的类必须在主线程中进行加载这个条件了

2 AsyncTask的对象必须在主线程中创建

3 execute方法必须在UI线程调用

4 不要在程序中直接调用onPreExecute(),onPostExecute,doInBackground和onProgressUpdate方法

5 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常

AsyncTask的工作原理


为了分许AsyncTask的工作原理,从它的execute方法开始,execute方法又会调用executeOnExecutor方法

8656692-f98c9741aafb6b64.png
8656692-63ec6145ed4e6ad2.png

在上面的代码中,sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的AsyncTask全部都在这个串行的线程池中排队执行,这个排队执行的过程后面会再进行分析,在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先执行,然后线程池开始执行

8656692-1a25f1abc402fc9d.png
8656692-f3a70d3c8785a8fb.png

从SerialExecutor的实现可以分析AsyncTask的排队执行的过程,首先系统会把AsyncTask的Params参数封装成FutureTask对象,FutureTask是一个并发类,在这里它充当了Runnable的作用,接着这个FutureTask会交给SerialExcetor的execute方法处理,SerialExecutor 的execute方法首先会把FutureTask对象插入到任务队列mTasks,如果这个时候没有正在活动的AsyncTask任务,同时当一个AsyncTask任务执行完后,AsyncTask会继续执行其他任务直到所有的任务被执行为止,在默认情况下,AsyncTask是串行执行的

AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正的执行任务,InternalHandler用于将执行环境从线程池切换到主线程,其本质仍然是线程的调用过程,在AsyncTask的构造方法中如下一段代码,由于FutureTask的run方法会调用mWorker的call方法,因此mWorker的call方法最终会在线程池中执行

8656692-601f678c8753bfba.png

在mWorker的call方法中,首先将mTaskInvoker设为true,表示当前任务已经被调用过了,然后执行AsyncTask的doInBackground方法,接着将其返回值传递给postResult方法

8656692-3f8642adfef24c72.png

在上面的代码中。postResult方法会通过sHanlder发送一个MESSAGE_POST_RESULT的消息,这个sHandler的定义如下所示。

8656692-e1426c7159350b2b.png
8656692-3da33665b64144c5.png

可以发现,sHandler是一个静态的Handler对象,为了能够将执行环境切换到主线程,这就要求sHandler这个对象必须在主线程中创建,由于静态成员会在加载类的时候进行初始化,因此这就变相要求AsyncTask的类必须在主线程中加载,否则同一个进程中的AsyncTask都将无法正常工作,sHandler收到MESSAHE_POSY_RESULT这个消息后会调用AsyncTask的finish方法

8656692-f6d1719805efceba.png

AsyncTask的finish方法的逻辑比较简单,如果AsyncTask被取消执行了,那么就调用onCancelled方法,否则就会调用onPostExecute方法,可以看到doInBackground的返回结果会传递给onPostExecute方法。

猜你喜欢

转载自blog.csdn.net/weixin_34270606/article/details/87218676