Android N上作业调度程序未运行

47

在Android Marshmallow和Lollipop设备上,作业调度程序按预期工作,但在Nexus 5x(Android N预览版)上无法运行。

用于调度作业的代码

        ComponentName componentName = new ComponentName(MainActivity.this, TestJobService.class.getName());
        JobInfo.Builder builder;
        builder = new JobInfo.Builder(JOB_ID, componentName);
        builder.setPeriodic(5000);
        JobInfo jobInfo;
        jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobInfo = builder.build();
        int jobId = jobScheduler.schedule(jobInfo);

服务在清单文件中定义如下:

<service android:name=".TestJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" />

有人在Android N(预览版)上遇到这个问题吗?


你在哪里尝试安排一个任务?我的意思是它是一个广播接收器还是其他什么东西? - romtsn
@rom4ek,目前它在活动中,因为它是一个示例应用程序。 - Adil Bhatty
4个回答

60
在Android Nougat中,setPeriodic(long intervalMillis) 方法调用使用了 setPeriodic (long intervalMillis, long flexMillis) 来安排定期工作。
根据文档:

JobInfo.Builder setPeriodic (long intervalMillis, long flexMillis)

指定此作业应重复并提供间隔和灵活性。该作业可以在周期结束时的任何时间执行一个持续时间为灵活长度的窗口内。

intervalMillis long: 此作业重复的毫秒间隔。强制执行getMinPeriodMillis()的最小值。

flexMillis long: 此作业的毫秒灵活性。灵活性被夹紧,至少为getMinFlexMillis()或周期的5%中较高的那个。

示例:定期作业每5秒钟安排一次。
private static final int JOB_ID = 1001;
private static final long REFRESH_INTERVAL  = 5 * 1000; // 5 seconds

JobInfo jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
        .setPeriodic(REFRESH_INTERVAL)
        .setExtras(bundle).build();

上面的代码在Lollipop和Marshmallow中运行良好,但是当您在Nougat中运行时,您会注意到以下日志:

W/JobInfo: Specified interval for 1001 is +5s0ms. Clamped to +15m0s0ms
W/JobInfo: Specified flex for 1001 is +5s0ms. Clamped to +5m0s0ms

鉴于我们已将定期刷新间隔设置为5秒,该间隔小于getMinPeriodMillis()阈值。Android Nougat强制执行getMinPeriodMillis()

作为解决方法,我正在使用以下代码在定期间隔下安排任务,如果任务间隔小于15分钟。

JobInfo jobInfo;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
      .setMinimumLatency(REFRESH_INTERVAL)
      .setExtras(bundle).build();
} else {
  jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
      .setPeriodic(REFRESH_INTERVAL)
      .setExtras(bundle).build();
}

示例JobService示例:

public class SampleService extends JobService {
    @Override public boolean onStartJob(JobParameters params) {
        doSampleJob(params); 
        return true;
    }

    @Override public boolean onStopJob(JobParameters params) {
        return false;
    }

    public void doSampleJob(JobParameters params) {
        // Do some heavy operation
        ...... 
        // At the end inform job manager the status of the job.
        jobFinished(params, false);
    }
}

8
但是似乎只能使用setMinimumLatency一次运行。 - Karthik Rk
1
@KarthikRk,作业不能同时是周期性的并且具有最小延迟时间,所以如果您可以设置最小延迟,则它是一次性作业。 - DataDino
18
这并不能解决问题...使用这段代码工作不会定期运行。 - forresthopkinsa
1
setMinimumLatency 用于指定该作业应该被延迟提供的时间量。因为在周期性作业上设置此属性没有意义,所以在调用 build() 时这样做会抛出 IllegalArgumentException 异常。 - Badhrinath Canessane
同样的问题只被调用一次。 - Jay Dangar
显示剩余5条评论

18

如果有人仍在努力克服这种情况,

以下是适用于Android N及以上版本的解决方法(如果您想将周期性作业设置为低于15分钟)

请确认仅使用setMinimumLatency。此外,如果您正在运行需要很长时间的任务,则下一个作业将在当前作业完成时间+PROVIDED_TIME_INTERVAL时被调度。

.对于Android N以下的API级别,.SetPeriodic(long millis)效果很好。

