Android Jobscheduler使用

Android Jobscheduler使用

Until android API 25


一、Jobscheduler誕生

Android 5.0系統(tǒng)以前,在處理一些特定情況下的任務(wù),或者是為了應(yīng)用的?;睿覀兺ǔJ?strong>使用了Service常駐后臺來滿足我們的需求。當(dāng)達(dá)到某個條件時觸發(fā)該Service來進(jìn)行相應(yīng)任務(wù)的處理。或者僅僅是為了我們自己的應(yīng)用不被系統(tǒng)回收銷毀。這樣做在滿足了自己應(yīng)用的需求的同時也消耗了部分硬件性能。對用戶的體驗上,和Android系統(tǒng)環(huán)境上都有不利的影響。而且在其它地方,大多數(shù)開發(fā)者都認(rèn)為在后臺永駐進(jìn)程,是獲取用戶的隱私,是不合法的。然而在我國也許是因為開發(fā)商的需求大,迫切想要達(dá)到自己的目標(biāo),使用永駐的進(jìn)程可以完成用戶行為分析和推送等其它后臺的業(yè)務(wù)。因此在開發(fā)的模式上采取了極端的個體主義思想。

Android 5.0系統(tǒng)以后,Google為了優(yōu)化Android系統(tǒng),提高使用流暢度以及延長電池續(xù)航,加入了在應(yīng)用后臺/鎖屏?xí)r,系統(tǒng)會回收應(yīng)用,同時自動銷毀應(yīng)用拉起的Service的機(jī)制。同時為了滿足在特定條件下需要執(zhí)行某些任務(wù)的需求,google在全新一代操作系統(tǒng)上,采取了Job (jobservice & JobInfo)的方式,即每個需要后臺的業(yè)務(wù)處理為一個job,通過系統(tǒng)管理job,來提高資源的利用率,從而提高性能,節(jié)省電源。這樣又能滿足APP開發(fā)商的要求,又能滿足系統(tǒng)性能的要求。Jobscheduler由此應(yīng)運而生。


二、Jobscheduler特性&適用

· 1 特性:

1、支持在一個任務(wù)上組合多個條件
2、內(nèi)置條件:設(shè)備待機(jī)、設(shè)備充電和連接網(wǎng)絡(luò)
3、支持持續(xù)的job,這意味著設(shè)備重啟后,之前被中斷的job可以繼續(xù)執(zhí)行
4、支持設(shè)置job的最后執(zhí)行期限
5、根據(jù)你的配置,可以設(shè)置job在后臺運行還是在主線程中運行

· 2 適用:

需要在Android設(shè)備滿足某種場合才需要去執(zhí)行處理數(shù)據(jù):
1、應(yīng)用具有可以推遲的非面向用戶的工作(定期數(shù)據(jù)庫數(shù)據(jù)更新)
2、應(yīng)用具有當(dāng)插入設(shè)備時希望優(yōu)先執(zhí)行的工作(充電時才希望執(zhí)行的工作備份數(shù)據(jù))
3、需要訪問網(wǎng)絡(luò)Wi-Fi 連接時需要進(jìn)行的任務(wù)(如向服務(wù)器拉取內(nèi)置數(shù)據(jù))
4、希望作為一個批次定期運行的許多任務(wù)(s)

· 3 特征:

1、Job Scheduler只有在Api21或以上的系統(tǒng)支持
2、Job Scheduler是將多個任務(wù)打包在一個場景下執(zhí)行。
3、在系統(tǒng)重啟以后,任務(wù)會依然保留在Job Scheduler當(dāng)中,因此不需要監(jiān)聽系統(tǒng)啟動狀態(tài)重復(fù)設(shè)定。
4、如果在一定期限內(nèi)還沒有滿足特定執(zhí)行所需情況,Job Scheduler會將這些任務(wù)加入隊列,并且隨后會進(jìn)行執(zhí)行。

使用Job Scheduler,應(yīng)用需要做的事情就是判斷哪些任務(wù)是不緊急的,可以交給Job Scheduler來處理,Job Scheduler集中處理收到的任務(wù),選擇合適的時間,合適的網(wǎng)絡(luò),再一起進(jìn)行執(zhí)行。把時效性不強(qiáng)的工作丟給它做。


三、Jobscheduler初識

通俗的來講,Jobscheduler也是通過Jobservice服務(wù)和JobInfo搭配來完成我們特定情況下需要完成的各種工作的一個新組件。和傳統(tǒng)的service相比較,(從上邊也可以了解到)它具有更優(yōu)秀的表現(xiàn),不僅對系統(tǒng)環(huán)境的友好,同時在特定的預(yù)置條件下進(jìn)行我們期望的工作也節(jié)省了手機(jī)的電量,節(jié)約了系統(tǒng)資源。下邊先了解下Jobservice和JobInfo。之后在完成搭配使用的用例以及應(yīng)用衍生。

