JobScheduler 是一個(gè)新特性挚币,用于實(shí)現(xiàn)周期性或定時(shí)運(yùn)行的亮蒋,且對具體運(yùn)行時(shí)間點(diǎn)要求不高的后臺任務(wù),僅兼容 API level 21 以上妆毕。
JobScheduler 提供的功能其實(shí)在以往的系統(tǒng)上完全可以通過廣播加服務(wù)的方式實(shí)現(xiàn)慎玖,但它的可貴之處有二:
節(jié)能 - 在于通過 JobScheduler 計(jì)劃的任務(wù)會經(jīng)過系統(tǒng)調(diào)度,以類似某些 ROM 中對齊喚醒的機(jī)制笛粘,把數(shù)個(gè)時(shí)間點(diǎn)相近的任務(wù)打包在一起執(zhí)行凄吏,創(chuàng)造休眠窗口,避免了系統(tǒng)被不斷喚醒闰蛔。
易用 - 在于它 API 簡單易用,一聲明一計(jì)劃即可图柏。且原生提供了網(wǎng)絡(luò)狀況序六、是否充電、是否閑置等數(shù)個(gè)條件的限制蚤吹,節(jié)約了手動實(shí)現(xiàn)的工作例诀。
對于諸如每周清理緩存、手機(jī)閑置一分鐘后清理后臺應(yīng)用裁着、添加了新聯(lián)系人時(shí)同步到服務(wù)器之類定時(shí)或者周期運(yùn)行繁涂,而且早一點(diǎn)晚一點(diǎn)都不要緊的后臺工作,實(shí)現(xiàn)起來可是輕松極了二驰。
1. 實(shí)現(xiàn) JobService
第一步扔罪,決定任務(wù)被觸發(fā)時(shí)要做什么。
首先繼承 JobService
類桶雀,有兩個(gè)抽象方法必須實(shí)現(xiàn):
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
params
參數(shù)中保存著任務(wù) ID 和配置任務(wù)時(shí)攜帶的額外信息矿酵,用于區(qū)分不同的任務(wù)。
1.1. onStartJob()
任務(wù)啟動時(shí)被調(diào)用矗积,在這時(shí)執(zhí)行具體任務(wù)內(nèi)容全肮。方法返回值分兩種情況:
-
false
- 表示任務(wù)在方法返回時(shí)就已經(jīng)同步地執(zhí)行完了 -
true
- 表示方法返回時(shí)還有異步的任務(wù)沒有執(zhí)行完畢,執(zhí)行完時(shí)需要手動調(diào)用jobFinished(JobParameters, boolean)
方法
jobFinished 方法的第二個(gè)參數(shù)指是否需要重新把這個(gè)任務(wù)加入計(jì)劃中棘捣,可以理解為任務(wù)執(zhí)行失敗了辜腺,值為 true
時(shí)會啟動任務(wù)重試,具體重試機(jī)制在下文詳述乍恐。
需要注意的是這個(gè)方法是在應(yīng)用的主線程執(zhí)行的评疗,因此平時(shí)開發(fā)時(shí)需要注意的問題在這里也適用。
1.2. onStopJob()
任務(wù)執(zhí)行完畢禁熏,被取消或者沒有滿足運(yùn)行條件而被放棄時(shí)調(diào)用壤巷,在這個(gè)方法內(nèi)做收尾善后工作。方法返回值同樣分為兩種情況:
-
false
- 徹底放棄這個(gè)任務(wù) -
true
- 重新計(jì)劃這次任務(wù)瞧毙,啟動重試機(jī)制
不管返回值如何胧华,這個(gè)方法被調(diào)用的時(shí)候都應(yīng)該停止目前正在執(zhí)行的工作寄症。
1.3. 舉個(gè)例子
private boolean stillRunning;
@Override
public boolean onStartJob(final JobParameters params) {
new Thread(() -> {
PersistableBundle bundle = params.getExtras();
int userId = bundle.getInt(KEY_PARAM_USER_ID);
stillRunning = true;
while (stillRunning) {
sendSpam(userId);
}
}).start();
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
stillRunning = false;
return false;
}
JobService 寫完后在清單文件中注冊,聲明 android.permission.BIND_JOB_SERVICE
權(quán)限即可矩动。
<service
android:name=".service.MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
2. JobInfo.Builder
決定好了任務(wù)怎樣執(zhí)行有巧,接下來該決定任務(wù)何時(shí)執(zhí)行了。JobScheduler 使用 JobInfo
類來描述任務(wù)觸發(fā)的種種條件悲没, JobInfo 實(shí)例使用建造者模式來初始化篮迎。
2.1. 條件限制
需要注意的是以下條件都是嚴(yán)格限制的,如果條件一直沒有被滿足示姿,任務(wù)就一直不會執(zhí)行甜橱。如果希望即使條件沒有滿足也在某個(gè)時(shí)間點(diǎn)執(zhí)行任務(wù),則需要配合下文中的超時(shí)強(qiáng)制執(zhí)行功能使用栈戳。
-
setRequiredNetworkType(int)
網(wǎng)絡(luò)類型限制岂傲,可選項(xiàng)有四個(gè):-
NETWORK_TYPE_NONE
:缺省值。對聯(lián)網(wǎng)狀態(tài)沒有要求子檀,離線也可以镊掖。 -
NETWORK_TYPE_ANY
:只要聯(lián)網(wǎng)就可以。 -
NETWORK_TYPE_UNMETERED
:要求非付費(fèi)網(wǎng)絡(luò)褂痰,『非付費(fèi)』的定義和系統(tǒng)里那個(gè)一樣難以捉摸亩进。 -
NETWORK_TYPE_NOT_ROAMING
:要求非漫游網(wǎng)絡(luò)。
-
setRequiresCharging(boolean)
要求正在充電缩歪,默認(rèn)關(guān)閉归薛。setRequiresDeviceIdle(boolean)
要求設(shè)備正在閑置狀態(tài),默認(rèn)關(guān)閉匪蝙。根據(jù)文檔說明苟翻,這里的『閑置狀態(tài)』是個(gè)寬泛的概念,指手機(jī)一段時(shí)間沒有被使用后進(jìn)入的狀態(tài)骗污。
2.2. 觸發(fā)條件
以下觸發(fā)條件只能粗略限制執(zhí)行時(shí)機(jī)崇猫,實(shí)際執(zhí)行的時(shí)間點(diǎn)還是要依靠系統(tǒng)調(diào)度的,并不能保證條件滿足了就會立刻執(zhí)行需忿。
addTriggerContentUri(TriggerContentUri)
使用ContentObserver
監(jiān)聽指定 URI诅炉,URI 內(nèi)容發(fā)生變動時(shí)觸發(fā)任務(wù)。設(shè)備重啟后失效屋厘。
因?yàn)橐粋€(gè)任務(wù)只監(jiān)聽一次變動涕烧,如果想監(jiān)聽到所有變動,則需要在處理任務(wù)并返回方法之前重新計(jì)劃一個(gè)監(jiān)聽任務(wù)汗洒。setTriggerContentUpdateDelay(long)
在監(jiān)聽的 URI 發(fā)生變動之后延遲一段時(shí)間才執(zhí)行任務(wù)议纯。如果在延遲時(shí)間內(nèi)有新的變動發(fā)生了,就拋棄當(dāng)前等待的變動溢谤,對新的變動重新開始計(jì)時(shí)瞻凤,只針對最近的一次變動執(zhí)行計(jì)劃任務(wù)憨攒。setTriggerContentMaxDelay(long)
設(shè)置單次變動等待超時(shí)。在監(jiān)聽的 URI 發(fā)生變動后最多等待指定的時(shí)間阀参,如果系統(tǒng)一直沒有調(diào)度到這次變動的任務(wù)肝集,就在超時(shí)時(shí)強(qiáng)制執(zhí)行。setPeriodic(long interval)
循環(huán)執(zhí)行任務(wù)蛛壳。但是只保證在指定interval
內(nèi)只執(zhí)行一次杏瞻,具體在這個(gè)時(shí)間段內(nèi)的哪個(gè)時(shí)間點(diǎn)執(zhí)行就不知道了。所以前一次任務(wù)到最后才執(zhí)行衙荐,而后一次任務(wù)一開始就執(zhí)行了捞挥,結(jié)果相當(dāng)于連續(xù)執(zhí)行了兩次任務(wù)也是有可能的。并不能當(dāng)定時(shí)器使用忧吟。
間隔時(shí)間最短為十五分鐘树肃,也可以設(shè)置為更短,但執(zhí)行時(shí)會被強(qiáng)制限制到十五分鐘瀑罗。
和 URI 監(jiān)聽功能一起使用沒有意義,同時(shí)設(shè)置的話會拋出異常雏掠。setPeriodic(long interval, long flexMillis)
同上斩祭,循環(huán)執(zhí)行任務(wù),區(qū)別在于可以把任務(wù)執(zhí)行的時(shí)間范圍限制在指定時(shí)間段內(nèi)的倒數(shù)flexMillis
毫秒里乡话。相比起上面的方法摧玫,可以做到更接近定時(shí)器的效果。
但flexMillis
也是有最短限制的绑青,第一不能小于五分鐘诬像,第二不能小于interval
的 5%。所以setPeriodic(30 * 60 * 1000, 0)
這種詭計(jì)用法就不要想了闸婴。setMinimumLatency(long)
設(shè)置最小延時(shí)坏挠,滿足條件后等待指定時(shí)間才執(zhí)行任務(wù)。
由于循環(huán)任務(wù)本來就沒有具體的執(zhí)行時(shí)間點(diǎn)邪乍,設(shè)置延時(shí)也沒有意義降狠,所以不能和setPeriodic
方法同時(shí)使用。setOverrideDeadline(long)
設(shè)置等待超時(shí)庇楞,超時(shí)后即使任何條件都沒滿足也會執(zhí)行任務(wù)榜配。同樣無法與setPeriodic
方法同時(shí)使用。
2.3. 其他設(shè)置
setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
設(shè)置任務(wù)執(zhí)行失敗時(shí)的重試策略吕晌。backoffPolicy
有兩種選項(xiàng)可用蛋褥,設(shè)置為BACKOFF_POLICY_LINEAR
時(shí)每次重試的間隔相同,設(shè)置為BACKOFF_POLICY_EXPONENTIAL
時(shí)每次重試的間隔都是上一次的兩倍睛驳。默認(rèn)情況下兩個(gè)值分別是 30 秒和 Exponential烙心。
和setRequiresDeviceIdle
同時(shí)使用的話膜廊,調(diào)用的時(shí)機(jī)會沖突,因此也是不能同時(shí)設(shè)置的弃理。setExtras(PersistableBundle)
與Intent
中的 extras 類似溃论,用于攜帶額外信息。里面的內(nèi)容會被存儲在硬盤上痘昌,所以只允許攜帶基本類型钥勋。setPersisted(boolean)
在關(guān)機(jī)時(shí)把任務(wù)保存在硬盤上,重啟后可以繼續(xù)執(zhí)行計(jì)劃辆苔。默認(rèn)情況下重啟后服務(wù)是運(yùn)行不起來的算灸。
但由于 Android 系統(tǒng)的限制,重啟后繼續(xù) URI 監(jiān)聽是做不到的驻啤,同時(shí)設(shè)置的話會拋異常菲驴。
另外需要在配置文件里加上RECEIVE_BOOT_COMPLETED
權(quán)限,不然也會拋異常骑冗。
2.4. 當(dāng)然赊瞬,
完全沒有限制是絕對不可以的,源碼里的注釋激烈地吐槽了這一點(diǎn):
// Allow jobs with no constraints - What am I, a database?
『你拿我當(dāng)什么了贼涩?』
3. 一切準(zhǔn)備停當(dāng)
最后使用 Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)
拿到 JobScheduler
實(shí)例巧涧,調(diào)用它的 schedule(JobInfo)
方法,就可以安靜等待任務(wù)被執(zhí)行了遥倦。