为主线程减轻负的多线程方案有哪些呢?这些方案分别适合在什么场景下使用?
Android系统为我们提供了若干组工具类来帮助解决这个问题。
- AsyncTask: 为UI线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
- HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
- ThreadPool: 把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
- IntentService: 适合于执行由UI触发的后台Service任务,并可以把后台任务执行的情况通过一定的机制反馈给UI。
1. HandlerThread
Android系统为我们提供了Looper,Handler,MessageQueue来帮助实现上面的线程任务模型:
Looper: 能够确保线程持续存活并且可以不断的从任务队列中获取任务并进行执行。
Handler: 能够帮助实现队列任务的管理,不仅仅能够把任务插入到队列的头部,尾部,还可以按照一定的时间延迟来确保任务从队列中能够来得及被取消掉。
MessageQueue: 使用Intent,Message,Runnable作为任务的载体在不同的线程之间进行传递。
把上面三个组件打包到一起进行协作,这就是HandlerThread
程序被启动,系统会帮忙创建进程以及相应的主线程,而这个主线程其实就是一个HandlerThread。这个主线程会需要处理系统事件,输入事件,系统回调的任务,UI绘制等等任务,为了避免主线程任务过重,我们就会需要不断的开启新的工作线程来处理那些子任务。
HandlerThread比较合适处理那些在工作线程执行,需要花费时间偏长的任务。我们只需要把任务发送给HandlerThread,然后就只需要等待任务执行结束的时候通知返回到主线程就好了。
特点:
(1) HandlerThread将loop转到子线程中处理,分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
(2) HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
(3) 处理任务是串行执行,按消息发送顺序进行处理。由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那 么就会导致后续的任务都会被延迟处理。
(4) 对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。
2. AsyncTask
使用AsyncTask需要注意的问题有哪些呢?请关注以下几点:
(1) 默认情况下,所有的AsyncTask任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。
为了解决上面提到的线性队列等待的问题,我们可以使用AsyncTask.executeOnExecutor()
强制指定AsyncTask使用线程池并发调度任务。
(2) 其次,如何才能够真正的取消一个AsyncTask的执行呢?
我们知道AsyncTaks有提供cancel()
的方法,但是这个方法实际上做了什么事情呢?线程本身并不具备中止正在执行的代码的能力,为了能够让一个线程更早的被销毁,我们需要在doInBackground()
的代码中不断的添加程序是否被中止的判断逻辑。
一旦任务被成功中止,AsyncTask就不会继续调用onPostExecute()
,而是通过调用onCancelled()
的回调方法反馈任务执行取消的结果。我们可以根据任务回调到哪个方法(是onPostExecute还是onCancelled)来决定是对UI进行正常的更新还是把对应的任务所占用的内存进行销毁等。
(3) 最后,使用AsyncTask很容易导致内存泄漏,在Activity内部定义的一个AsyncTask,它属于一个内部类,该类本身和外面的Activity是有引用关系的,如果Activity要销毁的时候,AsyncTask还仍然在运行,这会导致Activity没有办法完全释放,从而引发内存泄漏。
3. ThreadPoolExecutor
Runtime.getRuntime().availableProcesser()
方法并不可靠,他返回的值并不是真实的CPU核心数,因为CPU会在某些情况下选择对部分核心进行睡眠处理,在这种情况下,返回的数量就只能是激活的CPU核心数。
使用线程池ThreadPool的好处:
(1) 重用已经创建好的线程,避免频繁创建进而导致的频繁GC
(2) 控制线程并发数,合理使用系统资源,提高应用性能
(3) 可以有效的控制线程的执行,比如定时执行,取消执行等
4. IntentService
默认的Service是执行在主线程的,可是通常情况下,这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的AsyncTask与HandlerThread,我们还可以选择使用IntentService来实现异步操作。IntentService继承自普通Service同时又在内部创建了一个HandlerThread,在onHandlerIntent()
的回调里面处理扔到IntentService的任务。所以IntentService就不仅仅具备了异步线程的特性,还同时保留了Service不受主页面生命周期影响的特点。
正在运行的IntentService的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的.
5. 线程优先级
Android系统会根据当前运行的可见的程序和不可见的后台程序对线程进行归类,划分为forground的那部分线程会大致占用掉CPU的90%左右的时间片,background的那部分线程就总共只能分享到5%-10%左右的时间片。默认情况下,新创建的线程的优先级默认和创建它的母线程保持一致。如果主UI线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占CPU资源,需要把这些线程的优先级进行降低处理,这样才能给帮组CPU识别主次,提高主线程所能得到的系统资源。
我们可以通过android.os.Process.setThreadPriority(int)
设置线程的优先级,参数范围从-20到24,数值越小优先级越高。
Android系统里面的AsyncTask与IntentService已经默认帮助我们设置线程的优先级,
AsyncTask: Process.THREAD_PRIORITY_BACKGROUND
IntentService: Process.THREAD_PRIORITY_DEFAULT
6. 问题
在Activity不停的创建与销毁的过程当中,很有可能因为工作线程持有Activity的View而导致内存泄漏(因为工作线程很可能持有View的强引用,另外工作线程的生命周期还无法保证和Activity的生命周期一致,这样就容易发生内存泄漏了).
Loader的出现就是为了确保工作线程能够和Activity的生命周期保持一致,同时避免出现前面提到的问题。
在Activity或者Fragment中使用Loader可以方便的实现异步加载的框架,Loader有诸多优点。