Jobscheduler的使用流程大概分為以下四個部分:

  • 派生JobService 子類,定義需要執(zhí)行的任務(wù)(UI線程)
  • 從Context 中獲取JobScheduler 實例(相當(dāng)于管理器)
  • 構(gòu)建JobInfo 實例,指定 JobService任務(wù)實現(xiàn)類及其執(zhí)行條件
  • 通過JobScheduler 實例加入到任務(wù)隊列

· 1 自定義Jobservice

java.lang.Object ? android.content.Context ? android.content.ContextWrapper ? android.app.Service ? android.app.job.JobService

JobService是從Service中擴(kuò)展出來的一個新類,繼承Service。但是有兩個重要的新接口。

下邊使用用例代碼來講解:

public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示該工作耗時,同時工作處理完成后需要調(diào)用onStopJob銷毀(jobFinished)
        // 返回false,任務(wù)運行不需要很長時間,到return時已完成任務(wù)處理
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // 有且僅有onStartJob返回值為true時,才會調(diào)用onStopJob來銷毀job
        // 返回false來銷毀這個工作
        return false;
    }
}  

1、OnStartJob(JobParameters params)

當(dāng)開始一個任務(wù)時,onstartjob(jobparameters params) 是必須使用的方法,因為它是系統(tǒng)用來觸發(fā)已經(jīng)安排的工作(job)的。
從上邊的用例代碼可以看到,該方法返回一個布爾值。不同的返回值對應(yīng)了不同的處理方式。

  • 如果返回值是false,該系統(tǒng)假定任何任務(wù)運行不需要很長時間并且到方法返回時已經(jīng)完成。
  • 如果返回值是true,那么系統(tǒng)假設(shè)任務(wù)是需要一些時間并且是需要在我們自己應(yīng)用執(zhí)行的。
    當(dāng)給定的任務(wù)完成時需要通過調(diào)用
    jobFinished(JobParameters params, boolean needsRescheduled)告知系統(tǒng),該任務(wù)已經(jīng)處理完成。
    如果返回值為true,我們需要手動調(diào)用jobFinished來停止該任務(wù)

2、JobFinished

void jobFinished (JobParameters params, boolean needsReschedule)

Callback to inform the JobManager you've finished executing. This can be called from any thread, as it will ultimately be run on your application's main thread. When the system receives this message it will release the wakelock being held.
回調(diào)通知已完成執(zhí)行的JobManager。這可以從任何線程調(diào)用,因為它最終將在應(yīng)用程序的主線程上運行。當(dāng)系統(tǒng)收到該消息時,它將釋放正在保存的喚醒。

  • JobParameters params
    -- onStartJob(JobParameters).
    傳入的param需要和onStartJob中的param一致

  • boolean needsReschedule
    -- True if this job should be rescheduled according to the back-off criteria specified at schedule-time. False otherwise.
    如果這項工作應(yīng)按照計劃時間指定的停止條件進(jìn)行重新安排,則傳入true。 否則的話傳入false。
    說人話就是讓系統(tǒng)知道這個任務(wù)是否應(yīng)該在最初的條件下被重復(fù)執(zhí)行(稍后會介紹這個布爾值的用處)

3、OnStopJob(JobParameters params)

當(dāng)收到取消請求時,onStopJob(JobParameters params)是系統(tǒng)用來取消掛起的任務(wù)的。
重要的是要注意到,如果onStartJob(JobParameters params)返回 false,當(dāng)取消請求被接收時,該系統(tǒng)假定沒有目前運行的工作。換句話說,它根本就不調(diào)用onStopJob(JobParameters params)。那此時就需要我們手動調(diào)用jobFinished (JobParameters params, boolean needsReschedule)方法了。

要注意的是,工作服務(wù)需要在應(yīng)用程序的主線程上運行。這意味著,須使用另一個線程處理程序,或運行時間更長的任務(wù)異步任務(wù)用以不阻塞主線程。

簡單的來講,我們可以在上面JobSchedulerService類中創(chuàng)建一個Handler或者AsyncTask來處理需要進(jìn)行的Job。

public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示該工作耗時,同時工作處理完成后需要調(diào)用jobFinished銷毀
        mJobHandler.sendMessage(Message.obtain(mJobHandler, 1, params));
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        mJobHandler.removeMessages(1);
        return false;
    }
    
    // 創(chuàng)建一個handler來處理對應(yīng)的job
    private Handler mJobHandler = new Handler(new Handler.Callback() {
        // 在Handler中,需要實現(xiàn)handleMessage(Message msg)方法來處理任務(wù)邏輯。
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(getApplicationContext(), "JobService task running", Toast.LENGTH_SHORT).show();
            // 調(diào)用jobFinished
            jobFinished((JobParameters) msg.obj, false);
            return true;
        }
    });
} 

