在Android O中从IntentService移动到JobIntentService出现的问题

5
我正在使用Intent Service来监控地理围栏的转换。为此,我从一个粘性服务中使用以下调用。
 LocationServices.GeofencingApi.addGeofences(
                    mGoogleApiClient,
                    getGeofencingRequest(),
                    getGeofencePendingIntent()
            )

而Pending Intent会调用类似以下的Transition服务(一个IntentService)。

  private PendingIntent getGeofencePendingIntent() {
        Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
        // We use FLAG_UPDATE_CURRENT so that we get the 
          //same pending intent back when calling addgeoFences()
        return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

这在 Oreo 之前可以正常工作。但是,我不得不将我的粘性服务转换为 JobScheduler,并且我需要将 GeofenceTransitionsIntentService(一个 intentService)转换为 JobIntentService。

话虽如此,我不确定如何为 JobIntentService 返回 create a PendingIntent,因为我需要调用 JobIntentService 的 enqueueWork。

如果有任何建议/指针,将不胜感激。


4
将BroadcastReceiver用作Geofence API的待定意图,然后在该BroadcastReceiver中安排一个作业,一旦Geofence API触发它,即可执行该作业。 - andrei_zaitcev
我认为,Android O不建议使用广播接收器。 - Akshay
@andrei_zaitcev 在这种情况下,您会建议哪些意图过滤器用于广播呢?特别是考虑到Android Oreo? - Akshay
1
Android Oreo对广播接收器没有任何后台限制。您可以将其保留为无意图过滤器。但是,如果您的应用程序处于所谓的后台模式下,则无法从此接收器运行后台服务。 - andrei_zaitcev
@Akshay 在这里:https://developer.android.com/about/versions/oreo/background. 广播限制:除了有限的几个例外之外,应用程序不能使用其清单来注册隐式广播。它们仍然可以在运行时注册这些广播,并且可以使用清单来注册专门针对其应用程序的显式广播。 - Jose_GD
显示剩余5条评论
2个回答

10

问题

在 Android Oreo+ 设备上,我从 IntentService 迁移到 JobIntentService 时遇到了相同的问题。

所有我找到的指南和代码片段都是不完整的,它们忽略了这种迁移对于使用 PendingIntent.getServce 的影响。

特别地,此迁移会破坏任何使用 AlarmManager 触发启动服务的 Alarm,以及任何用于启动服务的 Actions 添加到 Notification 中的情况。


解决方案

使用以一个 BroastcastReceiver 启动的 PendingIntent.getBroadcast 替换 PendingIntent.getService

然后,该接收器使用 enqueueWork 启动 JobIntentService


当迁移多个服务时,这可能是重复且容易出错的。

为了使其更加简单并且与服务无关,在这里创建了一个通用的 StartJobIntentServiceReceiver,它需要一个作业 ID 和一个用于 JobIntentServiceIntent

当接收器启动时,它将使用作业 ID 启动最初预定的 JobIntentService,并且在幕后实际转发 Intent 的原始内容到服务中。

/**
 * A receiver that acts as a pass-through for enqueueing work to a {@link android.support.v4.app.JobIntentService}.
 */
public class StartJobIntentServiceReceiver extends BroadcastReceiver {

    public static final String EXTRA_SERVICE_CLASS = "com.sg57.tesladashboard.extra_service_class";
    public static final String EXTRA_JOB_ID = "com.sg57.tesladashboard.extra_job_id";

    /**
     * @param intent an Intent meant for a {@link android.support.v4.app.JobIntentService}
     * @return a new Intent intended for use by this receiver based off the passed intent
     */
    public static Intent getIntent(Context context, Intent intent, int job_id) {
        ComponentName component = intent.getComponent();
        if (component == null)
            throw new RuntimeException("Missing intent component");

        Intent new_intent = new Intent(intent)
                .putExtra(EXTRA_SERVICE_CLASS, component.getClassName())
                .putExtra(EXTRA_JOB_ID, job_id);

        new_intent.setClass(context, StartJobIntentServiceReceiver.class);

        return new_intent;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            if (intent.getExtras() == null)
                throw new Exception("No extras found");


            // change intent's class to its intended service's class
            String service_class_name = intent.getStringExtra(EXTRA_SERVICE_CLASS);

            if (service_class_name == null)
                throw new Exception("No service class found in extras");

            Class service_class = Class.forName(service_class_name);

            if (!JobIntentService.class.isAssignableFrom(service_class))
                throw new Exception("Service class found is not a JobIntentService: " + service_class.getName());

            intent.setClass(context, service_class);


            // get job id
            if (!intent.getExtras().containsKey(EXTRA_JOB_ID))
                throw new Exception("No job ID found in extras");

            int job_id = intent.getIntExtra(EXTRA_JOB_ID, 0);


            // start the service
            JobIntentService.enqueueWork(context, service_class, job_id, intent);


        } catch (Exception e) {
            System.err.println("Error starting service from receiver: " + e.getMessage());
        }
    }

}

您需要将包名称替换为自己的,并按照惯例在AndroidManifest.xml中注册此BroadcastReceiver

您需要用自己的包名替换掉示例中的名称,并像往常一样在AndroidManifest.xml中注册这个BroadcastReceiver

<receiver android:name=".path.to.receiver.here.StartJobIntentServiceReceiver"/>

现在,您可以安全地在任何地方使用Context.sendBroadcastPendingIntent.getBroadcast,只需将要传递给JobIntentServiceIntent封装在接收器的静态方法StartJobIntentServiceReceiver.getIntent中即可。


示例

您可以通过执行以下操作立即启动接收器(并因此启动JobIntentService):

Context.sendBroadcast(StartJobIntentServiceReceiver.getIntent(context, intent, job_id));

无论何时不立即启动服务,都必须使用PendingIntent,例如在使用AlarmManager定时调度Alarms或向Notification添加Action时:

PendingIntent.getBroadcast(context.getApplicationContext(),
    request_code,
    StartJobIntentServiceReceiver.getIntent(context, intent, job_id),
    PendingIntent.FLAG_UPDATE_CURRENT);

针对你提到的当迁移多个服务时,这可能会变得重复和容易出错, 这是主观的。你提供的解决方案可能可行,但在我看来并不是最好的选择,至少在我查阅谷歌文档时,答案是将作业加入队列。 - Akshay

-1

如 @andrei_zaitcev 建议的那样,我实现了自定义的 BroadCastReceiver 并调用 Service 的 enqueueWork() 方法,这个方法完美地运行。


3
请问您是否有您实现的示例?我正在升级一个应用程序到Oreo,并一直在寻找类似的解决方案。 - Kyle
你可以在他们的地理围栏示例中看到这种方法,但我无法使其工作,令人惊讶的是,它已经被弃用了!代码如何已经更新为O而被弃用??谷歌因这个愚蠢的垃圾堆得到了F。 - Rob

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