JobSchedulerService 源碼分析 -- cancelJob 流程 (API 21)

一置森、 調(diào)用流程

JobScheduler.cancel(int jobId)
|
JobSchedulerImpl.cancel(int jobId)
|
IJobScheduler mBinder;
public void cancel(int jobId) {
    try {
        mBinder.cancel(jobId);
    } catch (RemoteException e) {}

}
| 跨進(jìn)程調(diào)用至 JobSchedulerService
|
JobSchedulerService.cancel(int jobId)

二、JobSchedulerService 取消 job 流程

1. 最終調(diào)用該方法

public void cancel(int jobId) throws RemoteException {
    final int uid = Binder.getCallingUid();

    long ident = Binder.clearCallingIdentity();
    try {
        JobSchedulerService.this.cancelJob(uid, jobId);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}
public void cancelJob(int uid, int jobId) {
    JobStatus toCancel;
    synchronized (mJobs) {
        // 根據(jù) uid jobId 獲取之前已經(jīng)存在的 job
        toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
    }
    if (toCancel != null) {
        cancelJobImpl(toCancel);
    }
}

2. 從 mJobs 列表符糊、待執(zhí)行 Job 列表移除 job凫海,處理正在執(zhí)行的 job

/**
 * 該方法做了三件事情:
 * 1. 從 mJobs 中及各 Controller 的追蹤列表中移除 job
 * 2. 從 mPendingJobs 列表移除(待執(zhí)行 job 列表)
 * 3. 對(duì)正在被 JobServiceContext 執(zhí)行的 job 進(jìn)行處理
 * @param cancelled
 */
private void cancelJobImpl(JobStatus cancelled) {
    if (DEBUG) {
        Slog.d(TAG, "Cancelling: " + cancelled);
    }
    // Remove from store as well as controllers.
    // 從 mJobs 中及各 Controller 的追蹤列表中移除 job
    stopTrackingJob(cancelled);
    synchronized (mJobs) {
        // Remove from pending queue.
        // 從 mPendingJobs 列表移除(待執(zhí)行 job 列表)
        mPendingJobs.remove(cancelled);
        // Cancel if running.
        // 對(duì)正在被 JobServiceContext 執(zhí)行的 job 進(jìn)行處理
        stopJobOnServiceContextLocked(cancelled);
    }
}

3. 處理正在執(zhí)行的 job

private boolean stopJobOnServiceContextLocked(JobStatus job) {
    for (int i=0; i<mActiveServices.size(); i++) {
        JobServiceContext jsc = mActiveServices.get(i);
        final JobStatus executing = jsc.getRunningJob();
        if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
            // 找出正在執(zhí)行的 job,進(jìn)行處理
            jsc.cancelExecutingJob();
            return true;
        }
    }
    return false;
}

三男娄、取消正在執(zhí)行的 job

1. 發(fā)出 MSG_CANCEL 消息

/** Called externally when a job that was scheduled for execution should be cancelled. */
void cancelExecutingJob() {
    mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
}

2. 接收 MSG_CANCEL 消息

private class JobServiceHandler extends Handler {
    @Override
    public void handleMessage(Message message) {
        switch (message.what) {
            case MSG_CANCEL:
                handleCancelH();
                break;
        }
    }
}

3. 處理 MSG_CANCEL 消息

a) cancel job 不影響正在執(zhí)行的 job 進(jìn)行 bindService

b) 調(diào)起 JobService 中的 startJob 前會(huì)檢查 job 是否已被 cancel, 決定是否繼續(xù)執(zhí)行 JobService 中的 startJob

c) JobService 中的 onStartJob() 返回true 后會(huì)檢查該值, 決定是否調(diào)用 sendStopMessageH()

/**
 * A job can be in various states when a cancel request comes in:
 * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
 *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
 *     _STARTING   -> Mark as cancelled and wait for
 *                    {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
 *     _EXECUTING  -> call {@link #sendStopMessageH}}, but only if there are no callbacks
 *                      in the message queue.
 *     _ENDING     -> No point in doing anything here, so we ignore.
 */
private void handleCancelH() {
    switch (mVerb) {
        // bindService 前置為該值
        case VERB_BINDING:
            // 調(diào)用 JobService 的 onStartJob 前置為該值
        case VERB_STARTING:
            // 注意此處行贪,當(dāng)狀態(tài)為 VERB_BINDING、VERB_STARTING 時(shí)模闲,僅僅更改 mCancelled 值
            // 在完成 bindService 后建瘫,調(diào)起 JobService 中的 startJob 前,會(huì)檢查該值
            // 在 JobService 中的 onStartJob() 返回true 后會(huì)檢查該值
            mCancelled.set(true);
            break;
        // 調(diào)用 JobService 的 onStartJob 返回后置為該值
        case VERB_EXECUTING:
            if (hasMessages(MSG_CALLBACK)) {
                // If the client has called jobFinished, ignore this cancel.
                return;
            }
            sendStopMessageH();
            break;
        case VERB_STOPPING:
            // Nada.
            break;
    }
}

4. 調(diào)用 JobService 的 onStopJob()

/**
 * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
 * VERB_STOPPING.
 */
