JobScheduler的使用和原理

1潮剪、JobScheduler的使用

1.1 簡介

JobScheduler主要用于在未來某個時間下滿足一定條件時觸發(fā)執(zhí)行某項任務的情況涣楷,涉及的條件可以是網絡、電量抗碰、時間等狮斗,例如執(zhí)行特定的網絡、是否只在充電時執(zhí)行任務等弧蝇。

1.2 相關API

1.2.1 JobScheduler

JobScheduler類負責將應用需要執(zhí)行的任務發(fā)送給框架碳褒,以備對該應用Job的調度,是一個系統(tǒng)服務看疗,可以通過如下方式獲壬尘:

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

1.2.2 JobInfo 及 JobInfo.Builder

JobInfo是傳遞給JobScheduler類的數據容器,它封裝了針對調用應用程序調度任務所需的各種約束两芳,也可以認為一個JobInfo對象對應一個任務摔寨,JobInfo對象通過JobInfo.Builder創(chuàng)建。它將作為參數傳遞給JobScheduler:

mJobScheduler.scheduler(mJobInfo);

JobInfo.Builder是JobInfo的一個內部類怖辆,用來創(chuàng)建JobInfo的Builder類是复。

JobInfo.Builder mBuilder = new JobInfo.Builder(id,new ComponentName(this, MyJobService.class));
mJobInfo = mBuilder.build();

1.2.3 JobService

JobService是JobScheduler最終回調的端點,JobScheduler將會回調該類中的onStartJob()開始執(zhí)行異步任務疗隶。它是一個繼承于JobService的抽象類佑笋,做為系統(tǒng)回調執(zhí)行任務內容的終端,JobScheduler框架將通過bindService()方式來啟動該服務斑鼻。因此蒋纬,用戶必須在應用程序中創(chuàng)建一個JobService的子類,并實現(xiàn)其onStartJob()等回調方法坚弱,以及在AndroidManifest.xml中對它授予如下權限:

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

1.3 使用流程

1.3.1 創(chuàng)建一個JobService的子類蜀备,作為系統(tǒng)回調終端:

public class MyJobService extends JobService {

    @Override
    public boolean onStartJob(final JobParameters params) {
        //todo 執(zhí)行任務
        return true;
    }
    
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;//返回false表示停止后不再重試執(zhí)行
    }
}

注意在AndroidManifest.xml中添加權限

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

當任務開始時會執(zhí)行onStartJob(JobParameters params)方法,如果返回值是false荒叶,則系統(tǒng)認為這個方法返回時碾阁,任務已經執(zhí)行完畢。如果返回值是true些楣,那么系統(tǒng)認為這個任務正要被執(zhí)行脂凶,執(zhí)行任務的重擔就落在了你的肩上。當任務執(zhí)行完畢時你需要調用jobFinished(JobParameters params, boolean needsRescheduled)來通知系統(tǒng)愁茁。

當系統(tǒng)接收到一個取消請求時蚕钦,系統(tǒng)會調用onStopJob(JobParameters params)方法取消正在等待執(zhí)行的任務。很重要的一點是如果onStartJob(JobParameters params)返回false鹅很,那么系統(tǒng)假定在接收到一個取消請求時已經沒有正在運行的任務嘶居。換句話說,onStopJob(JobParameters params)在這種情況下不會被調用促煮。

需要注意的是這個Job Service運行在主線程邮屁,這意味著你需要使用子線程但壮,handler兴喂,或者一個異步任務來運行耗時的操作以防止阻塞主線程。

1.3.2 創(chuàng)建JobInfo.Builder對象苦始,為Job設置約束條件

