Skip to content

Latest commit

 

History

History
135 lines (103 loc) · 8.91 KB

File metadata and controls

135 lines (103 loc) · 8.91 KB

使用多个JobServices

原标题:Working with Multiple JobServices
链接:https://android-developers.googleblog.com/2017/10/working-with-multiple-jobservices.html
作者:Isai Damier (Android DA软件工程师)
翻译:arjinmc

Android平台不断努力改善用户体验,从API级别26开始,对后台服务引入了严格的限制。基本上,除非你的应用程序在前台运行,否则系统将在几分钟内停止所有应用的后台服务。

由于对后台服务的这些限制, JobScheduler作业已经成为执行后台任务的事实上的解决方案。对于熟悉服务的人员,JobScheduler通常使用简单:除少数情况外,我们现在将探索其中一个。

想像你正在建立一个Android TV应用程序。由于渠道对电视应用程序非常重要,因此你的应用程序应能够在频道上执行至少五种不同的后台操作:发布频道,向频道添加节目,将有关频道的日志发送到远程服务器,更新频道的元数据,并删除频道。在Android 8.0(Oreo)之前,这五个操作中的每一个都可以在后台服务中实现。然而,从API 26开始,你必须明智地决定哪些应该是简单的旧就、后台Service,哪些应该是JobService

在电视应用程序的情况下,上述五个操作,只有频道发布可以是一个简单的旧的后台服务。对于某些上下文,通道发布涉及三个步骤:首先用户单击按钮开始该过程; 第二,应用程序启动后台操作来创建和提交出版物; 第三,用户获取用户界面来确认订阅。因此,你可以看到,发布频道需要用户交互,因此需要可见的活动。因此,ChannelPublisherService可以IntentService 处理后台部分。你不应该使用JobService这里的原因 是因为JobService会引入执行延迟,而用户交互通常需要你的应用程序的即时响应。

然而,对于其他四个操作,你应该使用JobServices; 这是因为所有这些都可能在你的应用程序在后台执行。所以分别,你应该有ChannelProgramsJobServiceChannelLoggerJobServiceChannelMetadataJobService,和ChannelDeletionJobService

避免JobId冲突

由于以上所有的四个JobService处理Channel 对象,所以使用它们channelId中的 jobId每一个应该是方便的。但是由于JobService是Android框架中设计的方式 ,你不能。以下是jobId的官方描述

Application-provided id for this job. Subsequent calls to cancel, 
or jobs created with the same jobId, will update the pre-existing 
job with the same id. This ID must be unique across all clients 
of the same uid (not just the same package). You will want to 
make sure this is a stable id across app updates, so probably not 
based on a resource ID.

告诉你的是什么,即使你使用4个不同的Java对象(即-JobServices),你仍然不能使用channelId作为它们jobId。你不会得到类级别命名空间的信用。

这确实是一个真正的问题。你需要一个稳定和可扩展的方式来将channelId其与其一组相关联jobId。你想要的最后一件事是由于jobId碰撞而使不同的频道覆盖对方的操作。是jobId是String类型替代整数,该解决方案将是很容易: 对于ChannelProgramsJobService,jobId= "ChannelPrograms" + channelId, 对ChannelLoggerJobService, jobId= "ChannelLogs" + channelId等,但因为jobId是一个整数,而不是一个字符串,你要设计一个聪明的系统,可重复使用的产生job的jobId。为此,你可以使用以下JobIdManager

JobIdManager是根据你的应用需求调整的课程。对于这个现在的电视应用程序,基本思想是使用一个channelId处理Channels的所有工作。要加快澄清:我们先来看看这个样本JobIdManager类的代码 ,然后再讨论一下。

public class JobIdManager {

   public static final int JOB_TYPE_CHANNEL_PROGRAMS = 1;
   public static final int JOB_TYPE_CHANNEL_METADATA = 2;
   public static final int JOB_TYPE_CHANNEL_DELETION = 3;
   public static final int JOB_TYPE_CHANNEL_LOGGER = 4;