當(dāng)然一個異步任務(wù)也是可行的:

public class JobSchedulerService extends JobService {

    private JobParameters mJobParameters

    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示該工作耗時,同時工作處理完成后需要調(diào)用jobFinished銷毀
        mJobParameters = params;
        mTask.execute();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    private AsyncTask<Void, Void, Void> mTask = new AsyncTask<Void, Void, Void>() {

        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            Toast.makeText(wenfengService.this, "finish job", 1000).show();
            jobFinished(mJobParameters, true);
            super.onPostExecute(result);
        }
    } 
}

當(dāng)任務(wù)完成時,需要調(diào)用jobFinished(JobParameters params, boolean needsRescheduled)讓系統(tǒng)知道完成了哪項任務(wù),它可以開始排隊接下來的操作。如果不這樣做,工作將只運行一次,應(yīng)用程序?qū)⒉槐辉试S執(zhí)行額外的工作。

代碼片段中,因為handler中的操作可能比onStartJob(JobParameters params)方法它可能需要更長的時間來完成。通過設(shè)置true返回值,讓程序了解將手動調(diào)用jobFinished(JobParameters params, boolean needsRescheduled)方法標(biāo)記完成任務(wù)。

同時從上邊的用例也可發(fā)現(xiàn)jobFinished(JobParameters params, boolean needsRescheduled)布爾值是false,它讓系統(tǒng)知道是否需要根據(jù)工作的最初要求重新編排工作(重復(fù)執(zhí)行)。同時這個布爾值是非常有用,可以幫助我們解決如何處理由于其他問題(如一個失敗的網(wǎng)絡(luò)電話)而導(dǎo)致任務(wù)無法完成的情況。設(shè)置為true我們就可以重復(fù)(?參見下面的描述)的進(jìn)行這個任務(wù)。

任務(wù)失敗的情況有很多,例如下載失敗了,例如下載過程wifi斷掉了。
例如如果下載過程中,wifi斷掉了,JobService會回調(diào)onStopJob函數(shù),這是只需要把函數(shù)的返回值設(shè)置為true就可以了。當(dāng)wifi重新連接后,JobService會重新回調(diào)onStartJob函數(shù)。
而如果下載失敗了,例如上面的例子中的mJobHandler執(zhí)行失敗,怎么辦呢?我們只需要在Handler的handleMessage中執(zhí)行jobFinished(mJobParameters, true),這里的true代表任務(wù)要在wifi條件重新滿足情況下重新調(diào)度。

4、 綁定服務(wù):

在簡單的完成以上發(fā)送toast的Java代碼之后,同Service一樣,需要在AndroidManifest.xml中添加一個Service節(jié)點讓應(yīng)用擁有綁定和使用這個JobService的權(quán)限。

<service android:name="pkgName.JobSchedulerService"
    android:permission="android.permission.BIND_JOB_SERVICE" />

· 2 創(chuàng)建JobScheduler對象

在完成JobSchedulerService的構(gòu)建以及綁定Service節(jié)點之后,接下來進(jìn)行的是如何與JobScheduler API交互。

1、創(chuàng)建一個JobScheduler

在Activity中我們通過getSystemService(Context.JOB_SCHEDULER_SERVICE)實例化一個mJobScheduler的JobScheduler對象。

JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

創(chuàng)建定時任務(wù)時,可以使用JobInfo.Builder來構(gòu)建一個JobInfo對象,然后傳遞給JobService。

JobInfo.Builder builder = new JobInfo.Builder(jobId, 
new ComponentName(getPackageName(), JobSchedulerService.class.getName()));

JobInfo

Constants

  • BACKOFF_POLICY_EXPONENTIAL 、BACKOFF_POLICY_LINEAR
    與setBackoffCriteria (long initialBackoffMillis, int backoffPolicy)中backoffPolicy對應(yīng)

  • DEFAULT_INITIAL_BACKOFF_MILLIS
    默認(rèn)的執(zhí)行延遲時間

  • MAX_BACKOFF_DELAY_MILLIS
    最大的執(zhí)行延遲時間

  • NETWORK_TYPE_ANY
    網(wǎng)絡(luò)狀態(tài)聯(lián)網(wǎng)就行setRequiredNetworkType

  • NETWORK_TYPE_NONE
    默認(rèn)的網(wǎng)絡(luò)連接狀態(tài)setRequiredNetworkType

  • NETWORK_TYPE_NOT_ROAMING
    移動網(wǎng)絡(luò)連接情況setRequiredNetworkType

  • NETWORK_TYPE_UNMETERED
    WIFI連接情況setRequiredNetworkType

