JobScheduler之重試機制

文中的源代碼版本爲api23java

JobScheduler之重試機制

經過前面的學習,咱們知道若是Job須要進行重試,那麼會在JobSchedulerService.onJobCompleted方法中生成一個新的JobStatus實例,而後從新執行任務。 接下來咱們就來探討一下api

  1. 那些場景下Job會進行重試
  2. 如何進行重試的

首先咱們來看一下onJobCompleted方法異步

public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
    //log...
    if (!stopTrackingJob(jobStatus)) {
        //log...
        return;
    }
    if (needsReschedule) {
        JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
        startTrackingJob(rescheduled);
    } else if (jobStatus.getJob().isPeriodic()) {
        JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
        startTrackingJob(rescheduledPeriodic);
    }
    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
複製代碼

經過源碼的閱讀,咱們能夠看到有兩種重試場景ide

  1. 定時任務 JobInfo.isPeriodic返回true,Job會週期性的反覆執行
  2. needsReschedule參數爲true 咱們姑且稱這種爲失敗重試

定時任務

定時任務是經過構造JobInfo時調用Builder.setPeriodic方法時設置的學習

/** * intervalMillis爲時間間隔 */
public Builder setPeriodic(long intervalMillis) {
    mIsPeriodic = true;
    mIntervalMillis = intervalMillis;
    mHasEarlyConstraint = mHasLateConstraint = true;
    return this;
}
複製代碼

onJobCompleted方法中,檢測到Job是定時任務的話,會經過getRescheduleJobForPeriodic從新構造一個JobStatus實例ui

private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
    final long elapsedNow = SystemClock.elapsedRealtime();
    // Compute how much of the period is remaining.
    long runEarly = 0L;

    // If this periodic was rescheduled it won't have a deadline.
    if (periodicToReschedule.hasDeadlineConstraint()) {
        //若是當前時間尚未到上個Job的deadLine,那麼runEarly就是deadLine與當前時間的差
        //不然runEarly爲0
        runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
    }
    
    //新的最先執行時間是當前時間+runEarly
    long newEarliestRunTimeElapsed = elapsedNow + runEarly;
    long period = periodicToReschedule.getJob().getIntervalMillis();
    //新的deadLine爲newEarliestRunTimeElapsed+intervalMillis
    long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;

    //...
    return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
            newLatestRuntimeElapsed, 0 /* backoffAttempt */);
}
複製代碼

新建立的JobStatus對象相對於老對象主要更新了一下最先執行時間和最晚執行時間(deadLine)。同時爲了確保每一個時間間隔(intervalMillis)至多隻執行一次,最先執行時間還會加上一個runEarly.this

失敗重試

失敗重試依賴於onJobCompleted的調用點所傳的reschedule參數。 onJobCompleted的調用點只有一個JobServiceHandler.closeAndCleanupJobH,然後者的調用點則有多個,其中reschedule參數爲true的調用點4個。spa

第一個調用點

第一個調用點發生在JobServiceHandler.handleMessageMSG_SHUTDOWN_EXECUTIONcase中。 而觸發MSG_SHUTDOWN_EXECUTION消息的地方有兩個code

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    JobStatus runningJob;
    synchronized (mLock) {
        //...
        runningJob = mRunningJob;
    }
    if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
        //第一個點
        mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
        return;
    }
    //...
}

/** If the client service crashes we reschedule this job and clean up. */
@Override
public void onServiceDisconnected(ComponentName name) {
    //第二個點
    mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
}
複製代碼

因此總結以下對象

  1. 服務綁定成功後,發現當前的mRunningJob爲空,或者服務與JobStatus中指定的組件不一致則發送MSG_SHUTDOWN_EXECUTION消息
  2. 服務進程發生異常(如carsh等)致使鏈接斷開,則發送MSG_SHUTDOWN_EXECUTION消息

第二個調用點

private void handleServiceBoundH() {
    //...
    if (mCancelled.get()) {
        //log...
        closeAndCleanupJobH(true /* reschedule */);
        return;
    }
    //...
}
複製代碼

handleServiceBoundH方法的觸發時機在服務綁定成功以後,onStartJob觸發以前,這段時間內若是Job被取消,則會重試。

第三個調用點

private void handleFinishedH(boolean reschedule) {
    switch (mVerb) {
        case VERB_EXECUTING:
        case VERB_STOPPING:
            closeAndCleanupJobH(reschedule);
            break;
        default:
            //log...
    }
}
複製代碼

handleFinishedH的調用點有兩個,其中一個調用點入參寫死爲false,因此咱們只須要看另外一個就行了。另外一個調用點在JobServiceHandler.handleMessageMSG_CALLBACKcase中。

public void handleMessage(Message message) {
    switch (message.what) {
        //...
        case MSG_CALLBACK:
            //...
            if (mVerb == VERB_STARTING) {
                //...
            } else if (mVerb == VERB_EXECUTING ||
                    mVerb == VERB_STOPPING) {
                final boolean reschedule = message.arg2 == 1;
                handleFinishedH(reschedule);
            } else {
                ///...
            }
            break;
        //...
    }
}
複製代碼

mVerbVERB_EXECUTINGVERB_STOPPING的場景分別對應Job異步執行完畢的回調(jobFinished)以及執行完onStopJob後的回調。 而reshedule則對應着jobFinished方法的第二個入參以及onStopJob的返回值。

第四個調用點

第四個調用點在超時處理的VERB_STOPPINGcase中

private void handleOpTimeoutH() {
    switch (mVerb) {
        //...
        case VERB_STOPPING:
            //log...
            closeAndCleanupJobH(true /* needsReschedule */);
            break;
        //...
    }
}
複製代碼

也就是說若是onStopJob方法超時,也會重試。

失敗重試任務

失敗重試的任務是經過getRescheduleJobForFailure方法生成的

private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
    final long elapsedNowMillis = SystemClock.elapsedRealtime();
    final JobInfo job = failureToReschedule.getJob();

    final long initialBackoffMillis = job.getInitialBackoffMillis();
    final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
    long delayMillis;

    switch (job.getBackoffPolicy()) {
        case JobInfo.BACKOFF_POLICY_LINEAR:
            delayMillis = initialBackoffMillis * backoffAttempts;
            break;
        default:
            if (DEBUG) {
                Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
            }
        case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
            delayMillis =
                    (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
            break;
    }
    delayMillis =
            Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
    return new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
            JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
}
複製代碼

該方法會根據back-off策略,從新設置Job的延遲時間,同時將back-off次數加1,還有一個須要注意的是失敗重試的任務沒有deadLine

總結

綜上所述,咱們能夠整理出如下重試場景

  1. 定時任務
  2. jobFinished第二個入參(needsReschedule)傳true
  3. onStopJob返回true
  4. onStopJob方法超時
  5. 任務在服務綁定成功以後,onStartJob觸發以前被取消
  6. 服務綁定成功以後發現mRunningJob字段爲空,或者啓動的服務與mRunningJob指定的不一致
相關文章
相關標籤/搜索