private ComponentName mServiceComponent;
//根據JobService創(chuàng)建一個ComponentName對象
mServiceComponent = new ComponentName(this, MyJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
builder.setMinimumLatency(1000);//設置延遲調度時間
builder.setOverrideDeadline(2000);//設置該Job截至時間博杖,在截至時間前肯定會執(zhí)行該Job
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//設置所需網絡類型
builder.setRequiresDeviceIdle(true);//設置在DeviceIdle時執(zhí)行Job
builder.setRequiresCharging(true);//設置在充電時執(zhí)行Job
builder.setExtras(extras);//設置一個額外的附加項
//...

1.3.3 獲取JobScheduler實例

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

1.3.4 開始調度Job

mJobScheduler.schedule(builder.build());//調度Job
mJobScheduler.cancel(jobId);//取消特定Job
mJobScheduler.cancelAll();//取消應用所有的Job

具體的示例

Google官方的Sample:https://github.com/googlearchive/android-JobScheduler

2才沧、JobScheduler的原理

2.1 JobScheduler如何執(zhí)行

JobScheduler是一個抽象類样刷,它在系統(tǒng)框架的實現(xiàn)類是android.app.JobSchedulerImpl

public class JobSchedulerImpl extends JobScheduler {
    IJobScheduler mBinder;

   JobSchedulerImpl(IJobScheduler binder) {
        mBinder = binder;
    }

    @Override
    public int schedule(JobInfo job) {
        try {
            return mBinder.schedule(job);
        } catch (RemoteException e) {
            return JobScheduler.RESULT_FAILURE;
        }
    }

    @Override
    public void cancel(int jobId) {
        try {
            mBinder.cancel(jobId);
        } catch (RemoteException e) {}

    }

    @Override
    public void cancelAll() {
        try {
            mBinder.cancelAll();
        } catch (RemoteException e) {}

    }

    @Override
    public List<JobInfo> getAllPendingJobs() {
        try {
            return mBinder.getAllPendingJobs();
        } catch (RemoteException e) {
            return null;
        }
    }
}

執(zhí)行的入口是JobScheduler.scheduler败去,其實是調了JobSchedulerImpl中的schedule方法;然后再調了mBinder.schedule(job)盗飒。這個mBinder就是JobSchedulerService,通過Binder跨進程調用JobSchedulerService陋桂。

最后調用到JobSchedulerService中的schedule方法:

public int schedule(JobInfo job, int uId) {
    JobStatus jobStatus = new JobStatus(job, uId);
    cancelJob(uId, job.getId());
    startTrackingJob(jobStatus);
     //通過handler發(fā)消息
    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    return JobScheduler.RESULT_SUCCESS;
}

接著發(fā)送MSG_CHECK_JOB消息逆趣,消息處理的地方是

private class JobHandler extends Handler {
    ...
    @Override
    public void handleMessage(Message message) {
        ...
        switch (message.what) {
            ...
            case MSG_CHECK_JOB:
                synchronized (mJobs) {
                    //主要是遍歷將來要處理的工作任務,然后一個個加到待處理工作任務集合中去
                    maybeQueueReadyJobsForExecutionLockedH();
                }
                break;
        }
        maybeRunPendingJobsH();
    }
    ...
}   

接著執(zhí)行JobHandler中的maybeRunPendingJobsH方法,處理相應的任務

private void maybeRunPendingJobsH() {
    synchronized (mJobs) {
       ...
       if (!availableContext.executeRunnableJob(nextPending)) {
          ...        
       }
      ... 
}

availableContext是JobServiceContext嗜历,即ServiceConnection宣渗,這個是進程間通訊ServiceConnection,通過調用availableContext.executeRunnableJob(nextPending)方法梨州,會觸發(fā)調用onServiceConnected痕囱,看到這里應該明白了,onServiceConnected方法中的service就是Jobservice暴匠,里面還用了WakeLock鎖鞍恢,防止手機休眠。

public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        ...
        final PowerManager pm =
                (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, runningJob.getTag());
        mWakeLock.setWorkSource(new WorkSource(runningJob.getUid()));
        mWakeLock.setReferenceCounted(false);
        mWakeLock.acquire();
        mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
    }
}

接著每窖,通過Handler發(fā)消息帮掉,調用了handleServiceBoundH()方法。

/** Start the job on the service. */
private void handleServiceBoundH() {
     ...
    try {
        mVerb = VERB_STARTING;
        scheduleOpTimeOut();
        service.startJob(mParams);
    } catch (RemoteException e) {
        ...
    }
}

從上面源碼可以看出窒典,最終是觸發(fā)調用了JobService中的startJob方法蟆炊。

2.2 JobInfo.Buidler的設置

builder.setMinimumLatency(1000);//設置延遲調度時間
builder.setOverrideDeadline(2000);//設置該Job截至時間,在截至時間前肯定會執(zhí)行該Job
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//設置所需網絡類型
builder.setRequiresDeviceIdle(true);//設置在DeviceIdle時執(zhí)行Job
builder.setRequiresCharging(true);//設置在充電時執(zhí)行Job
builder.setExtras(extras);//設置一個額外的附加項

從源碼看瀑志,設置的內容應用于JobStatus涩搓,例如網絡限制

public class JobStatus {

    public boolean hasConnectivityConstraint() {
        return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
    }

    public boolean hasUnmeteredConstraint() {
        return job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED;
    }
}

而在JobSchedulerService類,相關的狀態(tài)控制在其構造函數里:

public JobSchedulerService(Context context) {
    super(context);
    // Create the controllers.
    mControllers = new ArrayList<StateController>();
    mControllers.add(ConnectivityController.get(this));
    mControllers.add(TimeController.get(this));
    mControllers.add(IdleController.get(this));
    mControllers.add(BatteryController.get(this));
    mControllers.add(AppIdleController.get(this));

    mHandler = new JobHandler(context.getMainLooper());
    mJobSchedulerStub = new JobSchedulerStub();
    mJobs = JobStore.initAndGet(this);
}

例如網絡控制類ConnectivityController類

