为了减轻主线程负担的多线程方案有哪些?它们分别适用于哪些场景?
在Android系统中,有许多工具类可以帮助我们解决这个问题。以下是一些常见的多线程方案:
1. AsyncTask:为UI线程与工作线程之间提供一种简单便捷的切换机制。适用于需要立即启动且生命周期较短的异步执行场景。
2. HandlerThread:为某些回调方法或等待任务执行设置一个专属线程,并提供调度机制。
3. ThreadPool:将任务分解成不同单元,分发到各个线程上进行并发处理。
4. IntentService:适合执行由UI触发的后台Service任务,并能将后台任务执行情况反馈给UI。
这些方案中,HandlerThread、ThreadPool和IntentService各有特点,适用于不同的场景。
1. HandlerThread:Android系统提供了Looper、Handler和MessageQueue这三个组件来实现线程任务模型。Looper确保线程持续存活并从任务队列中获取任务;Handler负责管理队列任务,可以在头部、尾部或按照时间延迟插入任务;MessageQueue使用Intent、Message和Runnable作为任务载体在不同线程间传递。将这三个组件组合在一起,构成了HandlerThread。程序启动后,系统会创建进程和相应的主线程(即HandlerThread)。主线程负责处理系统事件、输入事件、系统回调任务和UI绘制等任务。为了避免主线程过重,需要不断开启新的工作线程来处理子任务。HandlerThread适用于处理耗时较长的工作线程任务。只需将任务发送给HandlerThread,然后等待任务结束后通知返回主线程即可。
特点:
(1) HandlerThread将Looper转移到子线程中处理,分担MainLooper的工作量,降低主线程压力,使主界面更流畅。
(2) HandlerThread拥有自己的消息队列,不会干扰或阻塞UI线程。
AsyncTask是一种轻量级的异步执行任务的方式,它可以将耗时的操作放在子线程中执行,从而避免阻塞主线程。但是,使用AsyncTask也需要注意一些问题,比如默认情况下,所有的AsyncTask任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。其次,如何才能够真正的取消一个AsyncTask的执行呢?可以使用cancel()、doInBackground()、onPostExecute()和onCancelled()方法来实现。最后,使用AsyncTask很容易导致内存泄漏,在Activity内部定义的一个AsyncTask,它属于一个内部类,该类本身和外面的Activity是有引用关系的,如果Activity要销毁的时候,AsyncTask还仍然在运行,这会导致Activity没有办法完全释放,从而引发内存泄漏。
ThreadPoolExecutor是一种线程池实现方式。它可以通过Runtime.getRuntime().availableProcesser()获取系统可用的处理器数量来创建指定数量的线程池。使用线程池的好处包括:重用已经创建好的线程、控制线程并发数、合理使用系统资源、可以有效地控制线程的执行等等。
IntentService是一种服务方式。它可以在后台执行长时间运行的任务,并且不会被系统杀死。正在运行的IntentService相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。
线程优先级是指在一个进程中不同线程之间所分配到的CPU时间片大小不同。在Android中,可以通过setPriority()方法来设置线程优先级。例如:new Thread(new Runnable() { @Override public void run() {} }).setPriority(Thread.MAX_PRIORITY) 可以将新创建的线程设置为最高优先级。
Android系统中,线程的优先级决定了不同线程获得处理器时间的相对优先级。高优先级线程会比低优先级线程获得更多的 CPU 时间。在Android中,有两种常见的设置线程优先级的方式:使用 Thread 类实例的 setPriority 方法,来设置线程优先级;使用 Process 类的 setThreadPriority 方法,来设置线程优先级 。
如果主UI线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占CPU资源,需要把这些线程的优先级进行降低处理,这样才能给帮组CPU识别主次,提高主线程所能得到的系统资源。