JetPack 之 WorkManager

Q: 什么是WorkManager?

WorkManager 是一个 Android Jetpack 库,当满足工作的约束条件时,用来运行可延迟、需要保障的后台工作,即使应用程序退出,系统也会运行它们。

Q:WorkManager的适用场景?

1、可延迟进行的任务

a.满足某些条件才执行的任务,如需要在充电时才执行的任务。 b.用户无感知或可延迟感知的任务,如同步配置信息,同步资源,同步通讯录等。

2、定期重复性任务,但时效性要求不高的,如定期 log 上传,数据备份等。
3、退出应用后还应继续执行的未完成任务。

Q:WorkManager的好处?

  • 处理不同系统版本的兼容性
  • 遵循系统健康最佳实践
  • 支持异步一次性和周期性任务
  • 支持带输入/输出的链式任务
  • 允许你设置在任务运行时的约束
  • 即使应用程序或设备重启,也可以保证任务执行

Q:WorkManager的特点?

保证任务一定会被执行

WorkManager 有自己的数据库,每一个任务的信息与任务状态,都会保存在本地数据库中。所以即使程序没有在运行,或者在设备重启等情况下,WorkManager 依然可以保证任务的执行,只是不保证任务立即被执行。

合理使用设备资源

在执行很多周期性或非立即执行的任务时,WorkManager 提供我们 API,帮助我们合理利用设备资源,避免不必要的内存,流量,电量等消耗。

Q:WorkManager的初始化?

1、WorkManager 的初始化是在 app 冷启动后,由 WorkManagerInitializer 这个 ContentProvider 执行的。

2、初始化过程包含了 Configuration,WorkManagerTaskExecutor,WorkDatabase,Schedulers,Processor 等的初始化过程。
Schedulers 有两个。
(1) GreedyScheduler: 执行没有任何约束的非周期性的任务。
(2)SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler: 执行周期性或者有约束性的任务。优先返回 SystemJobScheduler,在 build version 小于 23 的情况下先尝试返回 GcmBasedScheduler,若返回为空再返回 SystemAlarmScheduler。

3、初始化的最后,会根据情况找到需要被执行的任务进行调度执行。

Q: 不带约束条件的任务执行流程?

1、 在 WorkManager 执行了 enqueue() 后,创建 WorkContinuationImpl 对象执行 enqueue() 方法。

2、WorkContinuationImpl 持有的 EnqueueRunnable 对象将任务添加到 db,并交给 Schedulers 去调度。

3、Schedulers 将任务交给每一个 Scheduler 去处理。假设是GreedyScheduler 会先处理这个任务。

4、GreedyScheduler 经过一系列判断后,调用 WorkManager 的 startWork() 方法执行这种一次性,非延迟,无约束的任务。

5、WorkManager 持有的 StartWorkRunnable 对象会将任务交给 Processor 去处理,执行 startWork() 方法。

6、Processor 创建一个 WorkerWrapper 对象,由它去调用 Worker 的 startWork() 方法,执行我们自定义 worker 的任务,并返回相应的 result。

7、任务完成后,WorkerWrapper 会根据 result 对任务状态,db 等进行更新,然后 schedule 下一个任务。

Q:带约束条件的任务执行过程?

在任务执行的过程中,由于增加了约束条件,常驻的 GreedyScheduler 的 schedule() 方法将不会 startWork(),而是根据 build version 交由 SystemJobScheduler 或 SystemAlarmScheduler 进行处理。

SystemJobScheduler (Build Version >=23)
合理使用设备资源
>> 在执行很多周期性或非立即执行的任务时,WorkManager 提供我们 API,帮助我们合理利用设备资源,避免不必要的内存,流量,电量等消耗。

Q:WorkManager的初始化?
> 1、WorkManager 的初始化是在 app 冷启动后,由 WorkManagerInitializer 这个 ContentProvider 执行的。

