WorkManager是一个API,用来调度任务,哪怕进程关闭或者设备重启都没事。这些任务是可延期的异步任务。
任务串行执行,具有延迟性,但好处是即使退出项目甚至重启设备,下次开启app时,只要满足执行条件就会执行。
比较适合在后台像服务器发送用户日志这类操作~
之所以关闭进程或者设备重启都没事,是因为已调度的工作都存储在SQLite中。
WorkManager是一个单例,获取方式:WorkManager.getInstance(this)
上图是使用WorkManager的流程
首先创建一个worker,这是一个任务单元
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val name = inputData.getString("key")
//获取缓存的数值
val sp = applicationContext.getSharedPreferences("app", Context.MODE_PRIVATE)
var num = sp.getInt(name, 0)
num++
sp.edit().putInt(name, num).apply()
//doWork都是任务线程
Thread.sleep(3000)
//返回使用Result.success表示成功,里面可以带数据,数据使用workDataOf()这里面带一个键值对,可以中中缀表达式 to
//或者使用Pair都可以
return Result.success(workDataOf("result" to "我的线程是:${Thread.currentThread().name}"))
}
}
创建好任务单元后,我们需要创建一个任务请求,使用WorkRequest。WorkRequest规定了任务何时才能运行·.并将任务添加到任务队列中,对添加到任务队列中的任务,WorkManager会对其进行缓存。
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private val workManager = WorkManager.getInstance(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
//增加对本地缓存中数值改变的监听
getSharedPreferences("app", Context.MODE_PRIVATE)
.registerOnSharedPreferenceChangeListener(this)
//从本地缓存中获取数值
updateView()
}
/**
* 更新文本显示
*/
fun updateView() {
val sp = getSharedPreferences("app", Context.MODE_PRIVATE)
text.text = sp.getInt("workA", 0).toString()
}
/**
* 点击btn相应
*/
fun addEnqueue(view: View) {
val workRequest = createTaskRequest("workA")
workManager.enqueue(workRequest)
//按照顺序执行的任务列表
//同时先执行workRequest 和workRequestA,然后顺序执行workRequestB,最后执行workRequestC
// workManager.beginWith(workRequest, workRequestA)
// .then(workRequestB)
// .then(workRequestC)
// .enqueue()
}
/**
* 创建一个WorkRequest
*/
private fun createTaskRequest(name: String): OneTimeWorkRequest {
/**
* 创建的请求可以添加一些条件,
* .setRequiredNetworkType(NetworkType.CONNECTED)是指只有网络处于连接状态下,才能进行任务
*
*/
val contrans = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
//创建data的一种方式
// val data = Data.Builder()
// .putString("key", "value")
// .build()
/**
* 创建任务请求的时候,可以添加一些数据给Worker,使用setInputData方法,
* 在Worker类中的doWork方法中,使用inputData.getString("key")获取我们这里传递的数据,
* 注意:传递的数据也是键值对,使用Pair或者中缀表达式 to
* OneTimeWorkRequestBuilder表示创建一次性的request
*
* 设置request的时候,可以设置任务执行的条件,通过 setConstraints 设置
*/
val taskRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(contrans)
.setInputData(workDataOf("key" to name))
.build()
/**
* OneTimeWorkRequestBuilder是创建单次任务,而PeriodicWorkRequest是创建周期任务,
* 最小的周期间隔时长是15分钟
*/
// val request = PeriodicWorkRequest
// .Builder(MyWorker::class.java, 15, TimeUnit.MINUTES)
// .setConstraints(constraints)
// .setInputData(data)
// .build()
/**
* 使用WorkManager获取任务的可观察者对象.并观察任务当前的状态
* 在成功回调中,接收后台任务返回的结果
*/
WorkManager.getInstance(this).getWorkInfoByIdLiveData(taskRequest.id)
.observe(this, Observer {
if (it.state == WorkInfo.State.SUCCEEDED) {
val result = it.outputData.getString("result")
Log.d("chenhua", "收到了结果:$result")
}
})
return taskRequest
}
override fun onSharedPreferenceChanged(p0: SharedPreferences?, p1: String?) {
updateView()
}
}
xml界面就很简单了,一个textView显示数值,一个button按钮,点击才添加任务
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:onClick="addEnqueue"
android:text="加入任务到队列" />
</RelativeLayout>
下面补充说明WrokRequest请求的可选择条件
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 网络状态
.setRequiresBatteryNotLow(true) // 不在电量不足时执行
.setRequiresCharging(true) // 在充电时执行
.setRequiresStorageNotLow(true) // 不在存储容量不足时执行
.setRequiresDeviceIdle(true) // 在待机状态下执行,需要 API 23
.build()
多任务执行情况:
/**
* 下面这段看chain3,说明chain1和chain2同时执行,然后再执行workRequestE
* 由于chain1和chain2同时执行,所以workRequestA和requestC基本同时执行,然后
* 基本同时执行requestB和requestD哈~
*/
val chain1 = WorkManager.getInstance(this)
.beginWith(workRequestA)
.then(workRequestB)
val chain2 = WorkManager.getInstance(this)
.beginWith(workRequestC)
.then(workRequestD)
val chain3 = WorkContinuation
.combine(listOf(chain1, chain2))
.then(workRequestE)
chain3.enqueue()
输出结果:
收到了结果:workA 执行完毕~----------------
收到了结果:workC 执行完毕~----------------
收到了结果:workB 执行完毕~----------------
收到了结果:workD 执行完毕~----------------
收到了结果:workE 执行完毕~----------------
很多情况下,我们希望在任务队列里,同一个任务只存在一个,避免任务的重复执行,这时候可以用到 beginUniqueWork 这个方法:
/**
* 避免任务队列中有相同的任务,重复执行,可以使用beginUniqueWork
*
* ExistingWorkPolicy.REPLACE 删除已经有的任务,添加现有的任务
* ExistingWorkPolicy.KEEP 不添加新的任务,让已经有的任务继续执行。
* ExistingWorkPolicy.APPEND 加入任务链的末端
* ExistingWorkPolicy.APPEND_OR_REPLACE 如果有一个同名的未完成的任务,
* 则把新添加的任务归属到同名任务的附属任务?如果不行的话,
* 就重新开一个队列,把新任务添加到这个新队列中?
*/
workManager
.beginUniqueWork("workNamer", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequestA)
.enqueue()
保活?
周期执行任务的最小时间是15分钟,而且进程一旦被杀,也没得救了。进程被杀只有再次启动app的时候,任务才会可能开始执行。所以保活就别想了。。
service对比
这东西就是做后台任务的,那service不是也能做吗。service是4大组件,滥用容易导致app卡顿。在后台的service,谷歌也做了不少限制。对于单纯只是为了在后台执行任务,更推荐使用WorkManager。
但并不是说service完全无用了,起码还是四大组件之一。开个前台服务,能降低app被杀的可能性等等。