前言
异步处理实现方式主要有:
- 实现Thread的run()方法或者实现Runable接口
- HandlerThread
- AsyncTask(已废弃)
- LoaderManager
- WorkManager
Thread
直接使用Thread实现方式,这种方式简单,但不是很优雅。适合数量很少(偶尔一两次)的异步任务,但要处理的异步任务很多的话,使用该方式会导致创建大量的线程,这会影响用户交互。
关键字join、sleep、yield
join() method suspends the execution of the calling thread until the object called finishes its execution.
也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。join()方法是让出执行资源(如:CPU时间片),使得其它线程可以获得执行的资源。所以调用join()方法会使进入阻塞状态,该线程被唤醒后会进入runable状态,等待下一个时间片的到来才能再次执行。
sleep()不会让出资源,只是处于睡眠状态(类似只执行空操作)。调用sleep()方法会使进入等待状态,当等待时间到后,如果还在时间片内,则直接进入运行状态,否则进入runable状态,等待下个时间片。
Yield()方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield()方法将不会起作用。
suspend()可能导致死锁,因此弃用
HandlerThread
HandlerThread,这种方式适合子线程有序的执行异步操作,异步任务的执行一个接着一个。
HandlerThread的内部实现机制很简单,在创建新的线程后,使该线程成为一个Looper线程,让该线程不断的从MessageQueue取出消息并处理。
就应用程序而言,Android系统中JAVA的应用程序和其他系统上相同,都是靠消息驱动来工作的,他们大致的工作原理如下:
1、有一个消息队列,可以往这个消息队列中投递消息。
2、有一个消息循环,不断从消息队列中取出消息,然后处理。
在Android中,一个线程对应一个Looper对象,而一个Looper对象又对应一个MessageQueue(用于存放message)。
循环者Looper类,消息处理类Handler,消息类Message。
Looper对象用来为一个线程开启一个消息循环,用来操作MessgeQueue。默认情况下,Android中新创建的线程是没有开启消息循环的。(主线程除外)
消息处理类(Handler)允许发送和处理Message和Rannable对象到其所在线程的MessageQueue中。(它主要有两个作用:1、将Message或Runnable应用post()方法或sendMessage()方法发送到MessageQueue中,在发送时可以指定延时时间、发送时间或者要携带的bundle数据。当MessageQueue循环到该Message时,调用相应的Handler对象的handlerMessage()方法对其进行处理。2、在子线程中与主线程进行通信,也就是在工作线程中与UI线程进行通信。)
另外,在一个线程中只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享一个Looper和MessageQueue。
消息类(Message)被存放在MessageQueue中,一个MessageQueue中可以包含多个Message对象。每个Message对象可以通过Messhe.obtain()方法或者Handler.obtainMessage()方法获得。Message是一个final类,所以不可被继承。
AsyncTask(已废弃)
AsyncTask的内部使用了两个线程池,使用AsyncTask执行异步操作时,会先在SerialExecutor进行一个顺序排队, 后再用ThreadPoolExcutor线程池为你分配一个线程并执行。而整个应用的AsyncTask任务都在排同一条队,有可能等待排队的任务很多,所以一般不会使用AsyncTask执行一些优先级比较高的异步任务。
当然我们是可以跳过不需要进行排队,直接就通过线程池分配一个线程并执行异步任务,但需要注意同时执行太多的异步任务,会影响用户体验,我想Google就是为了限制同时创建太多的线程才会采用一个排队机制的
/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
该方法是隐藏,但可使用反射,设置一个线程池。
AsyncTask, 通常用于耗时的异步处理,且时效性要求不是非常高的那种异步操作。如果时效性要求非常高的操作,不建议使用这个方式,因为AsyncTask的默认实现是有内部排队机制,且是整个应用的AsyncTask的任务进行排队,所以不能保证异步任务能很快的被执行。
问题如下:
- 并行串行问题:AsyncTasks should ideally be used for short operations (a few seconds at the most)
- 错误处理问题:AsyncTask没有对发生的一些异常进行处理,你只能在onBackground里进行一些判断,但之外的一些异常情况发生你都无法了解,比如线程异常退出等。
- 多个任务的管理问题:如果需要多个后台任务,需要新建多个AsyncTask来执行任务,在需要退出的时候你需要对每一个都进行一定的处理来避免内存泄露以及UI问题,这是一个很麻烦的事情。
LoaderManager
LoaderManager,当请求处理时机需要根据Activity的生命周期进行调整,或需要时刻监测数据的变化,那LoaderManager是很不错的解决方案。
LoaderManager可以解决的问题包括:
1.加载的数据有变化时,会自动通知我们,而不自己监控数据的变化情况,如:用CursorLoader来加载数据库数据,当数据库数据有变化时,可是个展示变化的数据
2.数据的请求处理时机会结合Activity和Fragment的生命周期进行调整,如:若Acivity销毁了,那就不会再去请求新的数据
1.LoaderManager
LoaderManager用来负责管理与Activity或者Fragment联系起来的一个或多个Loaders对象.
每个Activity或者Fragment都有唯一的一个LoaderManager实例(通过getLoaderManager()方法获得),用来启动,停止,保持,重启,关闭它的Loaders,这些功能可通过调用initLoader()/restartLoader()/destroyLoader()方法来实现.
LoaderManager并不知道数据如何装载以及何时需要装载.相反,它只需要控制它的Loaders们开始,停止,重置他们的Load行为,在配置变换或数据变化时保持loaders们的状态,并使用接口来返回load的结果.
2.Loader
Loades负责在一个单独线程中执行查询,监控数据源改变,当探测到改变时将查询到的结果集发送到注册的监听器上.Loader是一个强大的工具,具有如下特点
(1)它封装了实际的数据载入.
Activity或Fragment不再需要知道如何载入数据.它们将该任务委托给了Loader,Loader在后台执行查询要求并且将结果返回给Activity或Fragment.
(2)客户端不需要知道查询如何执行.Activity或Fragment不需要担心查询如何在独立的线程中执行,Loder会自动执行这些查询操作.
(3)它是一种安全的事件驱动方式.
Loader检测底层数据,当检测到改变时,自动执行并载入最新数据.
这使得使用Loader变得容易,客户端可以相信Loader将会自己自动更新它的数据.
Activity或Fragment所需要做的就是初始化Loader,并且对任何反馈回来的数据进行响应.除此之外,所有其他的事情都由Loader来解决.
Loader:该类用于数据的加载 ,类型参数D用于指定Loader加载的数据类型
public class Loader<D> {
}
一般我们不直接继承Loader,而是继承AsyncTaskLoader,因为Loader的加载工作并不是在异步线程中。而AsyncTaskLoader实现了异步线程,加载流程在子线程中执行。注意:对该类的调用应该在主线程中完成。
Loader负责数据加载逻辑,LoaderManager负责Loader的调度,开发者只需要自定义自己的Loader,实现数据的加载逻辑,而不再关注数据加载时由于Activity销毁引发的问题。
注意:其实AsyncTaskLoader内部实现异步的方式是使用AsyncTask完成的,上面我们说过AsyncTask的内部是有一个排队机制,但AsyncTaskLoader内部使用AsyncTask进行数据异步加载时,异步任务并不进行排队。而直接由线程池分配新线程来执行。
WorkManager
WorkManager最适用于可以延迟的任务,即使应用程序或设备重新启动(例如,使用后端服务定期同步数据并上载日志或分析数据),仍然可以运行。
特点:
- 允许在任务运行时设置约束,例如网络状态或充电状态;
- 支持异步一次性和周期性任务;
- 支持带输入和输出的链式任务;
- 即使应用程序或设备重新启动,也可确保任务执行
使用WorkManager,可以轻松添加网络可用性或计费状态等约束。任务将在满足约束时运行,并在运行时失败时自动重试。例如,如果任务需要网络可用,则当网络不再可用时将停止该任务,并在以后重试。
不仅如此,它还可以使用LiveData监视工作状态并检索工作结果,这样可以在任务完成时通知您的UI。如果任务执行失败,可以通过配置退避的处理方式来控制工作的重试方式。如果发生应用程序或设备重新启动,WorkManager还可以使用本地数据库中的工作记录重新安排工作。
利用OneTimeWorkRequest进行一次性调度或使用PeriodicWorkRequest进行重复调度。并且,我们还可以将一次性工作请求链接到按顺序或并行运行,如果链中的任何工作失败,WorkManager将确保不会运行剩余的工作链。
WorkManager API 可以很容易的指定可延迟的异步任务。允许你创建任务,并把它交给WorkManager来立即运行或在适当的时间运行。WorkManager根据设备API的级别和应用程序状态等因素来选择适当的方式运行任务。如果WorkManager在应用程序运行时执行你的任务,它会在应用程序进程的新线程中执行。如果应用程序没有运行,WorkManager会根据设备API级别和包含的依赖项选择适当的方式安排后台任务,可能会使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要编写设备逻辑来确定设备有哪些功能和选择适当的API;相反,你只要把它交给WorkManager让它选择最佳的方式。
基础功能:
- 使用WorkManager创建运行在你选择的环境下的单个任务或指定间隔的重复任务
- WorkManager API使用几个不同的类,有时,你需要继承一些类。
- Worker 指定需要执行的任务。有一个抽象类Worker,你需要继承并在此处工作。在后台线程同步工作的类。WorkManager在运行时实例化Worker类,并在预先指定的线程调用doWork方法(见Configuration.getExecutor())。此方法同步处理你的工作,意味着一旦方法返回,Worker被视为已经完成并被销毁。如果你需要异步执行或调用异步API,应使用ListenableWorker。如果因为某种原因工作没抢占,相同的Worker实例不会被重用。即每个Worker实例只会调用一次doWork()方法,如果需要重新运行工作单元,需要创建新的Worker。Worker最大10分钟完成执行并ListenableWorker.Result。如果过期,则会被发出信号停止。(Worker的doWork()方法是同步的,方法执行完则结束,不会重复执行,且默认超时时间是10分钟,超过则被停止。)
- WorkRequest 代表一个独立的任务。一个WorkRequest对象至少指定哪个Worker类应该执行该任务。但是,你还可以给WorkRequest添加详细信息,比如任务运行时的环境。每个WorkRequest有一个自动生成的唯一ID,你可以使用ID来取消排队的任务或获取任务的状态。WorkRequest是一个抽象类,你需要使用它一个子类,OneTimeWorkRequest或PeriodicWorkRequest。
1)WorkRequest.Builder 创建WorkRequest对象的帮助类,你需要使用子类OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。
2)Constraints(约束) 指定任务执行时的限制(如只有网络连接时)。使用Constraints.Builder创建Constraints对象,并在创建WorkRequest对象前传递给WorkRequest.Builder。 - WorkManager 排队和管理WorkRequest。将WorkRequest对象传递给WorkManager来将任务添加到队列。WorkManager 使用分散加载系统资源的方式安排任务,同时遵守你指定的约束。
1)WorkManager使用一种底层作业调度服务基于下面的标注
2)使用JobScheduler API23+
3)使用AlarmManager + BroadcastReceiver API14-22 - WorkInfo 包含有关特定任务的信息。WorkManager为每个WorkRequest对象提供一个LiveData。LiveData持有WorkInfo对象,通过观察LiveData,你可以确定任务的当前状态,并在任务完成后获取任何返回的值。
注意:
- WorkManager组件库里面提供了一个专门做周期性任务的类PeriodicWorkRequest。但是PeriodicWorkRequest类有一个限制条件最小的周期时间是15分钟。
- 链式任务的任务链里面的任何一个任务返回WorkerResult.FAILURE,则整个任务链终止;WorkManager会把上一个任务的输出自动作为下一个任务的输入。链式任务的关键在WorkContinuation,通过WorkContinuation来整理好队列(是顺序执行,还是组合执行)然后入队执行。
参考资料
https://blog.csdn.net/baidu_36385172/article/details/79705915
https://www.cnblogs.com/diysoul/p/5124886.html
WorkManager浅析
Android架构组件WorkManager详解