JobService 7.0 定時任務不生效


代碼

複製代碼
    // 構建JobInfo對象,傳遞給JobSchedulerService
        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,new ComponentName(mContext, AliveJobService.class));
        builder.setPeriodic(5000);
        builder.setPersisted(true);
        builder.setRequiresCharging(true);
        JobInfo info = builder.build();
        mJobScheduler.schedule(info);
複製代碼

這段定時任務在每隔5秒執行一次任務,Android 5.0和6.0系統能夠正常運行.但是在Android7.0不能正常工作了。

https://stackoverflow.com/questions/39641278/job-scheduler-in-android-n-with-less-then-15-minutes-interval?rq=1

https://stackoverflow.com/questions/38344220/job-scheduler-not-running-on-android-n/38774104

看萬兩片關於JobService 7.0(Nougat) 看完有幾個疑問

1.如果想到在小於15分鐘間隔執行爲什麼要設置setMinimumLatency()?

2.setBackoffCriteria(youtime, JobInfo.BACKOFF_POLICY_LINEAR)這個是有什麼作用,如果不設置會怎麼樣?

3.如果設置obFinished(parameters, true)obFinished(parameters, false)有什麼區別

4.如果想重複執行怎麼操作?

 

想知道這些問題的答案肯定要看源碼

 andorid 7.5JobInfo源碼

 

複製代碼
package android.app.job;

public class JobInfo implements Parcelable {
  //...

    /**
     * Amount of backoff a job has initially by default, in milliseconds.
     */
    public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L;  // 30 seconds.

    /**
     * Maximum backoff we allow for a job, in milliseconds.
     */
    public static final long MAX_BACKOFF_DELAY_MILLIS = 5 * 60 * 60 * 1000;  // 5 hours.

    public static final int BACKOFF_POLICY_LINEAR = 0;

    public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
    //默認指數
    public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;

    /* Minimum interval for a periodic job, in milliseconds. */
    private static final long MIN_PERIOD_MILLIS = 15 * 60 * 1000L;   // 15 minutes

    /* Minimum flex for a periodic job, in milliseconds. */
    private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
    
   
    

        
        /**
         * Specify that this job should be delayed by the provided amount of time.
         * Because it doesn't make sense setting this property on a periodic job, doing so will
         * throw an {@link java.lang.IllegalArgumentException} when
         * {@link android.app.job.JobInfo.Builder#build()} is called.
         * @param minLatencyMillis Milliseconds before which this job will not be considered for
         *                         execution.
         */
         
     public Builder setMinimumLatency(long minLatencyMillis) {
            mMinLatencyMillis = minLatencyMillis;
            mHasEarlyConstraint = true;
            return this;
        }
    
    /**
     * 最小15分鐘
     */
    public static final long getMinPeriodMillis() {
        return MIN_PERIOD_MILLIS;
    }
    
    public Builder setPeriodic(long intervalMillis, long flexMillis) {
            mIsPeriodic = true;
            mIntervalMillis = intervalMillis;
            mFlexMillis = flexMillis;
            mHasEarlyConstraint = mHasLateConstraint = true;
            return this;
    }

    /**
     * 最小15分鐘 如果值比15分鐘大取大的值
     */
    public long getIntervalMillis() {
        return intervalMillis >= getMinPeriodMillis() ? intervalMillis : getMinPeriodMillis();
    }
    
    
    /**
     * 最小5分鐘
     */
     public static final long getMinFlexMillis() {
        return MIN_FLEX_MILLIS;
    }

    /**
     * Math.max(percentClamp, getMinFlexMillis())最小15分鐘
     * flexMillis 是setPeriodic(long intervalMillis, long flexMillis)第二參數,默認這個和intervalMillis
     * clampedFlex的值最小值15分鐘
     */
    public long getFlexMillis() {
        long interval = getIntervalMillis();//最小15分鐘
        long percentClamp = 5 * interval / 100; //取時間間隔的5%
        //先取getMinFlexMillis()五分鐘 和getIntervalMillis()的5%的取最大值
        //然後和設置的setPeriodic(long intervalMillis, long flexMillis)中的flexMillis取最大值
        //所有這裏最小的值15分鐘
        long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
        //如果這個值比間隔小,去這個值,如果比間隔大去間隔值
        return clampedFlex <= interval ? clampedFlex : interval;
    }
    
