Issue Moving from IntentService to JobIntentService for Android O

荒凉一梦 提交于 2019-12-04 12:44:28

Problem

I had the same issue when migrating from IntentService to JobIntentService on Android Oreo+ devices.

All the guides and snippets I've found are incomplete, they leave out the breaking change this migration has on the use of PendingIntent.getServce.

In particular, this migration breaks any Alarms scheduled to start a service with the AlarmManager and any Actions added to a Notification that start a service.


Solution

Replace PendingIntent.getService with PendingIntent.getBroadcast that starts a BroastcastReceiver.

This receiver then starts the JobIntentService using enqueueWork.


This can be repetitive and error prone when migrating multiple services.

To make this easier and service agnostic, I created a generic StartJobIntentServiceReceiver that takes a job ID and an Intent meant for a JobIntentService.

When the receiver is started, it will start the originally intended JobIntentService with a job ID and actually forwards the Intent's original contents through to the service behind the scenes.

/**
 * 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());
        }
    }

}

You will need to replace package names with your own, and register this BroadcastReceiver per usual in your AndroidManifest.xml:

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

You are now safe to use Context.sendBroadcast or PendingIntent.getBroadcast anywhere, simply wrap the Intent you want delivered to your JobIntentService in the receiver's static method, StartJobIntentServiceReceiver.getIntent.


Examples

You can start the receiver, and by extension your JobIntentService, immediately by doing this:

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

Anywhere you aren't starting the service immediately you must use a PendingIntent, such as when scheduling Alarms with AlarmManager or adding Actions to Notifications:

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

As @andrei_zaitcev suggested, I implemented my custom BroadCastReceiver and call enqueueWork() of the Service, which works perfectly.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!