JobScheduler和JobService的詳細使用

一. Background

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

對于后臺網(wǎng)絡(luò)加載一汽,最初是一進行網(wǎng)絡(luò)加載就喚醒設(shè)備避消,加載就喚醒設(shè)備,這個電量浪費可想而知召夹,當電量低的時候岩喷,進行喚醒更不合適,因此在Android L后面加了此API监憎,限制后臺喚醒纱意,系統(tǒng)會在合適的時候一起調(diào)度去運行,然后就形成了下面的圖形鲸阔。

這點應該跟MIUI的對齊喚醒類似偷霉。

所以根據(jù)上面所說,我們也可以提前知道后面的Api里面為什么會提供一些低電量停止任務(wù)褐筛,充電時執(zhí)行任務(wù)的方法类少。

But!

看起來這么高大上的技術(shù)死讹,國內(nèi)的廠商卻用他來做甭鞯危活!T蘧妓忍!
實際上,在Android5上愧旦,JobScheduler的運行效果相當顯著世剖,在某測試機上測試,基本上可以按照時間周期性調(diào)度笤虫。 (當然國內(nèi)各大廠的系統(tǒng)還是太強了……)

但隨著系統(tǒng)的迭代旁瘫,在Android后面的版本上祖凫,系統(tǒng)底層所做的限制越來越多,包括周期執(zhí)行時間不得低于15分鐘酬凳,這就讓IM類應用很難受了惠况,還是要去另尋他法。

但無論如何宁仔,背硗溃活都是一個偽命題,就算是加入了系統(tǒng)白名單也不一定能存活或者重新喚醒翎苫,國內(nèi)某鵝廠的Tim所采用的Linux底層喚醒权埠,也免不了被一殺啥消息都收不到。

所以我們不談?wù)摫煎谍;钊帘危嗟氖怯懻揓obScheduler和JobService用在執(zhí)行后臺簡單任務(wù)上面。

二. Use

JobScheduler是需要協(xié)同JobService來一起使用的呐粘,在準備實現(xiàn)一個Schedule之前满俗,我們要實現(xiàn)一個JobService。

1. 擼起袖子實現(xiàn)的 JobService

首先查看一下文檔, JobService的繼承關(guān)系如下作岖。


JobService 是一個abstract class漫雷,繼承自Service,那么他當然也會擁有一些Service擁有的特性(使用特點)鳍咱,比如說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)了其中的方法,分別是對應job啟動的onStartJob()和job結(jié)束的onStopJob()方法嵌屎。
當然推正,不要忘記我們的API是在Android L里面加入的,所以加入@RequiresApi注解宝惰。

2. 記得注冊了喵~

剛才說過玩service一定不要忘了注冊植榕,所以在各個地方,Google都提醒了你 別忘記注冊D岫帷W鸩小炒瘸!

Android Studio里也有,但是本喵不會截warning的圖

 <service android:name=".DemoJobService"
              android:enabled="true"
            android:exported="true"
            android:permission="android.permission.BIND_JOB_SERVICE"
 />

這家伙與眾不同的一點就是需要配置一個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雕擂,仔細查看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();
            }
   });

好了戚绕,你可以運行一下看看效果了纹坐,雖然你什么都不可能看到,偶爾有幸可以看到你的log,為什么這么說舞丛,我們后面為你揭曉耘子。

三. Introduce

上面我們已經(jīng)跑了一個只有一個 button的破界面了,可是更強大的機制在后臺運行著呢球切。

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)源碼了的那個大佬了。

嫌棄我蒋伦?那我們簡單了解即可弓摘。(自導自演好歡樂)

  • jobId就是job的身份證,身份證號有啥特點就不用我說了吧痕届。
  • 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默認需要接受jobId和JobService所包裝的ComponentName璧针,構(gòu)造一個ComponentName對象需要兩個參數(shù),首先是包名渊啰,其次是要被安排上的JobService的名字探橱。

  • setPeriodic(xxx毫秒),重復執(zhí)行周期绘证,設(shè)置了這個屬性后隧膏,系統(tǒng)會每隔xxx毫秒來執(zhí)行一次,勤勤懇懇嚷那,不會說累胞枕,結(jié)合 setPersisted(true) 使用,媽媽再也不用擔心我關(guān)了機jobSevice就不重復執(zhí)行了餒魏宽。

注1. Android L的時候腐泻,時間可以設(shè)的很小,比如說10s執(zhí)行一次队询,系統(tǒng)也很認真的10s執(zhí)行一次派桩,但是隨著國內(nèi)各廠利用此Api進行保活手段越來越高明蚌斩,和Google越來越機靈铆惑,在后來的Android版本上,這個時間被限制到15分鐘以上了……尷尬送膳。