public class ConnectivityController extends StateController implements
            ConnectivityManager.OnNetworkActiveListener {
    //工作任務狀態(tài)集合
    private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
    //這個是手機網絡連接改變廣播劈猪,網絡發(fā)生改變昧甘,會觸發(fā)這個廣播
    private final BroadcastReceiver mConnectivityChangedReceiver =
        new ConnectivityChangedReceiver();
    /**
     * @param userId Id of the user for whom we are updating the connectivity state.
     */
    private void updateTrackedJobs(int userId) {
        ...
       if (changed) {
           mStateChangedListener.onControllerStateChanged();
       }
    }
   
    class ConnectivityChangedReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) {
                Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
                        + context.getUserId());
            }
            final String action = intent.getAction();
            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                final int networkType =
                        intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
                                ConnectivityManager.TYPE_NONE);
                // Connectivity manager for THIS context - important!
                final ConnectivityManager connManager = (ConnectivityManager)
                        context.getSystemService(Context.CONNECTIVITY_SERVICE);
                final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
                final int userid = context.getUserId();
                // This broadcast gets sent a lot, only update if the active network has changed.
                if (activeNetwork == null) {
                    mNetworkUnmetered = false;
                    mNetworkConnected = false;
                    updateTrackedJobs(userid);
                } else if (activeNetwork.getType() == networkType) {
                    mNetworkUnmetered = false;
                    mNetworkConnected = !intent.getBooleanExtra(
                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                    if (mNetworkConnected) {  // No point making the call if we know there's no conn.
                        mNetworkUnmetered = !connManager.isActiveNetworkMetered();
                    }
                    updateTrackedJobs(userid);
                }
            } else {
                ...
            }
        }
    }       
}

當網絡發(fā)生改變時,會調用updateTrackedJobs(userid)方法岸霹,在updateTrackedJobs方法中疾层,會判斷網絡是否有改變,有改變的會調mStateChangedListener.onControllerStateChanged()方法贡避;然后調用了JobSchedulerService類中onControllerStateChanged方法:

public class JobSchedulerService extends com.android.server.SystemService
        implements StateChangedListener, JobCompletedListener {      
    @Override
    public void onControllerStateChanged() {
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }             
}

接著也是處理MSG_CHECK_JOB 消息痛黎,和上文一樣予弧,最終觸發(fā)調用了JobService中的startJob方法。

2.3 JobSchedulerService的啟動

JobSchedulerService是一個系統(tǒng)服務湖饱,即應該在SystemServer啟動的掖蛤。閱讀SystemServer的源碼:

public final class SystemServer {
    private static final String TAG = "SystemServer";
    //手機開機啟動后會走這個main方法,然后調用run方法
    public static void main(String[] args) {
        new SystemServer().run();
    }
}

run方法如下:

private void run() {
    ...
    // Start services.
    try {
        startBootstrapServices();
        startCoreServices();
        startOtherServices();
    } catch (Throwable ex) {
       ...
    }
}

接著看startOtherServices()

private void startOtherServices() {
    ...
    mSystemServiceManager.startService(JobSchedulerService.class);
    ...
}

因此井厌,在這里就啟動了JobSchedulerService服務蚓庭。

引用

1.android 性能優(yōu)化JobScheduler使用及源碼分析

2.Android 9.0 JobScheduler(一) JobScheduler的使用

3.Android 9.0 JobScheduler(二) JobScheduler框架結構簡述及JobSchedulerService的啟動

4.Android 9.0 JobScheduler(三) 從Job的創(chuàng)建到執(zhí)行

5.Android 9.0 JobScheduler(四) Job約束條件的控制

6.理解JobScheduler機制

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仅仆,隨后出現(xiàn)的幾起案子器赞,更是在濱河造成了極大的恐慌,老刑警劉巖墓拜,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件港柜,死亡現(xiàn)場離奇詭異,居然都是意外死亡咳榜,警方通過查閱死者的電腦和手機夏醉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涌韩,“玉大人畔柔,你說我怎么就攤上這事〕加#” “怎么了靶擦?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長擎淤。 經常有香客問我奢啥,道長,這世上最難降的妖魔是什么嘴拢? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任桩盲,我火速辦了婚禮,結果婚禮上席吴,老公的妹妹穿的比我還像新娘赌结。我一直安慰自己,他們只是感情好孝冒,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布柬姚。 她就那樣靜靜地躺著,像睡著了一般庄涡。 火紅的嫁衣襯著肌膚如雪量承。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音撕捍,去河邊找鬼拿穴。 笑死,一個胖子當著我的面吹牛忧风,可吹牛的內容都是我干的默色。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狮腿,長吁一口氣:“原來是場噩夢啊……” “哼腿宰!你這毒婦竟也來了?” 一聲冷哼從身側響起缘厢,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吃度,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贴硫,有當地人在樹林里發(fā)現(xiàn)了一具尸體规肴,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年夜畴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片删壮。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡贪绘,死狀恐怖,靈堂內的尸體忽然破棺而出央碟,到底是詐尸還是另有隱情税灌,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布亿虽,位于F島的核電站菱涤,受9級特大地震影響,放射性物質發(fā)生泄漏洛勉。R本人自食惡果不足惜粘秆,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望收毫。 院中可真熱鬧攻走,春花似錦、人聲如沸此再。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽输拇。三九已至摘符,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逛裤。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工瘩绒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人别凹。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓草讶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炉菲。 傳聞我的和親對象是個殘疾皇子堕战,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355