▼▼▼▼重點來了!?。〃嫧嫧嫧?/strong>

JobInfo.Builder

JobInfo.Builder(int jobId, ComponentName jobService)
Initialize a new Builder to construct a JobInfo.

JobInfo.Builder接收兩個參數(shù)

  • jobId : 要運行的任務(wù)的標(biāo)識符
  • jobService : Service組件的類名。

下面簡要的介紹部分builder中的方法:(截止Android API 25)

  • addTriggerContentUri(JobInfo.TriggerContentUri uri):添加一個TriggerContentUri,該Uri將利用ContentObserver來監(jiān)控一個Content Uri,當(dāng)且僅當(dāng)其發(fā)生變化時將觸發(fā)任務(wù)的執(zhí)行。為了持續(xù)監(jiān)控content的變化,你需要在最近的任務(wù)觸發(fā)后再調(diào)度一個新的任務(wù)(需要注意的是觸發(fā)器URI不能與setPeriodic(long)setPersisted(boolean)組合使用。要持續(xù)監(jiān)控內(nèi)容更改,需要在完成JobService處理最近的更改之前,調(diào)度新的JobInfo,觀察相同的URI。因為設(shè)置此屬性與定期或持久化Job不兼容,這樣做會在調(diào)用build()時拋出IllegalArgumentException異常。)

  • setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)特殊:設(shè)置回退/重試的策略,詳細(xì)的可以參閱Google API。
    類似網(wǎng)絡(luò)原理中的沖突退避,當(dāng)一個任務(wù)的調(diào)度失敗時需要重試,所采取的策略。第一個參數(shù)時第一次嘗試重試的等待間隔,單位為毫秒,預(yù)設(shè)的參數(shù)有:DEFAULT_INITIAL_BACKOFF_MILLIS 30000MAX_BACKOFF_DELAY_MILLIS 18000000。第二個參數(shù)是對應(yīng)的退避策略,預(yù)設(shè)的參數(shù)有:BACKOFF_POLICY_EXPONENTIAL 二進(jìn)制退避。等待間隔呈指數(shù)增長 BACKOFF_POLICY_LINEAR

  • setExtras(PersistableBundle extras):設(shè)置可選附件。這是持久的,所以只允許原始類型。

  • setMinimumLatency(long minLatencyMillis): 這個函數(shù)能用以設(shè)置任務(wù)的延遲執(zhí)行時間(毫秒),相當(dāng)于post delay。

  • setOverrideDeadline(long maxExecutionDelayMillis): 這個方法讓用以設(shè)置任務(wù)最晚的延遲時間
    。如果到了規(guī)定的時間時其他條件還未滿足,任務(wù)也會被啟動。

  • setPeriodic(long time):設(shè)置任務(wù)運行的周期(每X毫秒,運行一次)。衍生在運行一些權(quán)限檢查的時候如果可以使用job scheduler的話,可以這樣來循環(huán)檢查權(quán)限的開啟,但是目前的觸發(fā)條件沒有權(quán)限部分的觸發(fā)。

  • setPeriodic(long intervalMillis, long flexMillis):設(shè)置在Job周期末的一個flex長度的窗口,任務(wù)都有可能被執(zhí)行 require API LEVEL 24

  • setPersisted(boolean isPersisted): 這個方法告訴系統(tǒng)當(dāng)設(shè)備重啟之后任務(wù)是否還要繼續(xù)執(zhí)行

  • setRequiredNetworkType(int networkType): 這個方法讓這個任務(wù)只有在滿足指定的網(wǎng)絡(luò)條件時才會被執(zhí)行。默認(rèn)條件是JobInfo.NETWORK_TYPE_NONE,這意味著不管是否有網(wǎng)絡(luò)這個任務(wù)都會被執(zhí)行。另外兩個可選類型,一種是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一種網(wǎng)絡(luò)才使得任務(wù)可以執(zhí)行。另一種是JobInfo.NETWORK_TYPE_UNMETERED,它表示設(shè)備不是蜂窩網(wǎng)絡(luò)( 比如在WIFI連接時 )時任務(wù)才會被執(zhí)行。

  • setRequiresCharging(boolean requiresCharging): 只有當(dāng)設(shè)備在充電時這個任務(wù)才會被執(zhí)行。這個也并非只是插入充電器,而且還要在電池處于健康狀態(tài)的情況下才會觸發(fā),一般來說是手機(jī)電量>15%

  • setRequiresDeviceIdle(boolean requiresDeviceIdle):指定Job在空閑狀態(tài)才能運行。設(shè)備處于屏幕關(guān)閉或dreaming狀態(tài)(類似window的休眠動畫狀態(tài))71分鐘后,執(zhí)行工作

  • setTransientExtras(Bundle extras):設(shè)置可選的臨時附加功能。(Android O Developer Preview)這里指定了需要Android O 系統(tǒng),可以看出,Google還是在繼續(xù)完善job scheduler的,期待能夠代替?zhèn)鹘y(tǒng)的service組件成為主流。

  • setTriggerContentMaxDelay(long durationMs):設(shè)置從第一次檢測到內(nèi)容更改到Job之前允許的最大總延遲(以毫秒為單位)。說人話就是設(shè)置從content變化到任務(wù)被執(zhí)行,中間的最大延遲。 同樣的require API 24

  • setTriggerContentUpdateDelay(long durationMs):設(shè)置從content變化到任務(wù)被執(zhí)行中間的延遲。如果在延遲期間content發(fā)生了變化,延遲會重新計算

  • setExtras(PersistableBundle extra):Bundle

