android-architecture-components/WorkManagerSample/
androidx.work.WorkManager通过WorkManager API,可以轻松安排即使应用程序退出或设备重新启动也可以运行的可延迟的异步任务。
关键功能:
WorkManager被设计用于执行延迟任务,这意味着这些任务不需要被立即执行,并且这些延迟任务在App被杀死或者设备重启后依然能够可靠的执行。这么说有点抽象了,举两个这样任务的例子:
WorkManager不适用于完成需要和App生命周期关联的任务,也不适用于完成需要被立即执行的任务。关于什么样的任务适用Android提供的相关API,可以参考下图(这里的任务都是指需要后台执行的任务)
[外链图片转存失败(img-d4K58xI9-1566298763317)(https://developer.android.google.cn/images/guide/background/bg-job-choose.svg)]
Event 事件 从组件或者Lifecycle类分发出来的生命周期,它们和Activity/Fragment生命周期的事件一一对应。(ON_CREATE,ON_START,ON_RESUME,ON_PAUSE,ON_STOP,ON_DESTROY)
State 状态 当前组件的生命周期状态(INITIALIZED,DESTROYED,CREATED,STARTED,RESUMED)
说的有点抽象了,看一下下面的图:
简单明了的选择分类,就不做过多解释了。
由于Android对后台任务管理的不断严格,在完成后台任务的时候需要考虑不同API版本对后台任务的限制。关于如何选择完成后台任务的API,可以按如下几点来考虑:
后台任务是否需要被立即执行还是可以延迟执行? 例如如果是点击按钮获取网络数据,那么这个任务就需要被立即执行。但是如果只是想把log上传到服务器,那么这个任务就可以延迟执行,这样就不会对App运行有影响
完成任务是否需要系统处于某些特定的场景 有些任务可能需要在某些特定的场景下执行,例如手机处于充电模式并且有网络连接等情况。为什么要处于这样的场景下才完成某些任务呢?如果手机处于充电、熄屏并且连接wifi等情况下,那么我们就可以完成一些比较耗电耗流量的任务,并且不会对用户体验造成任何影响。这样的任务有可能需要事先存储需要完成的任务,再集中执行。
任务是否需要在精确的时间被执行 日历类的应用需要在精确地时间提醒用户设置的事件,但其他的任务可能就没有必要在精确地时间执行。通常有可能的情况是:任务A执行完成->执行任务B完成->执行任务C,但是并不需要这些任务在精确地时间(例如下午6:30)执行。
好了,使用workmanager的场景想必大家都了解一点了,下面我们看看如何使用WorkManager
第一步当然是添加workmanager的依赖啦!!
将以下依赖添加进入你的build.gradle中:
dependencies {
def work_version = "2.2.0"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
}
创建一个类继承worker,重写dowork()
方法,doWork()方法是在WorkManager提供的后台线程上同步运行。请看下面代码:
class TestWorker(var context: Context,workParams:WorkerParameters) : Worker(context,workParams){
val TAG = TestWorker::class.java.simpleName
var handler = Handler(Looper.getMainLooper())
//这是在后台线程运行的
override fun doWork(): Result {
handler.post {
Toast.makeText(context,"测试work是在子线程运行的", Toast.LENGTH_SHORT).show()
}
return Result.success()
}
}
worker
定义要做什么任务,workRequest定义了如何以及何时运行任务.任务可能是一次性或定期的,对于一次性的任务,使用OneTimeWorkRequest,对于定期的任务使用PeriodicWorkRequest.
下面是WorkRequest的简单示例:
var testWorkRequest = OneTimeWorkRequestBuilder<TestWorker>().build()
之前以及定义了WorkRequest
,现在你可以使用WorkManager的enqueue()方法将任务添加到系统计划中.任务入队,WorkManager
调度执行
WorkManager.getInstance(this).enqueue(testWorkRequest)
可以添加约束Constraints
来指明任务什么时候执行,请看一下示例:
var constraints = Constraints.Builder()
.setRequiresBatteryNotLow(true)执行任务时电池电量不能偏低。
.setRequiresCharging(true)在充电时执行
// .setRequiresDeviceIdle(true)在待机状态执行
.setRequiresStorageNotLow(true)//设备储存空间足够时才能执行。
// .setTriggerContentMaxDelay(Duration.ZERO)
// .setTriggerContentUpdateDelay(Duration.ZERO)
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)//对网络没要求
.build()
var request = OneTimeWorkRequestBuilder<ConstraintWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance(this).enqueue(request)
其它的都只是传入一个boolean值,网络状态要复杂点,具体看一下说明:
/**
* 指定网络状态执行任务
* NetworkType.NOT_REQUIRED:对网络没有要求
* NetworkType.CONNECTED:网络连接的时候执行
* NetworkType.UNMETERED:不计费的网络比如WIFI下执行
* NetworkType.NOT_ROAMING:非漫游网络状态
* NetworkType.METERED:计费网络比如3G,4G下执行。
*/
当没有约束条件或约束条件都满足的情况下,系统会直接运行任务,如果不想直接运行,可以通过WorkRequest
的setInitialDelay()方法设置执行的初始延时时间.
var request = OneTimeWorkRequest.Builder(InitialDelayWorker::class.java)
.setInitialDelay(5, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(this).enqueue(request)
如果你要求WorkManager
重行执行你的任务,你可以在你的Worker
中返回Result.retry().你的任务会安排一个默认的退避延时和策略.退避策略定义了后续重试尝试的后退延迟将如何随时间增加(这块有点儿难理解,请接着往下看)
这是可以指定退避策略和延时通过WorkRequest.setBackoffCriteria(@NonNull BackoffPolicy backoffPolicy,
long backoffDelay,
@NonNull TimeUnit timeUnit)方法.
这里注意几点:
默认延时: 30s
退避策略BackoffPolicy: BackoffPolicy.EXPONENTIAL (指数)
退避延时要求最小为:10s(10秒),最大为5h(5个小时),设置的时间小于10秒按10秒来算,大于5小时按5小时算
退避策略解释(默认延时设置为10秒):
BackoffPolicy.EXPONENTIAL (指数)规律: Math.scalb(10, 尝试次数 - 1) 即 10 × 2^(尝试次数-1)
BackoffPolicy.LINEAR (线性)规律: 10 20 30 40 50 即 (10 * 尝试次数)
在WorkRequest
中定义的退避延时范围及默认延时
public abstract class WorkRequest {
/**
* The default initial backoff time (in milliseconds) for work that has to be retried.
*/
public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L;
/**
* The maximum backoff time (in milliseconds) for work that has to be retried.
*/
public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.
/**
* The minimum backoff time for work (in milliseconds) that has to be retried.
*/
public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.
....
}
您的任务可能要求将数据作为输入参数传递或作为结果返回。例如,处理上传图片的任务需要将图片的URI作为输入参数,并且可能需要将上传图片的URL作为输出。
注意:Data对象应该很小,值可以是字符串,基本类型或它们的数组变体,Data对象的最大大小限制为10KB
。如果你需要在worker中输入输出更多的数据,则应将数据放在其他位置,例如Room数据库。
Worker类可以通过调用Worker.getInputData()来访问输入参数。
Data类还可用于输出返回值。通过将Data对象包含在Result.success()或Result.failure()中的Result中来返回Data对象,如下所示。
/**
* |---------------------------------------------------------------------------------------------------------------|
* @description: worker的输入/输出数据
* @author: East
* @date: 2019-08-16
* |---------------------------------------------------------------------------------------------------------------|
*/
class InputOutputWorker(var context: Context, workParams:WorkerParameters) : Worker(context,workParams){
val TAG = InputOutputWorker::class.java.simpleName
var handler = Handler(Looper.getMainLooper())
//这是在后台线程运行的
override fun doWork(): Result {
val s = inputData.getString("key")
Log.d(TAG,"接受到的数据为:$s")
handler.post {
Toast.makeText(context,"接受到的数据为:$s", Toast.LENGTH_SHORT).show()
}
//创建需要返回的数据
var outputData = workDataOf("key" to "this is output data")
//返回数据
return Result.success(outputData)
}
}
您可以通过为任何WorkRequest对象分配标记字符串来逻辑地对任务进行分组。这允许您使用特定标记操作任务。例如:
示例如下:
var request = OneTimeWorkRequestBuilder<TestWorker>()
.addTag("clean up")
.build()
//关闭所有tag为clean up的任务
//WorkManager.getInstance(this).cancelAllWorkByTag("clean up")
WorkManager.getInstance(this).enqueue(request)
随着work的一生,work会经历很多状态,我们来学些下有哪些状态
Result.success()
时,这是一个终极状态,只有OneTimeWorkRequests可以进入此状态。Result.failure()
时,这也是一个终极状态,只有OneTimeWorkRequests可以进入此状态。后续所有相关联的worker都会被标记为FAILED状态且不会被执行.Worker的状态信息在WorkInfo对象中,WorkInfo中包含了work的id
,tags
,还有它当前的state
以及一些输出数据.
可以通过以下三种方式获取workInfo:
示例代码如下:
var request = OneTimeWorkRequestBuilder<InputOutputWorker>()
.addTag("clean up")
.build()
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer {
if(it != null && it.state == WorkInfo.State.SUCCEEDED){
val value = it.outputData.getString("key")
Log.d(TAG,"work 完成,接收到数据为:$value")
}
})
//val listenableFuture = WorkManager.getInstance(this).getWorkInfosByTag("clean up")
//val workInfo = listenableFuture.get()
//WorkManager.getInstance(this).getWorkInfosForUniqueWork("hehe")
WorkManager.getInstance(this).enqueue(request)
//WorkManager.getInstance(this).enqueueUniqueWork("hehe",ExistingWorkPolicy.REPLACE,request)
WorkManager允许你创建并排队运行一个任务链.
创建任务链使用 WorkManager.getInstance(Context).beginWith(OneTimeWorkRequest)
or WorkManager.getInstance(Context).beginWith(List
会返回一个WorkContinuation
WorkContinuation
添加OneTimeWorkRequest
使用方法 WorkContinuation.then(OneTimeWorkRequest)
or WorkContinuation.then(List
.会接着返回WorkContinuation
的新实例,如果添加List
of OneTimeWorkRequests
,则他们并行执行.最后调用WorkContinuation.enqueue()
方法来排队运行你的任务链.
var request = OneTimeWorkRequestBuilder<TestWorker>().build()
var requestConstraint = OneTimeWorkRequestBuilder<ConstraintWorker>().build()
var requestInputOutputWorker = OneTimeWorkRequestBuilder<InputOutputWorker>().build()
WorkManager.getInstance(this)
//workRequest并行运行
.beginWith(arrayListOf(request,requestConstraint))
//在之前的workRequst之后运行
.then(requestInputOutputWorker)
//最后别忘了enqueue
.enqueue()
在使用OneTimeWorkRequest
的任务链时,上一个WorkRequest的输出Data将会作为下一个WorkRequest的输入Data.比如上面示例中request
,requestConstraint
的输出Data将会作为requestInputOutputWorker
的输入Data.
为了管理来自多个父OneTimeWorkRequests的输入,WorkManager使用InputMergers。需要在WorkRequest中调用他的setInutMerger方法它有两种不同的类型:
dowork()
方法中调用 getInputData().getStringArray(String key)方法获取在链中之前执行的workRequest中相同key对应的value值.var request = OneTimeWorkRequestBuilder<TestWorker>()
.build()
var requestConstraint = OneTimeWorkRequestBuilder<ConstraintWorker>()
.build()
var requestInputOutputWorker = OneTimeWorkRequestBuilder<InputOutputWorker>()
.setInputMerger(ArrayCreatingInputMerger::class.java)
.build()
WorkManager.getInstance(this)
//workRequest并行运行
.beginWith(arrayListOf(request,requestConstraint))
//在之前的workRequst之后运行
.then(requestInputOutputWorker)
//最后别忘了enqueue
.enqueue()
InputOutputWorker:
//如果inputMerger是OverwritingInputMerger则 s = "constraintworker"
//val s = inputData.getString("key")
//如果inputMerger是ArrayCreatingInoputMerger则 s = "[testworker,constraintworker]"
val s = inputData.getStringArray("key")
TestWorker
return Result.success(workDataOf("key" to "testworker"))
ConstraintWorker:
return Result.success(workDataOf("key" to "constraintWorker"))
创建OneTimeWorkRequest任务链需要记住的几件事:
BLOCKED
状态转化为ENQUEUED
状态.FAILED
状态CANCELED
状态WorkManager.getInstance(Context).cancelWorkById(workRequest.id)
通过id来取消任务WorkManager.getInstance(Context).cancelAllWorkByTag(String)
通过tag来取消任务WorkManager.getInstance(Context).cancelUniqueWork(String)
通过unique name来取消任务WorkManager.getInstance(Context).cancelAllWork()
取消全部任务以下情况中你正在运行的worker将被WorkManager停止:
这些情况下你会接受到ListenableWorker.onStopped(),调用 ListenableWorker.isStopped()来判断worker是否停止.
你的应用有些时候会需要定期的执行一些任务,例如:定期备份数据,定期上传应用日志等等.
使用PeriodicWorkRequest用来执行定期的任务.
注意:PeriodicWorkRequest
最短的间隔时间是15分钟
PeriodicWorkRequest
不能添加到工作链中,如果需要工作链,请使用OneTimeWorkRequest
.关于PeriodicWorkRequest
的使用,请看下面的例子.
var constraints = Constraints.Builder()
.setRequiresCharging(true)
.build()
//过了间隔时间后,也会观察是否满足充电的约束条件,是的话才会执行(最小间隔时间必须大于等于15分钟)
var request = PeriodicWorkRequestBuilder<TestWorker>(5,TimeUnit.SECONDS)
.setConstraints(constraints)
.build()
WorkManager.getInstance(this).enqueue(request)
和tag
不一样unique name是仅和一个work 或 work chain 对应.
和id
不同的是unique name是开发者可定义的.
创建一个unique work 可以通过 WorkManager.getInstance(Context).enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) or WorkManager.getInstance(Context).enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest).
第一个参数是unique name 用来标识WorkRequest的秘钥
第二个参数是冲突解决策略
(如果已经存在了该unique name的work或work chain):
ExistingPeriodicWorkPolicy.APPEND
与PeriodicWorkRequests
一起使用。第三个参数是 WorkRequest
如果你需要创建 unique work chain 使用方法
WorkManager.getInstance(Context).beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)
示例代码:
btn_unique_work -> {
var request = PeriodicWorkRequestBuilder<TestWorker>(15,TimeUnit.MINUTES).build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork("test",ExistingPeriodicWorkPolicy.KEEP,request)
}
btn_unique_work_chain -> {
var request = OneTimeWorkRequestBuilder<TestWorker>()
.build()
var requestConstraint = OneTimeWorkRequestBuilder<ConstraintWorker>()
.build()
var requestInputOutputWorker = OneTimeWorkRequestBuilder<InputOutputWorker>()
.setInputMerger(ArrayCreatingInputMerger::class.java)
.build()
WorkManager.getInstance(this)
.beginUniqueWork("test",ExistingWorkPolicy.REPLACE,arrayListOf(request,requestConstraint))
.then(requestInputOutputWorker)
.enqueue()
}
默认初始化适用于大多数app,当应用打开时,WorkManager使用自定义的ContentProvider
初始化自己,使用默认的Configuration,默认初始化是自动加载,除非你 disable 它.
第一步先禁用默认初始化,在AndroidManifest.xml
中使用合并规则:tools:node="remove"
:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
第二步自定义自己的初始化:
确保初始化在Application.onCreate()或ContentProvider.onCreate()中运行。:
/**
* |---------------------------------------------------------------------------------------------------------------|
* @description: 自定义 WorkManager的初始化
* @author: East
* @date: 2019-08-20
* |---------------------------------------------------------------------------------------------------------------|
*/
class App : Application() {
override fun onCreate() {
super.onCreate()
//提供自定义的配置
var config = Configuration.Builder()
.setMinimumLoggingLevel(Log.INFO)
.setExecutor(Executors.newSingleThreadExecutor())
.build()
//初始化WorkManager
WorkManager.initialize(this,config)
}
}
为WorkManager提供自定义初始化的最灵活方法是使用按需初始化。按需初始化允许你仅在需要该组件时初始化WorkManager,加快应用启动速度.
第一步:编辑AndroidManifest.xml并禁用默认初始化程序。
第二步:让你的Application类实现Configuration.Provider接口,并提供你自己的Configuration.Provider.getWorkManagerConfiguration()实现
第三步:当您需要使用WorkManager时,请调用方法WorkManager.getInstance(Context)。WorkManager调用应用程序的自定义getWorkManagerConfiguration()方法来发现其配置.(您不需要自己调用WorkManager.initialize()。)
class App : Application(),Configuration.Provider {
override fun onCreate() {
super.onCreate()
}
//使用按需初始化workManager时 需要实现接口
override fun getWorkManagerConfiguration(): Configuration {
//提供自定义的配置
var config = Configuration.Builder()
.setMinimumLoggingLevel(Log.INFO)
.setExecutor(Executors.newSingleThreadExecutor())
.build()
return config
}
}
WorkManager提供了4种不同的Work:
worker
的dowork()
方法自动运行在WorkManager的Configuration中定义的Executor中的后台线程中,请注意,Worker.doWork()是一个同步调用 - 您需要以阻塞方式完成整个后台工作,并在方法退出时完成它。如果在doWork()中调用异步API并返回Result,则回调可能无法正常运行。要解决这类问题请使用ListenableWorker
对于Kotlin用户,WorkManager为协同程序提供一流的支持。CoroutineWorker.doWork()
此代码不在Configuration中指定的Executor上运行,相反,它默认为Dispatchers.Default。您可以通过提供自己的CoroutineContext来自定义它.
/**
* |---------------------------------------------------------------------------------------------------------------|
* @description: 适合kotlin用户的协同Worker CoroutineWorker
* @author: East
* @date: 2019-08-20
* |---------------------------------------------------------------------------------------------------------------|
*/
class KotlinWorker(var context:Context, params:WorkerParameters) : CoroutineWorker(context,params) {
//已经过时请在doWork方法中使用withContext来指定任务执行时运行的线程
// override val coroutineContext: CoroutineDispatcher
// get() = Dispatchers.IO
override suspend fun doWork(): Result {
withContext(Dispatchers.Main){
Toast.makeText(context,"证明CoroutineWorker可通过CoroutineContext来定义执行线程",Toast.LENGTH_SHORT).show()
}
return Result.success()
}
}
我们提供WorkManager和RxJava2之间的互操作性,首先继承 RxWorker
,然后重写RxWorker.createWork()方法以返回指示执行结果的Single,
public class RxDownloadWorker extends RxWorker {
public RxDownloadWorker(Context context, WorkerParameters params) {
super(context, params);
}
@Override
public Single<Result> createWork() {
return Observable.range(0, 100)
.flatMap { download("https://www.google.com") }
.toList()
.map { Result.success() };
}
}
请注意,在主线程上调用RxWorker.createWork(),但默认情况下在后台线程上订阅返回值。您可以覆盖RxWorker.getBackgroundScheduler()以更改订阅线程。
停止RxWorker会正确处理Observers,因此您无需以任何特殊方式处理停止工作。
ListenableWorker 是Worker
,CoroutineWorker
和RxWorker
的父类.
抽象方法ListenableWorker.startWork()
返回Result
的ListenableFuture
。
如果你想基于异步回调执行一些工作,你会做这样的事情:
dependencies {
def futures_version = "1.0.0-rc01"
implementation "androidx.concurrent:concurrent-futures:$futures_version"
}
/**
* |---------------------------------------------------------------------------------------------------------------|
* @description: worker中执行异步操作
* @author: East
* @date: 2019-08-20
* |---------------------------------------------------------------------------------------------------------------|
*/
class AsynchronousWorker(context: Context,params:WorkerParameters) : ListenableWorker(context,params) {
override fun startWork(): ListenableFuture<Result> {
// val future = ResolvableFuture.create()
// future.set(doWork())
// return future
return CallbackToFutureAdapter.getFuture<Result> { completer ->
//做异步操作
}
}
fun doWork() : Result{
return Result.success()
}
}