Android Jetpack 之使用 WorkManager
简介
WorkManager 是 Android Jetpack 的一部分,它主要用来执行持续的后台工作,比如文件上传、日志上传等。
WorManager 可以执行一次性任务(One time),也可以执行周期性任务(Perodic)。
WorkManager也可以按照执行时间分为 3 类:
- 立即运行(Immediate)
- 长期运行(Long Running)
- 推迟运行(Deferrable)
使用方法
添加依赖
dependencies {
def work_version = "2.7.1"
// (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 - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:$work_version"
}
定义 Worker
WorkManager 的所有任务都继承自 Worker 类。
重写 doWork 方法,执行任务,并且返回任务结果。
任务结果分 3 种:
- Result.success() 执行成功
- Result.failure() 执行失败
- Result.retry() 执行失败而且需要重试任务
class UploadWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
uploadImages()
return Result.success()
}
private fun uploadImages() {
}
}
构造 WorkRequest
WorkRequest 是任务请求的基本单元,可以为 WorkRequest 添加参数、设置约束。
WorkRequest 分为 2 种:
- OneTimeWorkRequest 一次性工作请求
- PeriodicWorkRequest 周期性工作请求
OneTimeWorkRequest
使用 OneTimeWorkRequestBuilder 构造 OneTimeWorkRequest。
class WorkManagerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.build()
...
}
}
PeriodicWorkRequest
使用 PeriodicWorkRequestBuilder 构造 PeriodicWorkRequest。
PeriodicWorkRequest 在构造时必须传入表示周期的时间维度,至少为 15 分钟。如果少于 15 分钟 PeriodicWorkRequest 会按照 15 分钟处理。
fun buildPeriodicRequest(): WorkRequest {
return PeriodicWorkRequestBuilder<UploadWorker>(15L, TimeUnit.MINUTES)
.build()
}
PeriodicWorkRequest
public final class PeriodicWorkRequest extends WorkRequest {
/**
* The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
*/
@SuppressLint("MinMaxConstant")
public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
...
public void setPeriodic(long intervalDuration) {
if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
Logger.get().warning(TAG, String.format(
"Interval duration lesser than minimum allowed value; Changed to %s",
MIN_PERIODIC_INTERVAL_MILLIS));
intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
}
setPeriodic(intervalDuration, intervalDuration);
}
设置 InputData
setInputData 为 WorkRequest 增加任务参数。
fun buildInputDataRequest(): WorkRequest {
val data = Data.Builder()
.putString("userName", "Bugra")
.build()
return OneTimeWorkRequestBuilder<OneTimeWorker>()
.setInputData(data)
.build()
}
在 doWork 方法可以直接调用 ListenableWorker 的 inputData,使用 getString 读取参数。
class OneTimeWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
...
override suspend fun doWork(): Result {
val userName = inputData.getString("userName")
...
}
}
设置 Constraints
使用 setConstraints 为 WorkRequest 设置参数。比如以下参数:
- setRequiredNetworkType 网络状态
- setRequiresBatteryNotLow 电量状态
- setRequiresCharging 充电状态
fun buildConstraintsRequest(): WorkRequest {
val constraints = buildConstraints()
return OneTimeWorkRequestBuilder<OneTimeWorker>()
.setConstraints(constraints)
.build()
}
/**
* constraints
*/
fun buildConstraints(): Constraints {
return Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresCharging(false)
.build()
}
使用 WorkManager 调度任务
构造 WorkRequest 后使用 WorkManager 把 WorkRequest 加入到任务队列。
class WorkManagerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.build()
val workManager: WorkManager = WorkManager.getInstance(this)
workManager.enqueue(uploadWorkRequest)
}
}
监听任务状态
使用 getWorkInfoByIdLiveData 方法从队列获取任务并且转换为可观察的 LiveData,便于观察任务的每个状态,在每种状态的回调做相应操作。
WorkInfo 有如下 6 种状态:
- ENQUEUED 入队
- RUNNING 运行中
- SUCCEEDED 运行成功
- FAILED 运行失败
- BLOCKED 被阻塞
- CANCELLED 取消
public enum State {
/**
* Used to indicate that the {@link WorkRequest} is enqueued and eligible to run when its
* {@link Constraints} are met and resources are available.
*/
ENQUEUED,
/**
* Used to indicate that the {@link WorkRequest} is currently being executed.
*/
RUNNING,
/**
* Used to indicate that the {@link WorkRequest} has completed in a successful state. Note
* that {@link PeriodicWorkRequest}s will never enter this state (they will simply go back
* to {@link #ENQUEUED} and be eligible to run again).
*/
SUCCEEDED,
/**
* Used to indicate that the {@link WorkRequest} has completed in a failure state. All
* dependent work will also be marked as {@code #FAILED} and will never run.
*/
FAILED,
/**
* Used to indicate that the {@link WorkRequest} is currently blocked because its
* prerequisites haven't finished successfully.
*/
BLOCKED,
/**
* Used to indicate that the {@link WorkRequest} has been cancelled and will not execute.
* All dependent work will also be marked as {@code #CANCELLED} and will not run.
*/
CANCELLED;
/**
* Returns {@code true} if this State is considered finished.
*
* @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and * {@link #CANCELLED}
* states
*/
public boolean isFinished() {
return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
}
}
定义 Worker
class OneTimeWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
...
override suspend fun doWork(): Result {
val userName = inputData.getString("userName")
val random = Random.nextInt(0, 2)
for (i in 1..10) {
delay(1000)
println("delay $i...")
}
val outputData = Data.Builder()
.putInt("random", random)
.build()
return if (random == 0 ) {
Result.Success(outputData)
} else {
Result.failure(outputData)
}
}
}
观察 Worker
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e(TAG, "onCreate")
val uploadWorkRequest = buildConstraintsRequest()
val workManager: WorkManager = WorkManager.getInstance(this)
workManager.enqueue(uploadWorkRequest)
// 2021-12-28 17:20:33.989 9202-9202 E/WorkManagerActivity: onCreate
// 2021-12-28 17:20:34.048 9202-9202 E/WorkManagerActivity: ENQUEUED
// 2021-12-28 17:20:34.051 9202-9202 E/WorkManagerActivity: RUNNING
// 2021-12-28 17:20:44.176 9202-9202 E/WorkManagerActivity: SUCCEEDED:0
workManager.getWorkInfoByIdLiveData(uploadWorkRequest.id)
.observe(this) { workInfo ->
when (workInfo.state) {
WorkInfo.State.ENQUEUED -> {
Log.e(TAG, "ENQUEUED")
}
WorkInfo.State.RUNNING -> {
Log.e(TAG, "RUNNING")
}
WorkInfo.State.SUCCEEDED -> {
val outputData = workInfo.outputData
val random = outputData.getInt("random", -1)
Log.e(TAG, "SUCCEEDED:$random")
}
WorkInfo.State.FAILED -> {
val outputData = workInfo.outputData
val random = outputData.getInt("random", -1)
Log.e(TAG, "FAILED:$random")
}
WorkInfo.State.BLOCKED -> {
Log.e(TAG, "BLOCKED")
}
WorkInfo.State.CANCELLED -> {
Log.e(TAG, "CANCELLED")
}
}
}
}
任务链
WorkManager 可以使用 beginWith、then、combine 等方法构造任务链,保证任务按照指定顺序运行。
先 beginWith 执行 compressRequest,再 then 执行 uploadWorkRequest。
class WorkManagerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
.build()
val uploadWorkRequest = OneTimeWorkRequestBuilder<OneTimeWorker>()
.build()
val workManager: WorkManager = WorkManager.getInstance(this)
workManager.beginWith(compressRequest)
.then(uploadWorkRequest)
.enqueue()
或者先 beginWith 同时执行 compressRequest、updateLocalRequest 两个任务,等两个任务都执行完毕后,使用 then 执行 uploadWorkRequest。
class WorkManagerActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
.build()
val updateLocalRequest = OneTimeWorkRequestBuilder<UpdateLocalWorker>().build()
val uploadWorkRequest = OneTimeWorkRequestBuilder<OneTimeWorker>()
.build()
val workManager: WorkManager = WorkManager.getInstance(this)
workManager.beginWith(listOf(compressRequest, updateLocalRequest))
.then(uploadWorkRequest)
.enqueue()
当有更复杂的任务链时,可以使用 WorkContinuation.combine 组合任务链。
compressRequest 和 updateLocalRequest 组成第一条任务链 workContinuation1。
oneTimeRequest 和 uploadWorkRequest 组成第二条任务链 workContinuation2。
使用 WorkContinuation.combine 组合两条任务链,两者都执行完毕后,执行 lastWorkRequest。
需要注意的是,任务链中如果前置任务执行失败,会导致后续无法执行。
class WorkManagerActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>().build()
val updateLocalRequest = OneTimeWorkRequestBuilder<UpdateLocalWorker>().build()
val oneTimeRequest = OneTimeWorkRequestBuilder<OneTimeWorker>().build()
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().build()
val lastWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().build()
val workManager: WorkManager = WorkManager.getInstance(this)
val workContinuation1 = workManager.beginWith(compressRequest)
.then(updateLocalRequest)
val workContinuation2 = workManager.beginWith(oneTimeRequest)
.then(uploadWorkRequest)
WorkContinuation.combine(listOf(workContinuation1, workContinuation2))
.then(lastWorkRequest)
.enqueue();
实现原理
WorkManager.getInstance
WorkManager 使用 getInstance 方法得到单例,本质是 WorkManagerImpl 的 getInstance。
class WorkManagerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e(TAG, "onCreate")
...
val workManager: WorkManager = WorkManager.getInstance(this)
workManager.enqueue(uploadWorkRequest)
...
}
public abstract class WorkManager {
...
public static @NonNull WorkManager getInstance(@NonNull Context context) {
return WorkManagerImpl.getInstance(context);
}
WorkManagerImpl.getInstance
WorkManagerImpl 的 getInstance 使用 initialize 初始化 。
public class WorkManagerImpl extends WorkManager {
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
...
if (instance == null) {
...
initialize(
appContext,
((Configuration.Provider) appContext).getWorkManagerConfiguration());
...
}
return instance;
}
}
WorkManagerImpl.initialize
WorkManagerImpl 的 initialize 通过 context、configuration、WorkManagerTaskExecutor 构造 WorkManagerImpl 单例。
public class WorkManagerImpl extends WorkManager {
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
synchronized (sLock) {
...
if (sDefaultInstance == null) {
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
}
sDelegatedInstance = sDefaultInstance;
}
}
}
WorkManagerImpl 构造
在 WorkManagerImpl 的构造方法中新建了一个 Processor,用来处理任务,构造一个 List 得到任务调度器列表。 然后将 processor 传入 internalInit 初始化。
public class WorkManagerImpl extends WorkManager {
public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
@NonNull WorkDatabase database) {
...
List<Scheduler> schedulers =
createSchedulers(applicationContext, configuration, workTaskExecutor);
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}
WorkManagerImpl.createSchedulers
WorkManagerImpl 的 createSchedulers 构造了 2 个 Scheduler。
- createBestAvailableBackgroundScheduler 用来选择合适的后台调度器
- GreedyScheduler 用来调度执行没有任何约束的任务
public class WorkManagerImpl extends WorkManager {
public List<Scheduler> createSchedulers(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor taskExecutor) {
return Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(context, this),
// Specify the task executor directly here as this happens before internalInit.
// GreedyScheduler creates ConstraintTrackers and controllers eagerly.
new GreedyScheduler(context, configuration, taskExecutor, this));
}
Schedulers.createBestAvailableBackgroundScheduler
Schedulers 的 createBestAvailableBackgroundScheduler 根据当前手机版本选择不同的调度器,用来在 app 处于后台时调用系统级别的服务执行任务。
- 系统版本大于等于 23。使用 SystemJobScheduler,启动 SystemJobService 执行任务。
- 系统版本小于 23。通过反射查看手机是否支持 GCM 谷歌云消息服务,如果支持则返回 GCM 调度器 GcmScheduler
- 如果系统版本小于 23且不支持 GCM。使用 SystemAlarmScheduler,通过 AlarmManager 执行任务。
public class Schedulers {
static Scheduler createBestAvailableBackgroundScheduler(
@NonNull Context context,
@NonNull WorkManagerImpl workManager) {
Scheduler scheduler;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
} else {
scheduler = tryCreateGcmBasedScheduler(context);
if (scheduler == null) {
scheduler = new SystemAlarmScheduler(context);
setComponentEnabled(context, SystemAlarmService.class, true);
Logger.get().debug(TAG, "Created SystemAlarmScheduler");
}
}
return scheduler;
}
总结
WorkManager 适合做持续性的后台任务,比如日志、图片、文件上传等。
相比普通的后台任务,WorkManager 可以做一次性任务、周期性任务、推迟任务,它也可以在 app 或者系统重启时重新调度任务,设置各种系统约束。WorkManager 还可以将任务组合成任务链的形式,保证任务的调度顺序。
从实现原理上来说,WorkManager 会根据不同的系统版本,选择 JobService、GCM 或者 AlarmMananger 执行任务。