     /**
     * The amount of time the JobScheduler will wait before rescheduling a failed job. This value
     * will be increased depending on the backoff policy specified at job creation time. Defaults
     * to 5 seconds.
     */
    public long getInitialBackoffMillis() {
        return initialBackoffMillis;
    }
    
    public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) {
            mBackoffPolicySet = true;
            mInitialBackoffMillis = initialBackoffMillis;
            mBackoffPolicy = backoffPolicy;
            return this;
        }

   //...

}
複製代碼

 

JobInfo這裏

 看完這個類可以回答這個問題

1.如果想到在小於15分鐘間隔執行爲什麼要設置.setMinimumLatency()

在7.0調用setPeriodic()之後在獲取間隔時間getIntervalMillis() 強制使用了最小時間15分鐘。所以想通過setPeriodic()來設置小於15分鐘間隔是不行的。所以如果小於15分鐘需要通過設置setMinimumLatency ()

 

複製代碼
         
     public Builder setMinimumLatency(long minLatencyMillis) {
            mMinLatencyMillis = minLatencyMillis;
            mHasEarlyConstraint = true;
            return this;
        }
    
    /**
     * 最小15分鐘
     */
    public static final long getMinPeriodMillis() {
        return MIN_PERIOD_MILLIS;
    }
    
    public Builder setPeriodic(long intervalMillis, long flexMillis) {
            mIsPeriodic = true;
            mIntervalMillis = intervalMillis;
            mFlexMillis = flexMillis;
            mHasEarlyConstraint = mHasLateConstraint = true;
            return this;
    }

    /**
     * 最小15分鐘 如果值比15分鐘大取大的值
     */
    public long getIntervalMillis() {
        return intervalMillis >= getMinPeriodMillis() ? intervalMillis : getMinPeriodMillis();
    }
複製代碼

 

1.getIntervalMillis()最小值15分鐘

2.getFlexMillis()最小值15分鐘

3.getMinFlexMillis最小值5分鐘

 

andorid 7.1NJobSchedulerService源碼 

複製代碼
  public final class JobSchedulerService extends com.android.server.SystemService
        implements StateChangedListener, JobCompletedListener {
        //...    
            
862    /**
863     * Called when we have a job status object that we need to insert in our
864     * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
865     * about.
866     */
867    private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
868        synchronized (mLock) {
869            final boolean update = mJobs.add(jobStatus);
870            if (mReadyToRock) {
871                for (int i = 0; i < mControllers.size(); i++) {
872                    StateController controller = mControllers.get(i);
873                    if (update) {
874                        controller.maybeStopTrackingJobLocked(jobStatus, null, true);
875                    }
876                    controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
877                }
878            }
879        }
880    }
 
 
882  /**
883     * Called when we want to remove a JobStatus object that we've finished executing. Returns the
884     * object removed.
          先從 JobStore 中移除這個 job,因爲 writeBack 爲 true,則需要更新 jobxs.xml 文件,通知控制器,取消 track!
885     */

886    private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
887            boolean writeBack) {
888        synchronized (mLock) {
889            // Remove from store as well as controllers. 先從 JobStore 中移除這個 job,因爲 writeBack 爲 true,則需要更新 jobxs.xml 文件!
890            final boolean removed = mJobs.remove(jobStatus, writeBack);
891            if (removed && mReadyToRock) {
892                for (int i=0; i<mControllers.size(); i++) {
893                    StateController controller = mControllers.get(i);//通知控制器,取消 track!
894                    controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
895                }
896            }
897            return removed;
898        }
899    }
 
 
1026    /**
1027     * A job just finished executing. We fetch the
1028     * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
1029     * whether we want to reschedule we readd it to the controllers.
1030     * @param jobStatus Completed job.
1031     * @param needsReschedule Whether the implementing class should reschedule this job.
1032     */
1033    @Override
1034    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
1035        if (DEBUG) {
1036            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
1037        }
1038        // Do not write back immediately if this is a periodic job. The job may get lost if system
1039        // shuts down before it is added back.
1040        if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {//調用了 stopTrackingJob 將這個 job 從 JobStore 和 controller 中移除:
1041            if (DEBUG) {
1042                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
1043            }
1044            // We still want to check for jobs to execute, because this job may have
1045            // scheduled a new job under the same job id, and now we can run it.
1046            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1047            return;
1048        }
1049        // Note: there is a small window of time in here where, when rescheduling a job,
1050        // we will stop monitoring its content providers.  This should be fixed by stopping
1051        // the old job after scheduling the new one, but since we have no lock held here
1052        // that may cause ordering problems if the app removes jobStatus while in here.
1053        if (needsReschedule) {
1054            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
1055            startTrackingJob(rescheduled, jobStatus);
1056        } else if (jobStatus.getJob().isPeriodic()) {//如果JobInfo調用setPeriodic會設置true
1057           JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
1058           startTrackingJob(rescheduledPeriodic, jobStatus); 
1059       }
1060       reportActive(); 
1061       mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();//發送 MSG_CHECK_JOB_GREEDY 給 JobSchedulerService.JobHandler
1062 }