注2. setPersisted(true)鸭津,true為關(guān)機后再開機不影響任務(wù),false為關(guān)機后就死翹翹了肠缨。

  • setRequiredNetworkType() ,需要在什么網(wǎng)絡(luò)下執(zhí)行盏阶,這里我們選的是NETWORK_TYPE_ANY晒奕,代表任何網(wǎng)絡(luò)均可。

2. 高級api

翻譯文檔走起名斟!
誰會干那么無聊的事情呢脑慧,況且我English也不是very well啊,所以這里就撿幾個重點的說一下吧砰盐,有些東西闷袒,可能你這輩子有永不到哦,(你非要用那你就用……)

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也將在該截止時間前運行坤次,說白了就是一個系統(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ù)
當用戶玩手機的時候骚露,就不執(zhí)行蹬挤,不玩了就執(zhí)行。


setRequiresStorageNotLow (boolean storageNotLow)

指定此Job是否只在可用存儲空間太低時運行棘幸,默認為false焰扳。

講Api簡直是世界上最無聊的事情了。我們來點刺激的误续。

3. 起始

又回到我們的JobService吨悍。

對于開始,這個話題蹋嵌,在這里就從onStartJob說起吧育瓜,設(shè)置了schedule之后,系統(tǒng)進入調(diào)度job狀態(tài)栽烂,而這個調(diào)度跟手機電量躏仇,網(wǎng)絡(luò),balabala都有關(guān)腺办,也有可能因為國產(chǎn)手機的一鍵清理全部死翹翹焰手,但無論如何,我們都要說下去怀喉。

假設(shè)現(xiàn)在某job被調(diào)度了书妻,進入了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ā)。)

首先我們要明白一點犹褒,這也是Google官方所做的限制抵窒,當我們的onStartJob返回值是false的時候,說明任務(wù)應執(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)誤以為你一直在運行,還沒結(jié)束芥永,導致后面的任務(wù)沒法執(zhí)行篡殷。

joinFinished就是需要我們手動執(zhí)行的方法,它有兩個參數(shù)埋涧,第一個是JobParameters贴唇,第二個是wantsReschedule


首先第一個參數(shù)是從onStartJob方法傳遞的,這就要求我們要記錄這個參數(shù)值飞袋,第二個參數(shù)是bool類型,表示是否想要被重新調(diào)度链患,填true的話就會被重新加入調(diào)度隊列巧鸭。

其實對于JobService需要知道的就是這三個比較重要的方法以及他們何時應該被主動調(diào)用和被動調(diào)用。

四. Summary

對于JobScheduler和JobService其實是作為一個后臺任務(wù)來使用的麻捻,如果作為后臺备偃裕活的招數(shù)來使用不僅達不到省電的目的還更耗電呀袱。
其次對于國內(nèi)大廠定制的Rom,JobService有些時候也不是工作很正常郑叠,目前還沒找到一個合適的工具來測試這個API夜赵,唯一的adb命令也沒啥可參考的,(可能是我太弱了……)乡革,請有知道的大佬熱心分享一下寇僧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沸版,隨后出現(xiàn)的幾起案子嘁傀,更是在濱河造成了極大的恐慌,老刑警劉巖视粮,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件细办,死亡現(xiàn)場離奇詭異,居然都是意外死亡蕾殴,警方通過查閱死者的電腦和手機笑撞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钓觉,“玉大人茴肥,你說我怎么就攤上這事∫楣龋” “怎么了炉爆?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卧晓。 經(jīng)常有香客問我芬首,道長,這世上最難降的妖魔是什么逼裆? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任郁稍,我火速辦了婚禮,結(jié)果婚禮上胜宇,老公的妹妹穿的比我還像新娘耀怜。我一直安慰自己,他們只是感情好桐愉,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布财破。 她就那樣靜靜地躺著,像睡著了一般从诲。 火紅的嫁衣襯著肌膚如雪左痢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音俊性,去河邊找鬼略步。 笑死,一個胖子當著我的面吹牛定页,可吹牛的內(nèi)容都是我干的趟薄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼典徊,長吁一口氣:“原來是場噩夢啊……” “哼杭煎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宫峦,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤岔帽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后导绷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犀勒,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年妥曲,在試婚紗的時候發(fā)現(xiàn)自己被綠了贾费。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡檐盟,死狀恐怖褂萧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情葵萎,我是刑警寧澤导犹,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站羡忘,受9級特大地震影響谎痢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卷雕,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一节猿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漫雕,春花似錦滨嘱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至魁蒜,卻和暖如春囊扳,著一層夾襖步出監(jiān)牢的瞬間煤墙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工宪拥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铣减。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓她君,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葫哗。 傳聞我的和親對象是個殘疾皇子缔刹,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

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