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指定的不一致
相关文章
相关标签/搜索