947    /**
948     * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
949     * specify an override deadline on a failed job (the failed job will run even though it's not
950     * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
951     * ready job with {@link JobStatus#numFailures} > 0 will be executed.
952     *
953     * @param failureToReschedule Provided job status that we will reschedule.
954     * @return A newly instantiated JobStatus with the same constraints as the last job except
955     * with adjusted timing constraints.
956     *
957     * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
958     */
959    private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
960        final long elapsedNowMillis = SystemClock.elapsedRealtime();
961        final JobInfo job = failureToReschedule.getJob();
962
963        final long initialBackoffMillis = job.getInitialBackoffMillis();
964        final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
965        long delayMillis;
966
967        switch (job.getBackoffPolicy()) {
968            case JobInfo.BACKOFF_POLICY_LINEAR:
969                delayMillis = initialBackoffMillis * backoffAttempts;
970                break;
971            default:
972                if (DEBUG) {
973                    Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
974                }
975            case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
976                delayMillis =
977                        (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
978                break;
979        }
980        delayMillis =
981                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);//和5小時比較
982        JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
983                JobStatus.NO_LATEST_RUNTIME, backoffAttempts);//構建新的JobStatu 設置下次時間 設置backoff次數
984        for (int ic=0; ic<mControllers.size(); ic++) {
985            StateController controller = mControllers.get(ic);
986            controller.rescheduleForFailure(newJob, failureToReschedule);
987        }
988        return newJob;
989    }


991    /**
992     * Called after a periodic has executed so we can reschedule it. We take the last execution
993     * time of the job to be the time of completion (i.e. the time at which this function is
994     * called).
995     * This could be inaccurate b/c the job can run for as long as
996     * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
997     * to underscheduling at least, rather than if we had taken the last execution time to be the
998     * start of the execution.
999     * @return A new job representing the execution criteria for this instantiation of the
1000     * recurring job.
1001     */
1002    private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
1003        final long elapsedNow = SystemClock.elapsedRealtime();
1004        // Compute how much of the period is remaining.
1005        long runEarly = 0L;
1006
1007        // If this periodic was rescheduled it won't have a deadline.
1008        if (periodicToReschedule.hasDeadlineConstraint()) {
1009            runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
1010        }
1011        long flex = periodicToReschedule.getJob().getFlexMillis();
1012        long period = periodicToReschedule.getJob().getIntervalMillis();//間隔時間
1013        long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
1014        long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
1015
1016        if (DEBUG) {
1017            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
1018                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
1019        }
1020        return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
1021                newLatestRuntimeElapsed, 0 /* backoffAttempt */);
1022    }
            //...
        }
複製代碼

 

 3.如果設置obFinished(parameters, true)和obFinished(parameters, false)有什麼區別

 這裏關鍵在onJobCompleted

首先調用了 stopTrackingJob 將這個 job 從 JobStore 和 controller 中移除,因爲之前已經移除過了,所以這個 stopTrackingJob 的返回值爲 false

