代碼
// 構建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/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.如果想重複執行怎麼操作?
想知道這些問題的答案肯定要看源碼
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); //開始定時執行該系統任務 }