private void sendStopMessageH() {
    removeOpTimeOut();
    // 調(diào)用 JobService 的 onStartJob 返回后置為該值
    if (mVerb != VERB_EXECUTING) {
        // 走入這里說明尸折,job 尚未執(zhí)行啰脚,或者 job 不是正在執(zhí)行狀態(tài),直接進(jìn)行 job 的收尾工作
        Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
        closeAndCleanupJobH(false /* reschedule */);
        return;
    }
    try {
        mVerb = VERB_STOPPING;
        scheduleOpTimeOut();
        // 調(diào)用 JobService 的 onStopJob()
        service.stopJob(mParams);
    } catch (RemoteException e) {
        Slog.e(TAG, "Error sending onStopJob to client.", e);
        closeAndCleanupJobH(false /* reschedule */);
    }
}

5. JobService 的 onStopJob() 完成后回調(diào) JobServiceContext 的 acknowledgeStopMessage()

/**
 * JobService 的 onStopJob() 完成后回調(diào)該方法
 */
@Override
public void acknowledgeStopMessage(int jobId, boolean reschedule) {
    if (!verifyCallingUid()) {
        return;
    }
    mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0).sendToTarget();
}

public void handleMessage(Message message) {
    switch (message.what) {
        case MSG_CALLBACK:
            if (DEBUG) {
                Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob + " v:" + (mVerb >= 0 ? VERB_STRINGS[mVerb] : "[invalid]"));
            }
            removeOpTimeOut();

            if (mVerb == VERB_STARTING) {
                // JobService.onStartJob(params) 后回調(diào)至這里
                final boolean workOngoing = message.arg2 == 1;
                handleStartedH(workOngoing);
            } else if (mVerb == VERB_EXECUTING || mVerb == VERB_STOPPING) {
                // JobService.jobFinished() 及執(zhí)行 onStopJob() 后會(huì)回調(diào)至這里
                final boolean reschedule = message.arg2 == 1;
                // unbindService()实夹,把該 job 從 mJobs 中移除橄浓,同時(shí)會(huì)處理需要重新 schedule 或周期性的 Job
                handleFinishedH(reschedule);
            } 
            break;
    }
}

/**
 * VERB_EXECUTING  -> Client called jobFinished(), clean up and notify done.
 *     _STOPPING   -> Successful finish, clean up and notify done.
 *     _STARTING   -> Error
 *     _PENDING    -> Error
 */
private void handleFinishedH(boolean reschedule) {
    switch (mVerb) {
        case VERB_EXECUTING:
        case VERB_STOPPING:
            // unbindService()粒梦,把該 job 從 mJobs 中移除,同時(shí)會(huì)處理需要重新 schedule 或周期性的 Job
            closeAndCleanupJobH(reschedule);
            break;
        default:
            Slog.e(TAG, "Got an execution complete message for a job that wasn't being" + "executed. Was " + VERB_STRINGS[mVerb] + ".");
    }
}

/**
 * unbindService()荸实,把該 job 從 mJobs 中移除匀们,同時(shí)會(huì)處理需要重新 schedule 或周期性的 Job
 * The provided job has finished, either by calling
 * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
 * or from acknowledging the stop message we sent. Either way, we're done tracking it and
 * we want to clean up internally.
 */
private void closeAndCleanupJobH(boolean reschedule) {
    final JobStatus completedJob = mRunningJob;
    synchronized (mLock) {
        try {
            mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getUid());
        } catch (RemoteException e) {
            // Whatever.
        }
        if (mWakeLock != null) {
            mWakeLock.release();
        }
        // 注意此處,調(diào)用 unbindService
        mContext.unbindService(JobServiceContext.this);
        mWakeLock = null;
        mRunningJob = null;
        mParams = null;
        mVerb = -1;
        mCancelled.set(false);
        service = null;
        mAvailable = true;
    }
    removeOpTimeOut();
    removeMessages(MSG_CALLBACK);
    removeMessages(MSG_SERVICE_BOUND);
    removeMessages(MSG_CANCEL);
    removeMessages(MSG_SHUTDOWN_EXECUTION);
    // onJobCompleted 中會(huì)把該 job 從 mJobs 中移除准给,同時(shí)會(huì)處理需要重新 schedule 或周期性的 Job
    mCompletedListener.onJobCompleted(completedJob, reschedule);
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泄朴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子露氮,更是在濱河造成了極大的恐慌祖灰,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沦辙,死亡現(xiàn)場離奇詭異夫植,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)油讯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門详民,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陌兑,你說我怎么就攤上這事沈跨。” “怎么了兔综?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵饿凛,是天一觀的道長。 經(jīng)常有香客問我软驰,道長涧窒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任锭亏,我火速辦了婚禮纠吴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慧瘤。我一直安慰自己戴已,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布锅减。 她就那樣靜靜地躺著糖儡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怔匣。 梳的紋絲不亂的頭發(fā)上握联,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼拴疤。 笑死永部,一個(gè)胖子當(dāng)著我的面吹牛独泞,可吹牛的內(nèi)容都是我干的呐矾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼懦砂,長吁一口氣:“原來是場噩夢啊……” “哼蜒犯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荞膘,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤罚随,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后羽资,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淘菩,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年屠升,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了潮改。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腹暖,死狀恐怖汇在,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脏答,我是刑警寧澤糕殉,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站殖告,受9級(jí)特大地震影響阿蝶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜黄绩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一羡洁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宝与,春花似錦焚廊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诽里,卻和暖如春袒餐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工灸眼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卧檐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓焰宣,卻偏偏與公主長得像霉囚,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匕积,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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