@Override
public boolean onStartJob(final JobParameters jobParameters) {
    Log.d(TAG,"Running service now..");
    //Small or Long Running task with callback
    //Call Job Finished when your job is finished, in callback
    jobFinished(jobParameters, false );

    //Reschedule the Service before calling job finished
    if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
              scheduleRefresh();



    return true;
}

@Override
public boolean onStopJob(JobParameters jobParameters) {
    return false;
}

private void scheduleRefresh() {
  JobScheduler mJobScheduler = (JobScheduler)getApplicationContext()
                    .getSystemService(JOB_SCHEDULER_SERVICE);
  JobInfo.Builder mJobBuilder = 
  new JobInfo.Builder(YOUR_JOB_ID,
                    new ComponentName(getPackageName(), 
                    GetSessionService.class.getName()));

  /* For Android N and Upper Versions */
  if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      mJobBuilder
                .setMinimumLatency(60*1000) //YOUR_TIME_INTERVAL
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
  }

更新: 如果您考虑让您的重复工作在Doze模式下运行,并且正在考虑使用JobScheduler,请注意:JobSchedulers无法在Doze模式下运行。

我没有讨论Dozing,因为我们正在谈论JobScheduler。感谢@Elletlar指出一些人可能认为即使应用程序在Doze模式下也会运行,但事实并非如此。

对于Doze模式,AlarmManager仍然提供最佳解决方案。如果您想要定期运行任务,则可以使用setExactAndAllowWhileIdle(),或者如果您想要更灵活,则可以使用setAndAllowWhileIdle()

您还可以使用setAlarmClock(),因为设备始终会从Doze模式中退出以响铃,然后再次返回Doze模式。另一种方法是使用FCM。


1
好的解决方案,我的初步测试表明这比FCM效果更好。 - androidneil
@MRah 我理解你的计划安排解决方法,但是为什么你从onStartJob返回true而不是false呢?这会使得任务处于“未完成”状态,不是吗? - dor506
@dor506,如果你正在执行一个长时间运行的任务并返回false,则会调用onStopJob,你的任务将无法完成。因此,你需要返回true,并在任务完成时调用JobFinished(params,false)来释放wakelock并停止任务。我编辑了答案,希望它更容易理解。 - MRah
在我的情况下,我不需要长时间运行的作业,只需要一个简单的if语句。在这种情况下,我应该调用jobFinished -> schedule(如果需要)->返回false。对吗? - dor506
非常感谢你的帮助。不过,我很好奇为什么谷歌会推荐JobScheduler,如果它在使用setPeriodic时无法正常工作。 - Lonie

12

我已经找到了解决Nougat设备面临的问题的答案。如果工作需要在15分钟内重新安排,则Nougat设备无法调度该工作。

我尝试将间隔时间设置为15分钟,这样工作就开始每15分钟调度一次。

工作调度程序代码:

public static void scheduleJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context, PAJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceComponent);
    builder.setPeriodic(15 * 60 * 1000, 5 * 60 *1000);

    JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
    int ret = jobScheduler.schedule(builder.build());
    if (ret == JobScheduler.RESULT_SUCCESS) {
        Log.d(TAG, "Job scheduled successfully");
    } else {
        Log.d(TAG, "Job scheduling failed");
    }
}

就业服务:

public class PAJobService extends JobService {
    private static final String TAG = PRE_TAG + PAJobService.class.getSimpleName();
    private LocationManager mLocationManager;

    public boolean onStartJob(JobParameters params) {
        Log.d(TAG, "onStartJob");
        Toast.makeText(getApplicationContext(), "Job Started", Toast.LENGTH_SHORT).show();
        return false;
    }

    public boolean onStopJob(JobParameters params) {
        Log.d(TAG, "onStopJob");
        return false;
    }
}

简而言之,如果您将时间间隔增加到15分钟,则代码将开始工作。

private static final long REFRESH_INTERVAL = 15 * 60 * 1000;


将REFRESH_INTERVAL设置为15 * 60 * 1000真的很有帮助!谢谢! - Michaël Randria

1
如果您想要定期运行代码,时间间隔少于15分钟,则可以使用一种巧妙的方法。将jobFinished()设置为以下方式。
jobFinished(parameters, true);

它将使用重试策略重新安排代码。使用自定义退避标准进行定义。
.setBackoffCriteria();

在构建器中。然后它将定期运行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接