JetPack 之 WorkManager

Q: 什么是WorkManager蒙幻?

WorkManager 是一個(gè) Android Jetpack 庫从撼,當(dāng)滿足工作的約束條件時(shí)怜森,用來運(yùn)行可延遲、需要保障的后臺(tái)工作暇咆,即使應(yīng)用程序退出锋爪,系統(tǒng)也會(huì)運(yùn)行它們。

Q:WorkManager的適用場景糯崎?

1、可延遲進(jìn)行的任務(wù)

a.滿足某些條件才執(zhí)行的任務(wù)河泳,如需要在充電時(shí)才執(zhí)行的任務(wù)沃呢。 b.用戶無感知或可延遲感知的任務(wù),如同步配置信息拆挥,同步資源薄霜,同步通訊錄等某抓。

2、定期重復(fù)性任務(wù)惰瓜,但時(shí)效性要求不高的否副,如定期 log 上傳,數(shù)據(jù)備份等崎坊。
3备禀、退出應(yīng)用后還應(yīng)繼續(xù)執(zhí)行的未完成任務(wù)。

Q:WorkManager的好處奈揍?

  • 處理不同系統(tǒng)版本的兼容性
  • 遵循系統(tǒng)健康最佳實(shí)踐
  • 支持異步一次性和周期性任務(wù)
  • 支持帶輸入/輸出的鏈?zhǔn)饺蝿?wù)
  • 允許你設(shè)置在任務(wù)運(yùn)行時(shí)的約束
  • 即使應(yīng)用程序或設(shè)備重啟曲尸,也可以保證任務(wù)執(zhí)行

Q:WorkManager的特點(diǎn)?

保證任務(wù)一定會(huì)被執(zhí)行

WorkManager 有自己的數(shù)據(jù)庫男翰,每一個(gè)任務(wù)的信息與任務(wù)狀態(tài)另患,都會(huì)保存在本地?cái)?shù)據(jù)庫中。所以即使程序沒有在運(yùn)行蛾绎,或者在設(shè)備重啟等情況下昆箕,WorkManager 依然可以保證任務(wù)的執(zhí)行,只是不保證任務(wù)立即被執(zhí)行租冠。

合理使用設(shè)備資源

在執(zhí)行很多周期性或非立即執(zhí)行的任務(wù)時(shí)鹏倘,WorkManager 提供我們 API,幫助我們合理利用設(shè)備資源肺稀,避免不必要的內(nèi)存第股,流量,電量等消耗话原。

Q:WorkManager的初始化夕吻?

1、WorkManager 的初始化是在 app 冷啟動(dòng)后繁仁,由 WorkManagerInitializer 這個(gè) ContentProvider 執(zhí)行的涉馅。

2、初始化過程包含了 Configuration黄虱,WorkManagerTaskExecutor稚矿,WorkDatabase,Schedulers捻浦,Processor 等的初始化過程晤揣。
Schedulers 有兩個(gè)。
(1) GreedyScheduler: 執(zhí)行沒有任何約束的非周期性的任務(wù)朱灿。
(2)SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler: 執(zhí)行周期性或者有約束性的任務(wù)昧识。優(yōu)先返回 SystemJobScheduler,在 build version 小于 23 的情況下先嘗試返回 GcmBasedScheduler盗扒,若返回為空再返回 SystemAlarmScheduler跪楞。

3缀去、初始化的最后,會(huì)根據(jù)情況找到需要被執(zhí)行的任務(wù)進(jìn)行調(diào)度執(zhí)行甸祭。

Q: 不帶約束條件的任務(wù)執(zhí)行流程缕碎?

1、 在 WorkManager 執(zhí)行了 enqueue() 后池户,創(chuàng)建 WorkContinuationImpl 對象執(zhí)行 enqueue() 方法咏雌。

2、WorkContinuationImpl 持有的 EnqueueRunnable 對象將任務(wù)添加到 db煞檩,并交給 Schedulers 去調(diào)度处嫌。

3、Schedulers 將任務(wù)交給每一個(gè) Scheduler 去處理斟湃。假設(shè)是GreedyScheduler 會(huì)先處理這個(gè)任務(wù)熏迹。

4、GreedyScheduler 經(jīng)過一系列判斷后凝赛,調(diào)用 WorkManager 的 startWork() 方法執(zhí)行這種一次性注暗,非延遲,無約束的任務(wù)墓猎。

5捆昏、WorkManager 持有的 StartWorkRunnable 對象會(huì)將任務(wù)交給 Processor 去處理,執(zhí)行 startWork() 方法毙沾。