要注意的是:
1、
設(shè)置延遲時間 setMinimumLatency(long minLatencyMillis)設(shè)置最終期限時間 setOverrideDeadline(long maxExecutionDelayMillis)的兩個方法不能同時與setPeriodic(long time)同時設(shè)置,也就是說,在設(shè)置延遲和最終期限時間時是不能設(shè)置重復(fù)周期時間的。還有在具體開發(fā)過程中需要注意各個方法的API兼容情況。

2、setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging)setRequiresDeviceIdle(boolean requireIdle)這幾個方法可能會使得任務(wù)無法執(zhí)行,除非調(diào)用setOverrideDeadline(long time)設(shè)置了最大延遲時間,使得任務(wù)在為滿足條件的情況下也會被執(zhí)行。

構(gòu)建一個JobInfo對象設(shè)置預(yù)置的條件,然后通過如下所示的代碼將它發(fā)送到的JobScheduler中。

開啟一個JobScheduler任務(wù):

mJobScheduler.schedule(JobInfo job)

在schedule時,會返回一個int類型的值來標(biāo)記這次任務(wù)是否執(zhí)行成功,如果返回小于0的錯誤碼,這表示該次任務(wù)執(zhí)行失敗,反之則成功(成功會返回該任務(wù)的id,這里可以使用這個id來判斷哪些任務(wù)成功了)。所以在返回值小于0的時候就需要我們手動去處理一些事情了。

if(mJobScheduler.schedule(JobInfo job) < 0){
    // do something when schedule goes wrong
}

最后如果需要停止一個任務(wù),就通過JobScheduler中,cancel(int jobId)來實現(xiàn)(所以之前在Builer中的指定id又有了重要作用);如果想取消所有的任務(wù),可以調(diào)用JobScheduler對象的cancelAll()來實現(xiàn)。


四、Jobscheduler使用

在上邊初略的講解了job scheduler的一些使用方法,下邊通過一個用例來加深一下理解。

創(chuàng)建我們Job依附的Activity:

public class SchedulerAcitvity extends Activity {  

    private static final String TAG = "SchedulerAcitvity";  

    public static final String MESSENGER_INTENT_KEY = TAG + ".MESSENGER_INTENT_KEY";
    public static final String WORK_DURATION_KEY = TAG + ".WORK_DURATION_KEY";
    public static final int MSG_JOB_START = 0;
    public static final int MSG_JOB_STOP = 1;
    public static final int MSG_ONJOB_START = 2;
    public static final int MSG_ONJOB_STOP = 3;

    private int mJobId = 0;// 執(zhí)行的JobId

    ComponentName mServieComponent;// 這就是我們的jobservice組件了
    private IncomingMessageHandler mHandler;// 用于來自服務(wù)的傳入消息的處理程序。
    
