Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue Moving from IntentService to JobIntentService for Android O

I am using Intent Service to monitor Geofence transition. For that I am using following call from a Sticky Service.

 LocationServices.GeofencingApi.addGeofences(
                    mGoogleApiClient,
                    getGeofencingRequest(),
                    getGeofencePendingIntent()
            )

and the Pending Intent calls Transition service (an IntentService) like below.

  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);
    }

This worked fine Pre Oreo. However, I had to convert my sticky service to a JobScheduler and I need to convert GeofenceTransitionsIntentService which is an intentService to JobIntentService.

Having said that I am not sure how to return create a PendingIntent for JobIntentService, because I need to call enqueueWork for JobIntentService.

Any suggestions/pointer would be appreciated.

like image 912
Akshay Avatar asked Oct 10 '17 20:10

Akshay


People also ask

What can I use instead of IntentService on Android?

This class was deprecated in API level 30. IntentService is subject to all the background execution limits imposed with Android 8.0 (API level 26). Consider using WorkManager or JobIntentService , which uses jobs instead of services when running on Android 8.0 or higher.

What is difference between IntentService and JobIntentService?

Both work same but the only difference with JobIntentService is that JobIntentService gets restarted if the application gets killed while the service was executing.

Why is IntentService deprecated Android?

Provided since Android API 3, the purpose of IntentService was to allow asynchronous tasks to be performed without blocking the main thread. The deprecation is one of a number of steps introduced starting with Android 8.0 (API 26) to limit the actions that can be performed while an app is in the background.

Does IntentService run on background thread?

If the Background task is to be performed for a long time we can use the intent service. Service will always run on the main thread. intent service always runs on the worker thread triggered from the main thread.


1 Answers

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);
like image 67
Cord Rehn Avatar answered Nov 15 '22 08:11

Cord Rehn