複製代碼
   @Override
    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
        if (DEBUG) {
            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
        }
        // Do not write back immediately if this is a periodic job. The job may get lost if system
        // shuts down before it is added back.
        // 再次停止 track 這個 job,這裏 stopTrackingJob 的返回值爲 false!
        if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
            if (DEBUG) {
                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
            }
            // We still want to check for jobs to execute, because this job may have
            // scheduled a new job under the same job id, and now we can run it.
            // 發送 MSG_CHECK_JOB_GREEDY,繼續執行其他的 job,然後直接 return
            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
            return;
        }
        // Note: there is a small window of time in here where, when rescheduling a job,
        // we will stop monitoring its content providers.  This should be fixed by stopping
        // the old job after scheduling the new one, but since we have no lock held here
        // that may cause ordering problems if the app removes jobStatus while in here.
        if (needsReschedule) {
            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
            startTrackingJob(rescheduled, jobStatus);
        } else if (jobStatus.getJob().isPeriodic()) {
            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
            startTrackingJob(rescheduledPeriodic, jobStatus);
        }
        reportActive();
        mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
    }
複製代碼
如果是true那麼needsReschedule的也是true執行就是這段代碼

      if (needsReschedule) {
            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
            startTrackingJob(rescheduled, jobStatus);
        } 

 這段點主要調用getRescheduleJobForFailure

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

       final long initialBackoffMillis = job.getInitialBackoffMillis();//獲取setBackoffCriteria設置的值,如果沒有設置默認是30秒
        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;
        }
        //getNumFailures值越大這個值也越大間隔時間會越來越長
       delayMillis =
                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);//和5小時比較
        JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
               JobStatus.NO_LATEST_RUNTIME, backoffAttempts);//構建新的JobStatu 設置下次時間 設置backoff次數
        for (int ic=0; ic<mControllers.size(); ic++) {
            StateController controller = mControllers.get(ic);
           controller.rescheduleForFailure(newJob, failureToReschedule);
       }
        return newJob;
    }
複製代碼
看完這段代碼可以回答
2.setBackoffCriteria(youtime, JobInfo.BACKOFF_POLICY_LINEAR)這個是有什麼作用,如果不設置會怎麼樣?
這個代碼可以看出如果不想默認的時間是30秒,默認指數DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL
就必須通過setBackoffCriteria()來設置時間 和線性或者指數增長策略



如果是false 那麼needsReschedule的也是false 執行就是這段代碼
這段如果設置了setPeriodic()被調用那麼isPeriodic()是true則下面代碼被執行
     else if (jobStatus.getJob().isPeriodic()) {
            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
            startTrackingJob(rescheduledPeriodic, jobStatus);
        }

  這段代碼主要調用getRescheduleJobForPeriodic方法

複製代碼
    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()) {
            runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
        }
        long flex = periodicToReschedule.getJob().getFlexMillis();//最小15分鐘
        long period = periodicToReschedule.getJob().getIntervalMillis();//最小15分鐘
        long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
        long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;

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

 

4.如果想重複執行怎麼操作?

 1.設置jobFinished((JobParameters) msg.obj, true);設置true

         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                jobFinished((JobParameters) msg.obj, true);
            } else {
                jobFinished((JobParameters) msg.obj, false);
            }

2.在jobFinished在jobFinished之前重新調用startJobScheduler

 

以下是android7.0怎麼設置startJobScheduler執行的

複製代碼
 public void startJobScheduler() {
        if (DEBUG) {
            Log.i(TAG, "startJobScheduler");
        }
        int id = JOB_ID;
        if (DEBUG) {
            Log.i(TAG, "開啓AstJobService id=" + id);
        }
        mJobScheduler.cancel(id);
        JobInfo.Builder builder = new JobInfo.Builder(id, new ComponentName(mContext, AstJobService.class));
        if (Build.VERSION.SDK_INT >= 24) {
            builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //執行的最小延遲時間
            builder.setOverrideDeadline(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);  //執行的最長延時時間
            builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
            builder.setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR);//線性重試方案
        } else {
            builder.setPeriodic(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
        }
        builder.setPersisted(true);  // 設置設備重啓時,執行該任務
        builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        builder.setRequiresCharging(true); // 當插入充電器,執行該任務
        JobInfo info = builder.build();
        mJobScheduler.schedule(info); //開始定時執行該系統任務
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章