    // UI
    private EditText mEt_Delay;// 設(shè)置delay時間
    private EditText mEt_Deadline;// 設(shè)置最長的截止時間
    private EditText mEt_DurationTime;// setPeriodic周期
    private RadioButton mRb_WiFiConnectivity;// 設(shè)置builder中的是否有WiFi連接
    private RadioButton mRb_AnyConnectivity;// 設(shè)置builder中的是否有網(wǎng)絡(luò)即可
    private CheckBox mCb_RequiresCharging;// 設(shè)置builder中的是否需要充電
    private CheckBox mCb_RequiresIdle;// 設(shè)置builder中的是否設(shè)備空閑
    private Button mBtn_StartJob;// 點擊開始任務(wù)的按鈕
    private Button mBtn_StopAllJob;// 點擊結(jié)束所有任務(wù)的按鈕
    
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.durian_main);

        mHandler = new IncomingMessageHandler(this);
        mServieComponent = new ComponentName(this, MyJobService.class);// 獲取到我們自己的jobservice,同時啟動該service

        // 設(shè)置UI
        mEt_Delay = (EditText) findViewById(R.id.delay_time);
        mEt_DurationTime = (EditText) findViewById(R.id.duration_time);
        mEt_Deadline = (EditText) findViewById(R.id.deadline_time);
        mRb_WiFiConnectivity = (RadioButton) findViewById(R.id.checkbox_unmetered);
        mRb_AnyConnectivity = (RadioButton) findViewById(R.id.checkbox_any);
        mCb_RequiresCharging = (CheckBox) findViewById(R.id.checkbox_charging);
        mCb_RequiresIdle = (CheckBox) findViewById(R.id.checkbox_idle);
        mBtn_StartJob = (Button)findViewById(R.id.button_start_job);
        mBtn_StopAllJob = (Button)findViewById(R.id.button_start_job);

        mBtn_StartJob.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                scheduleJob();
                }  
        }); 

        mBtn_StopAllJob.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                cancelAllJobs();
                }  
        });
    }
    
    @Override
    protected void onStart() {
        super.onStart();
        // 啟動服務(wù)并提供一種與此類通信的方法。
        Intent startServiceIntent = new Intent(this, MyJobService.class);
        Messenger messengerIncoming = new Messenger(mHandler);
        startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming);
        startService(startServiceIntent);
    }
    
    @Override
    protected void onStop() {
        // 服務(wù)可以是“開始”和/或“綁定”。 在這種情況下,它由此Activity“啟動”
        // 和“綁定”到JobScheduler(也被JobScheduler稱為“Scheduled”)。
        // 對stopService()的調(diào)用不會阻止處理預(yù)定作業(yè)。
        // 然而,調(diào)用stopService()失敗將使它一直存活。
        stopService(new Intent(this, MyJobService.class));
        super.onStop();
    }
    
    // 當(dāng)用戶單擊SCHEDULE JOB時執(zhí)行。
    public void scheduleJob() {
        //開始配置JobInfo
        JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);

        //設(shè)置任務(wù)的延遲執(zhí)行時間(單位是毫秒)
        String delay = mEt_Delay.getText().toString();
        if (!TextUtils.isEmpty(delay)) {
            builder.setMinimumLatency(Long.valueOf(delay) * 1000);
        }
        //設(shè)置任務(wù)最晚的延遲時間。如果到了規(guī)定的時間時其他條件還未滿足,你的任務(wù)也會被啟動。
        String deadline = mEt_Deadline.getText().toString();
        if (!TextUtils.isEmpty(deadline)) {
            builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
        }
        boolean requiresUnmetered = mRb_WiFiConnectivity.isChecked();
        boolean requiresAnyConnectivity = mRb_AnyConnectivity.isChecked();

        //讓你這個任務(wù)只有在滿足指定的網(wǎng)絡(luò)條件時才會被執(zhí)行
        if (requiresUnmetered) {
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
        } else if (requiresAnyConnectivity) {
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        }

        //你的任務(wù)只有當(dāng)用戶沒有在使用該設(shè)備且有一段時間沒有使用時才會啟動該任務(wù)。
        builder.setRequiresDeviceIdle(mCb_RequiresIdle.isChecked());
        //告訴你的應(yīng)用,只有當(dāng)設(shè)備在充電時這個任務(wù)才會被執(zhí)行。
        builder.setRequiresCharging(mCb_RequiresCharging.isChecked());

        // Extras, work duration.
        PersistableBundle extras = new PersistableBundle();
        String workDuration = mEt_DurationTime.getText().toString();
        if (TextUtils.isEmpty(workDuration)) {
            workDuration = "1";
        }
        extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000);

        builder.setExtras(extras);

        // Schedule job
        Log.d(TAG, "Scheduling job");
        JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        // 這里就將開始在service里邊處理我們配置好的job
        mJobScheduler.schedule(builder.build());

        //mJobScheduler.schedule(builder.build())會返回一個int類型的數(shù)據(jù)
        //如果schedule方法失敗了,它會返回一個小于0的錯誤碼。否則它會返回我們在JobInfo.Builder中定義的標(biāo)識id。
    }

    // 當(dāng)用戶點擊取消所有時執(zhí)行
    public void cancelAllJobs() {
        JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        mJobScheduler.cancelAll();
        Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show();
    }

    /**
    * {@link Handler}允許您發(fā)送與線程相關(guān)聯(lián)的消息。
    * {@link Messenger}使用此處理程序從{@link MyJobService}進(jìn)行通信。
    * 它也用于使開始和停止視圖在短時間內(nèi)閃爍。
    */
    private static class IncomingMessageHandler extends Handler {

        // 使用弱引用防止內(nèi)存泄露
        private WeakReference<SchedulerAcitvity> mActivity;

        IncomingMessageHandler(SchedulerAcitvity activity) {
            super(/* default looper */);
            this.mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SchedulerAcitvity mSchedulerAcitvity = mActivity.get();
            if (mSchedulerAcitvity == null) {
                // 活動不再可用,退出。
                return;
            }
            
            // 獲取到兩個View,用于之后根據(jù)Job運行狀態(tài)顯示不同的運行狀態(tài)(顏色變化)
            View showStartView = mSchedulerAcitvity.findViewById(R.id.onstart_textview);
            View showStopView = mSchedulerAcitvity.findViewById(R.id.onstop_textview);

            Message m;
            switch (msg.what) {
                 // 當(dāng)作業(yè)登錄到應(yīng)用程序時,從服務(wù)接收回調(diào)。 打開指示燈(上方View閃爍)并發(fā)送一條消息,在一秒鐘后將其關(guān)閉。
                 case MSG_JOB_START:
                    // Start received, turn on the indicator and show text.
                    // 開始接收,打開指示燈(上方View閃爍)并顯示文字。
                    showStartView.setBackgroundColor(getColor(R.color.start_received));
                    updateParamsTextView(msg.obj, "started");

                    // Send message to turn it off after a second.
                    // 發(fā)送消息,一秒鐘后關(guān)閉它。
                    m = Message.obtain(this, MSG_ONJOB_START);
                    sendMessageDelayed(m, 1000L);
                    break;

                // 當(dāng)先前執(zhí)行在應(yīng)用程序中的作業(yè)必須停止執(zhí)行時,
                // 從服務(wù)接收回調(diào)。 打開指示燈并發(fā)送一條消息,
                // 在兩秒鐘后將其關(guān)閉。
                case MSG_JOB_STOP:
                    // Stop received, turn on the indicator and show text.
                    // 停止接收,打開指示燈并顯示文本。
                    showStopView.setBackgroundColor(getColor(R.color.stop_received));
                    updateParamsTextView(msg.obj, "stopped");

                    // Send message to turn it off after a second.
                    // 發(fā)送消息,一秒鐘后關(guān)閉它。
                    m = obtainMessage(MSG_ONJOB_STOP);
                    sendMessageDelayed(m, 2000L);
                    break;
                case MSG_ONJOB_START:
                    showStartView.setBackgroundColor(getColor(R.color.none_received));
                    updateParamsTextView(null, "job had started");
                    break;
                case MSG_ONJOB_STOP:
                    showStopView.setBackgroundColor(getColor(R.color.none_received));
                    updateParamsTextView(null, "job had stoped");
                    break;
            }
        } 

        // 更新UI顯示
        // @param jobId jobId
        // @param action 消息
        private void updateParamsTextView(@Nullable Object jobId, String action) {
            TextView paramsTextView = (TextView) mActivity.get().findViewById(R.id.task_params);
            if (jobId == null) {
                paramsTextView.setText("");
                return;
            }
            String jobIdText = String.valueOf(jobId);
            paramsTextView.setText(String.format("Job ID %s %s", jobIdText, action));
        }

        private int getColor(@ColorRes int color) {
            return mActivity.get().getResources().getColor(color);
        }
    }
}  