   public static final int JOB_TYPE_USER_PREFS = 11;
   public static final int JOB_TYPE_USER_BEHAVIOR = 21;

   @IntDef(value = {
           JOB_TYPE_CHANNEL_PROGRAMS,
           JOB_TYPE_CHANNEL_METADATA,
           JOB_TYPE_CHANNEL_DELETION,
           JOB_TYPE_CHANNEL_LOGGER,
           JOB_TYPE_USER_PREFS,
           JOB_TYPE_USER_BEHAVIOR
   })
   @Retention(RetentionPolicy.SOURCE)
   public @interface JobType {
   }

   //16-1 for short. Adjust per your needs
   private static final int JOB_TYPE_SHIFTS = 15;

   public static int getJobId(@JobType int jobType, int objectId) {
       if ( 0 < objectId && objectId < (1<< JOB_TYPE_SHIFTS) ) {
           return (jobType << JOB_TYPE_SHIFTS) + objectId;
       } else {
           String err = String.format("objectId %s must be between %s and %s",
                   objectId,0,(1<<JOB_TYPE_SHIFTS));
           throw new IllegalArgumentException(err);
       }
   }
}

正如你所看到的,JobIdManager只需组合一个前缀与一个channelId来获得jobId。然而,这种优雅的简单只是冰山一角。让我们考虑下面的假设和注意事项。

第一个洞察力:你必须能够强制channelId进入一个Short,所以当你结合channelId一个前缀你最终会得到一个有效的Java整数。当然,严格来说,它不一定是短的。只要你的前缀和channelId组合成一个不溢出的整数,它将会工作。但边缘对声音工程至关重要。所以,除非你真的没有选择,否则就用短暂的强制去。你可以在实践中为远程服务器上具有较大ID的对象执行此操作的一种方法是在本地数据库或内容提供者中定义一个密钥,并使用该密钥生成你jobId的密钥。

第二个见解:你的整个应用程序应该只有一个JobIdManager类。该类应该jobId为你的所有应用程序的工作生成:这些工作是否与Channels,Users或 Cats和Dogs有关。示例JobIdManager类指出了这一点:并不是所有JOB_TYPE的都与 Channel操作有关。一个job类型与用户prefs有关,一个与用户行为有关。JobIdManager通过为每个job类型分配一个不同的前缀,它们的帐户都是这样。

第三个见解:对于每个-JobService应用程序,你必须具有唯一和最终的JOB_TYPE_前缀。再次,这必须是一个详尽的一对一的关系。

使用JobIdManager

以下代码片段是ChannelProgramsJobService演示如何在你的项目中使用JobIdManager。无论何时需要安排新工作,都会生成jobId使用JobIdManager.getJobId(...)

import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.PersistableBundle;

public class ChannelProgramsJobService extends JobService {
  
   private static final String CHANNEL_ID = "channelId";
   . . .

   public static void schedulePeriodicJob(Context context,
                                      final int channelId,
                                      String channelName,
                                      long intervalMillis,
                                      long flexMillis)
{
   JobInfo.Builder builder = scheduleJob(context, channelId);
   builder.setPeriodic(intervalMillis, flexMillis);

   JobScheduler scheduler = 
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
   if (JobScheduler.RESULT_SUCCESS != scheduler.schedule(builder.build())) {
       //todo what? log to server as analytics maybe?
       Log.d(TAG, "could not schedule program updates for channel " + channelName);
   }
}

private static JobInfo.Builder scheduleJob(Context context,final int channelId){
   ComponentName componentName =
           new ComponentName(context, ChannelProgramsJobService.class);
   final int jobId = JobIdManager
             .getJobId(JobIdManager.JOB_TYPE_CHANNEL_PROGRAMS, channelId);
   PersistableBundle bundle = new PersistableBundle();
   bundle.putInt(CHANNEL_ID, channelId);
   JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName);
   builder.setPersisted(true);
   builder.setExtras(bundle);
   builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
   return builder;
}

   ...
}

脚注:感谢Christopher Tate和Trevor Johns的宝贵意见