>2、初始化过程包含了 Configuration,WorkManagerTaskExecutor,WorkDatabase,Schedulers,Processor 等的初始化过程。
Schedulers 有两个。
>(1) GreedyScheduler: 执行没有任何约束的非周期性的任务。
(2)SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler: 执行周期性或者有约束性的任务。优先返回 SystemJobScheduler,在 build version 小于 23 的情况下先尝试返回 GcmBasedScheduler,若返回为空再返回 SystemAlarmScheduler。

>3、初始化的最后,会根据情况找到需要被执行的任务进行调度执行。

Q: 不带约束条件的任务执行流程?
>1、 在 WorkManager 执行了 enqueue() 后,创建 WorkContinuationImpl 对象执行 enqueue() 方法。

> 2、WorkContinuationImpl 持有的 EnqueueRunnable 对象将任务添加到 db,并交给 Schedulers 去调度。

>3、Schedulers 将任务交给每一个 Scheduler 去处理。假设是GreedyScheduler 会先处理这个任务。

>4、GreedyScheduler 经过一系列判断后,调用 WorkManager 的 startWork() 方法执行这种一次性,非延迟,无约束的任务。

5、WorkManager 持有的 StartWorkRunnable 对象会将任务交给 Processor 去处理,执行 startWork() 方法。

6、Processor 创建一个 WorkerWrapper 对象,由它去调用 Worker 的 startWork() 方法,执行我们自定义 worker 的任务,并返回相应的 result。

7、任务完成后,WorkerWrapper 会根据 result 对任务状态,db 等进行更新,然后 schedule 下一个任务。

Q:带约束条件的任务执行过程?
> 在任务执行的过程中,由于增加了约束条件,常驻的 GreedyScheduler 的 schedule() 方法将不会 startWork(),而是根据 build version 交由 SystemJobScheduler 或 SystemAlarmScheduler 进行处理。
>>SystemJobScheduler (Build Version >=23)
在SystemJobScheduler的构造函数中,会获取JobScheduler对象,并传入了一个SystemJobInfoConverter对象( 用来将我们的workSpecs转化为JobInfo,这个JobInfo是jobScheduler需要的参数 ,用来描述任务的执行时间,条件,策略等一系列的行为。),在SystemJobScheduler 的 schedule() 方法执行了 scheduleInternal();

JobScheduler是系统服务,由android程序启动的时候自动拉起无需手动去操作,而与JobScheduler对应的jobservice由系统自动生成,这里的JobService是SystemJobService,当JobScheduler中的条件满足时,SystemJobService就会被调用到。

在 SystemJobService中的onStartJob中,重点是最后一个方法,该方法调用了WorkManagerImpl.startWork。

SystemAlarmScheduler(Build Version < 23)
SystemAlarmScheduler 使用的是 AlarmManager 来调度执行任务。
在 AndroidManifest 里有如下 receiver 注册:


<receiver android:directBootAware="false" android:enabled="false" android:exported="false" android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy">
    <intent-filter>
          <action android:name="android.intent.action.BATTERY_OKAY"/>
          <action android:name="android.intent.action.BATTERY_LOW"/>
    </intent-filter>
</receiver>

在电量变化时,收到 BATTERY_LOW 的广播。在 BatteryNotLowProxy 的 onReceive() 进行处理:


//ConstraintProxy
    public static class BatteryNotLowProxy extends ConstraintProxy {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Logger.get().debug(TAG, String.format("onReceive : %s", intent));
        Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
    }

createConstraintsChangedIntent() 的执行如下:


//ConstraintProxy
   static Intent createConstraintsChangedIntent(@NonNull Context context) {
       Intent intent = new Intent(context, SystemAlarmService.class);
       intent.setAction(ACTION_CONSTRAINTS_CHANGED);
       return intent;
   }

SystemAlarmService 的 onStartCommand() 处理如下:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        ... ...

        if (intent != null) {
            mDispatcher.add(intent, startId);
        }

        // If the service were to crash, we want all unacknowledged Intents to get redelivered.
        return Service.START_REDELIVER_INTENT;
    }

调用了 SystemAlarmDispatcher.add() 方法。

