一. Background
首先這兩個概念是從Android L加入的,最初的目的是為了省電!
從名字可以看出,是一個計劃安排,但肯定不是為了保活就是了,我們可以通過官方一張圖來看一下他是個什么原理。

對于后臺網(wǎng)絡(luò)加載,最初是一進(jìn)行網(wǎng)絡(luò)加載就喚醒設(shè)備,加載就喚醒設(shè)備,這個電量浪費(fèi)可想而知,當(dāng)電量低的時候,進(jìn)行喚醒更不合適,因此在Android L后面加了此API,限制后臺喚醒,系統(tǒng)會在合適的時候一起調(diào)度去運(yùn)行,然后就形成了下面的圖形。
這點(diǎn)應(yīng)該跟MIUI的對齊喚醒類似。
所以根據(jù)上面所說,我們也可以提前知道后面的Api里面為什么會提供一些低電量停止任務(wù),充電時執(zhí)行任務(wù)的方法。
But!
看起來這么高大上的技術(shù),國內(nèi)的廠商卻用他來做?;睿。?!
實際上,在Android5上,JobScheduler的運(yùn)行效果相當(dāng)顯著,在某測試機(jī)上測試,基本上可以按照時間周期性調(diào)度。 (當(dāng)然國內(nèi)各大廠的系統(tǒng)還是太強(qiáng)了……)
但隨著系統(tǒng)的迭代,在Android后面的版本上,系統(tǒng)底層所做的限制越來越多,包括周期執(zhí)行時間不得低于15分鐘,這就讓IM類應(yīng)用很難受了,還是要去另尋他法。
但無論如何,保活都是一個偽命題,就算是加入了系統(tǒng)白名單也不一定能存活或者重新喚醒,國內(nèi)某鵝廠的Tim所采用的Linux底層喚醒,也免不了被一殺啥消息都收不到。
所以我們不談?wù)摫;睿嗟氖怯懻揓obScheduler和JobService用在執(zhí)行后臺簡單任務(wù)上面。
二. Use
JobScheduler是需要協(xié)同JobService來一起使用的,在準(zhǔn)備實現(xiàn)一個Schedule之前,我們要實現(xiàn)一個JobService。
1. 擼起袖子實現(xiàn)的 JobService
首先查看一下文檔, JobService的繼承關(guān)系如下。

JobService 是一個abstract class,繼承自Service,那么他當(dāng)然也會擁有一些Service擁有的特性(使用特點(diǎn)),比如說onCreate,onStartCommand,注冊<service>等等。

對于其中的方法,主要是兩個abstract的method,一個final的方法,而其中的abstract就是我們需要自行去實現(xiàn)的。
擼起袖子就是一頓繼承。
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class DemoJobService extends JobService {
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d("tag","onStartJob");
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d("tag","onStopJob");
return false;
}
}
闊以看到,我們已經(jīng)實現(xiàn)了其中的方法,分別是對應(yīng)job啟動的onStartJob()和job結(jié)束的onStopJob()方法。
當(dāng)然,不要忘記我們的API是在Android L里面加入的,所以加入@RequiresApi注解。
2. 記得注冊了喵~
剛才說過玩service一定不要忘了注冊,所以在各個地方,Google都提醒了你 別忘記注冊?。?!