在activity中我們使用一個按鈕來開啟JobScheduler,同時簡單的配置了相應(yīng)Builder的配置。在點擊button的時候,此項scheduler就開始schedule(我們配置的jobInfo)。那當(dāng)我們的條件匹配我們配置的JobInfo的時候會開始怎樣的處理呢?這里就需要我們在service里邊做作業(yè)了。

滿足配置條件時候啟動任務(wù)的Service:

public class MyJobService extends JobService {

    private static final String TAG = MyJobService.class.getSimpleName();

    private Messenger mActivityMessenger;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Service created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Service destroyed");
    }

    // 當(dāng)應(yīng)用程序的MainActivity被創(chuàng)建時,它啟動這個服務(wù)。
    // 這是為了使活動和此服務(wù)可以來回通信。 請參見“setUiCallback()”
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY);
        return START_NOT_STICKY;
    }

    @Override
    public boolean onStartJob(final JobParameters params) {
        // The work that this service "does" is simply wait for a certain duration and finish
        // the job (on another thread).

        // 該服務(wù)做的工作只是等待一定的持續(xù)時間并完成作業(yè)(在另一個線程上)。
        sendMessage(MSG_JOB_START, params.getJobId());
        // 當(dāng)然這里可以處理其他的一些任務(wù)
        // TODO something else
        
        // 獲取在activity里邊設(shè)置的每個任務(wù)的周期,其實可以使用setPeriodic()
        long duration = params.getExtras().getLong(WORK_DURATION_KEY);

        // 使用一個handler處理程序來延遲jobFinished()的執(zhí)行。
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                sendMessage(MSG_JOB_STOP, params.getJobId());
                jobFinished(params, false);
            }
        }, duration);
        Log.i(TAG, "on start job: " + params.getJobId());

        // 返回true,很多工作都會執(zhí)行這個地方,我們手動結(jié)束這個任務(wù)
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // 停止跟蹤這些作業(yè)參數(shù),因為我們已經(jīng)完成工作。
        sendMessage(MSG_JOB_STOP, params.getJobId());
        Log.i(TAG, "on stop job: " + params.getJobId());

        // 返回false來銷毀這個工作
        return false;
    }

    private void sendMessage(int messageID, @Nullable Object params) {
        // 如果此服務(wù)由JobScheduler啟動,則沒有回調(diào)Messenger。
        // 它僅在MainActivity在Intent中使用回調(diào)函數(shù)調(diào)用startService()時存在。
        if (mActivityMessenger == null) {
            Log.d(TAG, "Service is bound, not started. There's no callback to send a message to.");
            return;
        }

        Message m = Message.obtain();
        m.what = messageID;
        m.obj = params;
        try {
            mActivityMessenger.send(m);
        } catch (RemoteException e) {
            Log.e(TAG, "Error passing service object back to activity.");
        }
    }
}