//SystemAlarmDispatcher
@MainThread
    public boolean add(@NonNull final Intent intent, final int startId) {
        ... ...
        if (CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action)
                && hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED)) {
            return false;
        }

        intent.putExtra(KEY_START_ID, startId);
        synchronized (mIntents) {
            boolean hasCommands = !mIntents.isEmpty();
            mIntents.add(intent);
            if (!hasCommands) {
                // Only call processCommand if this is the first command.
                // The call to dequeueAndCheckForCompletion will process the remaining commands
                // in the order that they were added.
                processCommand();
            }
        }
        return true;
    }

add() 方法中执行了 processCommand(),这段代码的核心执行语句是:


//SystemAlarmDispatcher
mCommandHandler.onHandleIntent(mCurrentIntent, startId,
                                    SystemAlarmDispatcher.this);

在 CommandHandler 的 onHandleIntent() 方法中,action 为 ACTION_CONSTRAINTS_CHANGED 的执行是:


//CommandHandler
 if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
            handleConstraintsChanged(intent, startId, dispatcher);
        } 

//CommandHandler
    private void handleConstraintsChanged(
            @NonNull Intent intent, int startId,
            @NonNull SystemAlarmDispatcher dispatcher) {

        Logger.get().debug(TAG, String.format("Handling constraints changed %s", intent));
        // Constraints changed command handler is synchronous. No cleanup
        // is necessary.
        ConstraintsCommandHandler changedCommandHandler =
                new ConstraintsCommandHandler(mContext, startId, dispatcher);
        changedCommandHandler.handleConstraintsChanged();
    }

在 handleConstraintsChanged() 方法的执行中,会创建一个 action 为 ACTION_DELAY_MET 的 Intent 然后由 SystemAlarmDispatcher 发送出去,实际上也是调用了 SystemAlarmDispatcher.add() 方法。回到 SystemAlarmDispatcher 的 add() 流程。


//ConstraintsCommandHandler
Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
            Logger.get().debug(TAG, String.format(
                    "Creating a delay_met command for workSpec with id (%s)", workSpecId));
            mDispatcher.postOnMainThread(
                    new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));

回到 onHandleIntent() 方法,在 CommandHandler 的 onHandleIntent() 方法中,action 为 ACTION_DELAY_MET 的执行是:


//CommandHandler
else if (ACTION_DELAY_MET.equals(action)) {
                    handleDelayMet(intent, startId, dispatcher);
                } 

handleDelayMet() 的执行过程,会调用 DelayMetCommandHandler 的 handleProcessWork() 方法,接着执行 onAllConstraintsMet():

@Override
    public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
        ... ...
        synchronized (mLock) {
            if (mCurrentState == STATE_INITIAL) {
                ... ...
                boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
                ... ...
            } else {
                Logger.get().debug(TAG, String.format("Already started work for %s", mWorkSpecId));
            }
        }
    }

到这里终于看到由 SystemAlarmDispatcher 调用了 Processor 的 startWork() 方法,回到了之前章节分析的任务执行流程。到此为止,一个任务在不同条件下的创建,执行流程就分析完毕。

Q:WorkManager在设备重启后如何保证任务可以得到执行?

WorkManager在初始化时创建了Room数据库, 将任务列表序列化到本地,记录每一个任务的属性,执行条件,执行顺序及执行状态等。从而保证任务在冷启动或硬件重启后,可以根据条件继续执行。

WorkManager 流程分析和源码解析 | 开发者说·DTalk
](https://mp.weixin.qq.com/s?__biz=MzAwODY4OTk2Mg==&mid=2652069060&idx=2&sn=77398948c657a70aeae4db35f8042a35&chksm=808cfc81b7fb759727b3b96d41addb3d6bb5f8793c064fb295631f0be496a4567ba75214dd3d&mpshare=1&scene=23&srcid=1107uIVkYdQ2fu0qgbADCEBW&sharer_sharetime=1604719995685&sharer_shareid=8206e3713102e9ca474649d51eda190c%23rd)

使用 WorkManager 处理需要立刻执行的后台任务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容