Android Studio里也有,但是本喵不會截warning的圖
<service android:name=".DemoJobService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"
/>
這家伙與眾不同的一點(diǎn)就是需要配置一個permission。
3. 關(guān)鍵人物 - Scheduler
對于JobScheduler,理論上是暴露給外人用的,比如我們在MainActivity中。
int jobId = 1;
JobScheduler jobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(getPackageName(),
DemoJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(jobId,jobService)
.setPeriodic(15 * 60 * 1000)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
if(jobScheduler != null){
jobScheduler.schedule(jobInfo);
}
但我們可愛的Java程序猿們總喜歡吧功能封裝起來,給他扣一頂可愛的static帽子,然后公布于眾。
于是修改DemoJobService代碼,加一個靜態(tài)的invoke()方法。
public static void invoke(){
int jobId = 1;
JobScheduler jobScheduler = (JobScheduler)
App.instance().getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(App.instance().getPackageName(),
DemoJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(jobId,jobService)
.setPeriodic(15 * 60 * 1000)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
if(jobScheduler != null){
jobScheduler.schedule(jobInfo);
}
}
核心是最后一句,用系統(tǒng)的JobScheduler,執(zhí)行了一個自定義的JobInfo,仔細(xì)查看schedule()方法。

這家伙還有返回值,代表了schedule的結(jié)果。
安排?
ok, return RESULT_SUCCESS
這家伙甚至是一個abstract method,至于誰實現(xiàn)了他,我們先不用管。
這樣調(diào)用變的簡單起來。
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DemoJobService.invoke();
}
});
好了,你可以運(yùn)行一下看看效果了,雖然你什么都不可能看到,偶爾有幸可以看到你的log,為什么這么說,我們后面為你揭曉。
三. Introduce
上面我們已經(jīng)跑了一個只有一個 button的破界面了,可是更強(qiáng)大的機(jī)制在后臺運(yùn)行著呢。
1. 基礎(chǔ)api
int jobId = 1;
JobScheduler jobScheduler = (JobScheduler)
App.instance().getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(App.instance().getPackageName(),
DemoJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(jobId,jobService)
.setPeriodic(15 * 60 * 1000)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
if(jobScheduler != null){
jobScheduler.schedule(jobInfo);
}
還是不變的代碼,變心了的文章。
你變了,變得開始講api怎么使用了,你再也不是balabala給我說的天花亂墜的,我一句聽不懂的,覺得你超帥的,講系統(tǒng)源碼了的那個大佬了。
嫌棄我?那我們簡單了解即可。(自導(dǎo)自演好歡樂)
- jobId就是job的身份證,身份證號有啥特點(diǎn)就不用我說了吧。
- JobScheduler是從系統(tǒng)服務(wù)獲取出來的用于執(zhí)行job的大佬,至于App.instance()是我自己實現(xiàn)的獲取context的方法,下面是代碼,記得添加Application 的name屬性(不會用自定義Application的請面Google思過)
public class App extends Application {
protected static App instance;
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static Context instance() {
return instance.getApplicationContext();
}
}
JobInfo是基于構(gòu)造者模式來實現(xiàn)的,其Builder默認(rèn)需要接受jobId和JobService所包裝的ComponentName,構(gòu)造一個ComponentName對象需要兩個參數(shù),首先是包名,其次是要被安排上的JobService的名字。
setPeriodic(xxx毫秒),重復(fù)執(zhí)行周期,設(shè)置了這個屬性后,系統(tǒng)會每隔xxx毫秒來執(zhí)行一次,勤勤懇懇,不會說累,結(jié)合 setPersisted(true) 使用,媽媽再也不用擔(dān)心我關(guān)了機(jī)jobSevice就不重復(fù)執(zhí)行了餒。
注1. Android L的時候,時間可以設(shè)的很小,比如說10s執(zhí)行一次,系統(tǒng)也很認(rèn)真的10s執(zhí)行一次,但是隨著國內(nèi)各廠利用此Api進(jìn)行保活手段越來越高明,和Google越來越機(jī)靈,在后來的Android版本上,這個時間被限制到15分鐘以上了……尷尬。
注2. setPersisted(true),true為關(guān)機(jī)后再開機(jī)不影響任務(wù),false為關(guān)機(jī)后就死翹翹了。
- setRequiredNetworkType() ,需要在什么網(wǎng)絡(luò)下執(zhí)行,這里我們選的是NETWORK_TYPE_ANY,代表任何網(wǎng)絡(luò)均可。
2. 高級api
翻譯文檔走起!
誰會干那么無聊的事情呢,況且我English也不是very well啊,所以這里就撿幾個重點(diǎn)的說一下吧,有些東西,可能你這輩子有永不到哦,(你非要用那你就用……)
setMinimumLatency (long minLatencyMillis)
設(shè)置job延遲一段時間后執(zhí)行:誰還不是個寶寶,就不能等我打扮完再安排我?ps:打扮時間是
minLatencyMillis 毫秒
調(diào)用了此方法后又設(shè)置了setPeriodic周期將會 boom!
按照邏輯想想也不對是把,設(shè)置了周期又設(shè)置延遲,你讓系統(tǒng)怎么執(zhí)行,按照誰來走?左右為難啊。
setOverrideDeadline (long maxExecutionDelayMillis)
設(shè)置此Job的最大延遲調(diào)度,無論任何條件,即使有條件不滿足,Job也將在該截止時間前運(yùn)行,說白了就是一個系統(tǒng)執(zhí)行此job的deadline。
同樣,設(shè)置了周期時間會拋異常。
setRequiredNetworkType (int networkType)
在某種 網(wǎng)絡(luò)類型下才可以執(zhí)行此任務(wù),比如說無網(wǎng),任何網(wǎng)絡(luò),wifi,4g等等
setRequiresBatteryNotLow (boolean batteryNotLow)
設(shè)置是否低電量才執(zhí)行這個任務(wù)
setRequiresCharging (boolean requiresCharging)
設(shè)置是否在充電時執(zhí)行任務(wù)
setRequiresDeviceIdle (boolean requiresDeviceIdle)
設(shè)置是否在交互時執(zhí)行任務(wù)
當(dāng)用戶玩手機(jī)的時候,就不執(zhí)行,不玩了就執(zhí)行。
setRequiresStorageNotLow (boolean storageNotLow)
指定此Job是否只在可用存儲空間太低時運(yùn)行,默認(rèn)為false。
講Api簡直是世界上最無聊的事情了。我們來點(diǎn)刺激的。
3. 起始
又回到我們的JobService。
對于開始,這個話題,在這里就從onStartJob說起吧,設(shè)置了schedule之后,系統(tǒng)進(jìn)入調(diào)度job狀態(tài),而這個調(diào)度跟手機(jī)電量,網(wǎng)絡(luò),balabala都有關(guān),也有可能因為國產(chǎn)手機(jī)的一鍵清理全部死翹翹,但無論如何,我們都要說下去。
假設(shè)現(xiàn)在某job被調(diào)度了,進(jìn)入了onStartJob()方法。
@Override
public boolean onStartJob(JobParameters jobParameters) {
//我是一個小任務(wù)
//我一會就執(zhí)行完了
//到return時已完成任務(wù)
return false;
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
//我是一個耗時任務(wù)
//半天才執(zhí)行完
//到return時還沒執(zhí)行完,需要手動結(jié)束
return true;
}
首先onStartJob和onStopJob都是在主線程執(zhí)行的,而onStartJob有這樣兩種情況,非耗時任務(wù)和耗時任務(wù),對于這兩種任務(wù),系統(tǒng)要求我們返回false和true兩種值。(對于自己的任務(wù)是否耗時開發(fā)者自己清楚,要是自欺欺人的話!哼哼!ANR來一發(fā)。)
首先我們要明白一點(diǎn),這也是Google官方所做的限制,當(dāng)我們的onStartJob返回值是false的時候,說明任務(wù)應(yīng)執(zhí)行完(在return之前),那就沒必要再去執(zhí)行onStopJob()方法, 因為我們完全可以在onStartJob最后面寫執(zhí)行完的邏輯,因此Google文檔上有這么一段話。

這意味著,如果你的onStartJob返回false,你的onStopJob不會執(zhí)行,千萬不要在onStopJob里面做無用功了。
但是如果你要返回true的話,意味著你的任務(wù)是一個耗時操作。
按照Service是在主線程執(zhí)行的規(guī)定,你需要在onStartJob里面寫一個異步操作,這里用AsynTask和Handler都行。
在任務(wù)執(zhí)行結(jié)束的時候需要手動調(diào)用相關(guān)方法來結(jié)束本次任務(wù),否則將會造成阻塞,讓系統(tǒng)誤以為你一直在運(yùn)行,還沒結(jié)束,導(dǎo)致后面的任務(wù)沒法執(zhí)行。
joinFinished就是需要我們手動執(zhí)行的方法,它有兩個參數(shù),第一個是JobParameters,第二個是wantsReschedule

首先第一個參數(shù)是從onStartJob方法傳遞的,這就要求我們要記錄這個參數(shù)值,第二個參數(shù)是bool類型,表示是否想要被重新調(diào)度,填true的話就會被重新加入調(diào)度隊列。
其實對于JobService需要知道的就是這三個比較重要的方法以及他們何時應(yīng)該被主動調(diào)用和被動調(diào)用。
四. Summary
對于JobScheduler和JobService其實是作為一個后臺任務(wù)來使用的,如果作為后臺保活的招數(shù)來使用不僅達(dá)不到省電的目的還更耗電。
其次對于國內(nèi)大廠定制的Rom,JobService有些時候也不是工作很正常,目前還沒找到一個合適的工具來測試這個API,唯一的adb命令也沒啥可參考的,(可能是我太弱了……),請有知道的大佬熱心分享一下。