雖然在service里邊只是簡單地進(jìn)行了一個我們設(shè)置的耗時操作,但是通過以上的例子應(yīng)該很容易理解JobScheduler的使用了。在某些條件下(充電,網(wǎng)絡(luò)連接【可以指定特定的狀態(tài)】,設(shè)備空閑)JobScheduler可以更優(yōu)秀的完成我們的觸發(fā)型任務(wù),雖然目前來講匹配的條件很是很少,但使用JobScheduler可以更優(yōu)雅的處理這些觸發(fā)事件,也是值得使用的。同時隨著Android的發(fā)展,我相信JobScheduler的應(yīng)用場景會越來越多。


五、衍生:

似乎都很熱衷于應(yīng)用的?;?,很多地方都是將jobScheduler應(yīng)用于Service殺不死,進(jìn)一步拉應(yīng)用的狀態(tài)
使用JobScheduler進(jìn)行開機(jī)自啟動
Android服務(wù)?;?JobScheduler拉活
Android進(jìn)程?;畹囊话闾茁?/a>

JobScheduler省電:
Android L 的 JobScheduler API 是怎么讓設(shè)備省電的

除了JobScheduler ,還有其他一些類似的APIs去幫助安排你的工作計劃,它們包括:
AlarmManager
Firebase JobDispatcher
GCM NETwork Manager
SyncAdapter
Additional Facilities

坑:

自啟動問題

按照官方文檔的定義,在原生的Android系統(tǒng)上,當(dāng)設(shè)定了一個Job之后,哪怕該App的進(jìn)程已經(jīng)結(jié)束或者被殺掉,對應(yīng)的JobService也是可以啟動的。
然而Android已經(jīng)被國內(nèi)的各大廠商重新定制過,導(dǎo)致的一個問題就是當(dāng)前App的進(jìn)程被殺掉之后,JobService無法啟動。
例如在MIUI系統(tǒng)中,第三方App如果沒有被用戶設(shè)置到允許自啟動的名單中,在啟動Service的時候會被攔截掉。
這恐怕會導(dǎo)致很多第三方應(yīng)用無法使用這個東西,正如之前的AlarmManager一樣。
還有一個比較坑的地方在于,當(dāng)Job正在執(zhí)行時,如果使用類似MIUI上的一鍵清除所有進(jìn)程,JobService會被強(qiáng)制停止,也不會執(zhí)行對應(yīng)的onStopJob方法。

兼容性問題
JobScheduler支持Android 5.0以上的系統(tǒng),但對于運行Android 5.0以下系統(tǒng)的手機(jī)是否有辦法呢?

Github上有一個開源的項目也許能幫得上忙:項目地址
這個項目在介紹中也宣稱不在進(jìn)行維護(hù)了,所以,僅供參考吧。

其他一些文章

Android省電的秘密之JobScheduler
Android省電的秘密(2)之a(chǎn)db解讀JobScheduler

本文整合部分網(wǎng)上相關(guān)文章,感謝所有文章的作者。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容