6骗卜、Processor 創(chuàng)建一個(gè) WorkerWrapper 對象,由它去調(diào)用 Worker 的 startWork() 方法左胞,執(zhí)行我們自定義 worker 的任務(wù)寇仓,并返回相應(yīng)的 result。

7烤宙、任務(wù)完成后遍烦,WorkerWrapper 會(huì)根據(jù) result 對任務(wù)狀態(tài),db 等進(jìn)行更新躺枕,然后 schedule 下一個(gè)任務(wù)服猪。

Q:帶約束條件的任務(wù)執(zhí)行過程?

在任務(wù)執(zhí)行的過程中拐云,由于增加了約束條件罢猪,常駐的 GreedyScheduler 的 schedule() 方法將不會(huì) startWork()核行,而是根據(jù) build version 交由 SystemJobScheduler 或 SystemAlarmScheduler 進(jìn)行處理司顿。

SystemJobScheduler (Build Version >=23)
合理使用設(shè)備資源
>> 在執(zhí)行很多周期性或非立即執(zhí)行的任務(wù)時(shí)叮趴,WorkManager 提供我們 API纠吴,幫助我們合理利用設(shè)備資源,避免不必要的內(nèi)存贤笆,流量挤安,電量等消耗挡篓。

Q:WorkManager的初始化捅暴?
> 1恬砂、WorkManager 的初始化是在 app 冷啟動(dòng)后,由 WorkManagerInitializer 這個(gè) ContentProvider 執(zhí)行的蓬痒。

>2泻骤、初始化過程包含了 Configuration,WorkManagerTaskExecutor梧奢,WorkDatabase狱掂,Schedulers,Processor 等的初始化過程亲轨。
Schedulers 有兩個(gè)趋惨。
>(1) GreedyScheduler: 執(zhí)行沒有任何約束的非周期性的任務(wù)。
(2)SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler: 執(zhí)行周期性或者有約束性的任務(wù)惦蚊。優(yōu)先返回 SystemJobScheduler器虾,在 build version 小于 23 的情況下先嘗試返回 GcmBasedScheduler,若返回為空再返回 SystemAlarmScheduler蹦锋。

>3兆沙、初始化的最后,會(huì)根據(jù)情況找到需要被執(zhí)行的任務(wù)進(jìn)行調(diào)度執(zhí)行莉掂。

Q: 不帶約束條件的任務(wù)執(zhí)行流程葛圃?
>1、 在 WorkManager 執(zhí)行了 enqueue() 后憎妙,創(chuàng)建 WorkContinuationImpl 對象執(zhí)行 enqueue() 方法库正。

> 2、WorkContinuationImpl 持有的 EnqueueRunnable 對象將任務(wù)添加到 db尚氛,并交給 Schedulers 去調(diào)度诀诊。

>3、Schedulers 將任務(wù)交給每一個(gè) Scheduler 去處理阅嘶。假設(shè)是GreedyScheduler 會(huì)先處理這個(gè)任務(wù)属瓣。

>4、GreedyScheduler 經(jīng)過一系列判斷后讯柔,調(diào)用 WorkManager 的 startWork() 方法執(zhí)行這種一次性抡蛙,非延遲,無約束的任務(wù)魂迄。

5粗截、WorkManager 持有的 StartWorkRunnable 對象會(huì)將任務(wù)交給 Processor 去處理,執(zhí)行 startWork() 方法捣炬。

6熊昌、Processor 創(chuàng)建一個(gè) WorkerWrapper 對象绽榛,由它去調(diào)用 Worker 的 startWork() 方法,執(zhí)行我們自定義 worker 的任務(wù)婿屹,并返回相應(yīng)的 result灭美。

7、任務(wù)完成后昂利,WorkerWrapper 會(huì)根據(jù) result 對任務(wù)狀態(tài)届腐,db 等進(jìn)行更新,然后 schedule 下一個(gè)任務(wù)蜂奸。

Q:帶約束條件的任務(wù)執(zhí)行過程犁苏?
> 在任務(wù)執(zhí)行的過程中,由于增加了約束條件扩所,常駐的 GreedyScheduler 的 schedule() 方法將不會(huì) startWork()围详,而是根據(jù) build version 交由 SystemJobScheduler 或 SystemAlarmScheduler 進(jìn)行處理。
>>SystemJobScheduler (Build Version >=23)
在SystemJobScheduler的構(gòu)造函數(shù)中祖屏,會(huì)獲取JobScheduler對象短曾,并傳入了一個(gè)SystemJobInfoConverter對象( 用來將我們的workSpecs轉(zhuǎn)化為JobInfo,這個(gè)JobInfo是jobScheduler需要的參數(shù) 赐劣,用來描述任務(wù)的執(zhí)行時(shí)間嫉拐,條件,策略等一系列的行為魁兼。)婉徘,在SystemJobScheduler 的 schedule() 方法執(zhí)行了 scheduleInternal();

