记录一下11章Android的线程和线程池的学习过程。
简介
- 线程是Android中的一个很重要的概念,从用途上来说,线程分为主线程和子线程,主线程主要处理界面相关的事情,而子线程则往往用于执行耗时的操作。
- Android中可以扮演线程的角色还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。对于AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlerThread来说,它们的底层则直接使用了线程。
- 在操作系统中,线程是操作系统调度的最小单元,线程是一种受限制的系统资源,不能无限制地产生,并且线程的创建和销毁都会有相应的开销。如果在一个进程中频繁地创建和销毁线程,则应该采用线程池。
- 线程池中会缓存一定数量的线程,通过线程池可以避免因为频繁创建和销毁线程所带来的系统开销,Android中的线程池来源于Java,主要通过Executor来派生特定类型的线程池。
主线程和子线程
- 主线程是指进程所拥有的线程,在Java中默认情况下一个进程只有一个线程,这个线程就是主线程。
- 主线程主要处理界面交互的相关逻辑,因为用户随时会和界面发生交互,因此住线程在任何时候都要有较高的相应速度,否则就会产生界面的卡顿。为了保持较高的响应速度,主线程中不能执行耗时的任务,这时候,子线程就派上用场。
- Android沿用了Java的线程模型,其中的线程也分为主线程和子线程,主线程也叫UI线程。主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用是执行耗时任务,比如网络请求、I/O操作等。
Android中的线程形态
书中义工介绍了三种,分别是
- AsyncTask
- HandlerThread
- IntentService
首先是AsyncTask:
AsyncTask
AsyncTask是一种轻量级的异步任务类,可以在线程池中之行后台任务,然后把之行的进度和最终结果传递给主线程并在主线程中更新UI。
AsyncTask封装了Thread和Handler,底部实现是通过线程池,但是AsyncTask并不适合进行特别耗时的任务,对于特别耗时的任务建议使用线程池。
首先来看AsyncTask的声明:
|
|
这是一个抽象泛型类,提供了Params,Progress和Result三个参数,其中Params表示参数的类型,Progress表示后台任务的执行进度的类型,Result则表示后台任务的返回结果的类型。如果不需要传递具体的参数,那么三个泛型参数可以用Void来代替。
AsyncTask的4个核心方法
- onPreExecute()
- doInBackground(Params… params)
- onProgressUpdate(Progress… values)
- onPostExecute(Result result)
首先是onPreExecute,该方法之行在主线程中,在异步任务之行之前,该方法会被调用,一般用于做一些准备工作。
doInBackground方法在线程池中之行,该方法用于执行异步任务,params表示异步任务的输入参数。该方法需要返回结果给onPostExecute方法,在该方法中可以通过publishProgress方法来更行任务的进度,publishProgress方法会调用onProgressUpdate方法。
onProgressUpdate方法在主线程中之行,当后台任务的执行发生变化的时候,该方法会被调用。
onPostExecute方法在主线程中之行,在异步任务之行完后,此方法会被调用,result参数就是doInBackground的返回值。
还有一个onCancelled方法,同样运行在主线程中,在异步任务被取消事会被调用,这个时候onPostExecute就不会被调用。
一个示例如下:
|
|
上述代码实现了一个具体的AsyncTask类,用于模拟文件的下载
AsyncTask使用的注意点:
- AsyncTask必须在主线程中加载,也就是说第一次访问AsyncTask必须在主线程,在Android4.1及以上版本已经被系统自动完成。
- AsyncTask的对象必须在主线程中创建。
- execute方法必须在主线程中调用。
- 不要在程序中直接调用onPreExecute、doInBackground、onProgressUpdate、onPostExecute方法
- 一个AsyncTask对象职能调用一次execute方法,否则会报运行异常。
- Android1.6之前AsyncTask是串行执行任务,Android1.6的时候才用线程池并行处理任务,Android3.0开始为了避免并发错误,又采用一个线程串行执行任务。
AsyncTask的工作原理
首先从execute方法开始:
|
|
该方法返回executeOnExecutor,sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的线程池中排队执行。
|
|
在这个方法里面对现在的状态进行判断,当状态为RUNNING和FINISHED时分别抛出异常。如果不是这两个状态的话会把mStates赋值为RUNNING,由此可见,该方法只能执行一次。之后会调用onPreExecute方法。
接下来分析线程池的执行过程:
|
|
系统首先会吧AsyncTask的Params参数封装为FutureTask对象,FutureTask是一个并发类,这里充当了Runnable作用。接着这个FutureTask会交给SerialExecutor的execute方法处理(这里不是很懂?)SerialExecutor的execute方法会首先把FutureTask插入到任务队列mTasks中,如果这个时候没有正在活动的AsyncTask任务,就会调用scheduleNext方法。当一个AsyncTask执行完了之后,AsyncTask会继续执行其他任务直到所有任务都被执行为止。从此可以看出,默认情况下,AsyncTask是串行执行的
AsyncTask中有两个线程池:SerialExecutor和THREAD_POOL_EXECUTOR,以及一个Handler:InternalHandler。其中SerialExecutor用于排队,而THREAD_POOL_EXECUTOR用于真正的执行任务。InternalHandler用于将执行环境从线程池切换到主线程。
接下来看AsyncTask的构造函数:
|
|
首先,初始化了mWorker,这个mWorker是一个WorkerRunnable对象。WorkerRunnable是一个抽象类,它实现了Callable
然后看mFuture部分,在初始化mFuture的时候,我们传入了mWorker作为参数,即mFuture时一个封装了后台任务的FutureTask对象(因为上面说了mWorker中的call方法包含了后台需要执行的任务),FutureTask实现了FutureRunnable接口,通过这个接口可以方便的取消后台任务以及获取后台任务的执行结果。(这里需要补Java的多线程知识)
总结一下上面的分析:首先mWorker中的call执行的时候,doInBackground就会被执行,我们所定义的后台任务也就开始了。而通过mFuture方法的构造方法能知道mFuture封装了call方法,也就是说当mFuture被提交到AsyncTask的线程池中之行的时候,call方法会被调用,即doInBackground也就会被调用。
最后来看上面所提到的postResult方法:
|
|
首先调用getHandler获取一个sHandler,然后通过它发送一个MESSAGE_POST_RESULT的消息。而sHandler是一个InternalHandler:
|
|
这是一个私有的静态Handler对象。由于创建Handler对象的时候必须使用当前线程的Looper,所以为了以后能够通过sHandler将执行环境从后台线程切换到主线程(即在主线程中执行handleMessage方法),我们必须使用主线程的Looper,因此必须在主线程中创建sHandler。这也就解释了为什么必须在主线程中加载AsyncTask类,是为了完成sHandler这个静态成员的初始化工作。
HandlerThread
HandlerThread继承了Thread,是一种可以使用Handler的Thread。它的实现就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。HandlerThread的run方法如下:
|
|
普通的Thread主要用于在run方法中之行一个耗时的任务,而HandlerThread则在内部创建了消息队列,需要外界通过Handler的方式来通知HandlerThread来执行一个具体的任务。由于HandlerThread的runn方法是一个无限循环,因此当明确不需要再使用HandlerThread的时候,可以通过它的quit或者quitSafely来终止线程的执行。
IntentService
IntentService是一种特殊的Service,它继承了Service并且是一个抽象类,因此必须创建它的子类才能使用IntentService。
IntentService可以用于执行后台的耗时任务,当任务执行后就会自动停止。并且由于它是一个服务,所以它的优先级比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务,因为它优先级高不容易被系统杀死。
首先看它的onCreate方法:
|
|
从onCreate中可以看出IntentService封装了HandlerThread呵Handler。当IntentService第一次被启动时,onCreate方法被调用,onCreate会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。每次启动IntentService,它的onStartCommand就会调用一次,IntentService在onStartCommand中处理每个后台任务的Intent,onStartCommand方法调用了onStart:
|
|
可以看出IntentService只是通过mServiceHandler发送了一个消息,这个消息会在HandlerThread中被处理。mServiceHandler收到消息后,会将Intent对象传递给onHandleIntent方法去处理。这个Intent对象和外界的startService(intent)中的intent内容完全是一致的,通过这个Intent对象即可解析出外界启动IntentService时所传递的参数,通过这些参数可以区分具体的后台的任务。这样就可以对不同的后台任务进行处理。当onHandleIntent方法执行结束之后,IntentService会通过stopSelf(int startId)来尝试停止服务。
ServiceHandler的实现如下:
|
|
而onHandleIntent是一个抽象方法,需要在子类中去实现,它的作用时从Intent参数中区分具体的任务并执行这些任务。IntentService的具体使用请参考:Android IntentService使用初体验