JobScheduler是系統(tǒng)服務(wù)咐汞,由android程序啟動(dòng)的時(shí)候自動(dòng)拉起無需手動(dòng)去操作,而與JobScheduler對應(yīng)的jobservice由系統(tǒng)自動(dòng)生成盖呼,這里的JobService是SystemJobService,當(dāng)JobScheduler中的條件滿足時(shí)化撕,SystemJobService就會(huì)被調(diào)用到几晤。

在 SystemJobService中的onStartJob中,重點(diǎn)是最后一個(gè)方法植阴,該方法調(diào)用了WorkManagerImpl.startWork蟹瘾。

SystemAlarmScheduler(Build Version < 23)
SystemAlarmScheduler 使用的是 AlarmManager 來調(diào)度執(zhí)行任務(wù)。
在 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>

在電量變化時(shí)掠手,收到 BATTERY_LOW 的廣播憾朴。在 BatteryNotLowProxy 的 onReceive() 進(jìn)行處理:


//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() 的執(zhí)行如下:


//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;
    }

調(diào)用了 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() 方法中執(zhí)行了 processCommand()喷鸽,這段代碼的核心執(zhí)行語句是:


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

在 CommandHandler 的 onHandleIntent() 方法中众雷,action 為 ACTION_CONSTRAINTS_CHANGED 的執(zhí)行是:


//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() 方法的執(zhí)行中,會(huì)創(chuàng)建一個(gè) action 為 ACTION_DELAY_MET 的 Intent 然后由 SystemAlarmDispatcher 發(fā)送出去,實(shí)際上也是調(diào)用了 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 的執(zhí)行是:


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

handleDelayMet() 的執(zhí)行過程,會(huì)調(diào)用 DelayMetCommandHandler 的 handleProcessWork() 方法翻诉,接著執(zhí)行 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 調(diào)用了 Processor 的 startWork() 方法,回到了之前章節(jié)分析的任務(wù)執(zhí)行流程捌刮。到此為止碰煌,一個(gè)任務(wù)在不同條件下的創(chuàng)建,執(zhí)行流程就分析完畢绅作。

Q:WorkManager在設(shè)備重啟后如何保證任務(wù)可以得到執(zhí)行芦圾?

WorkManager在初始化時(shí)創(chuàng)建了Room數(shù)據(jù)庫, 將任務(wù)列表序列化到本地俄认,記錄每一個(gè)任務(wù)的屬性个少,執(zhí)行條件,執(zhí)行順序及執(zhí)行狀態(tài)等眯杏。從而保證任務(wù)在冷啟動(dòng)或硬件重啟后夜焦,可以根據(jù)條件繼續(xù)執(zhí)行。

WorkManager 流程分析和源碼解析 | 開發(fā)者說·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 處理需要立刻執(zhí)行的后臺(tái)任務(wù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岂贩,一起剝皮案震驚了整個(gè)濱河市茫经,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萎津,老刑警劉巖卸伞,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锉屈,居然都是意外死亡荤傲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門颈渊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遂黍,“玉大人,你說我怎么就攤上這事俊嗽〖讼妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵乌询,是天一觀的道長榜贴。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么唬党? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任鹃共,我火速辦了婚禮,結(jié)果婚禮上驶拱,老公的妹妹穿的比我還像新娘霜浴。我一直安慰自己,他們只是感情好蓝纲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布阴孟。 她就那樣靜靜地躺著,像睡著了一般税迷。 火紅的嫁衣襯著肌膚如雪永丝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天箭养,我揣著相機(jī)與錄音慕嚷,去河邊找鬼。 笑死毕泌,一個(gè)胖子當(dāng)著我的面吹牛喝检,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撼泛,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挠说,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愿题?” 一聲冷哼從身側(cè)響起纺涤,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抠忘,沒想到半個(gè)月后撩炊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崎脉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年拧咳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囚灼。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骆膝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灶体,到底是詐尸還是另有隱情阅签,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布蝎抽,位于F島的核電站政钟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜养交,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一精算、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碎连,春花似錦灰羽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至倒戏,卻和暖如春怠噪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峭梳。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹂喻,地道東北人葱椭。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像口四,于是被迫代替她去往敵國和親孵运。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容