Franco Franco - 3 months ago 30
Android Question

Can GcmTaskService run async tasks?

I need to run a network task periodically on the background of an Android app.

I was initially going to use the

AlarmManager
(can't use the
JobScheduler
cause it has to work on pre-lollipop devices), but then I came across the
GcmNetworkManager
which seems easier to use and provides an easier API, and it takes care of running the task if the device is connected to the internet (also no need to use broadcast receivers with it, so less classes to maintain).

The problem I'm having is that the task I need to run is composed of 3 async steps, and the
GcmTaskService
seems to be created to run sync tasks.

I have tested this, and found that my async tasks run just fine till the end inside the
GcmTaskService
(my service then stops itself), however I'm worried that this might be more a coincidence because my async tasks are very fast, rather than the fact that the service is not stopped inside the
GcmTaskService
code (I tried to look into the code, but it is obfuscated so it's pretty hard to understand what it does).

Does anyone know if the
GcmTaskService
in fact runs until the extending class stops it or if it'll be stopped when the sync task ends?

Answer

After some investigation and debugging I found an answer. I'll describe it here so maybe it can help someone else in the future.

As I suspected, the GcmTaskService stops itself when all the tasks it needs to run are finished (which makes lots of sense). The proof of this is on this method (which is inside the GcmTaskService class):

private void zzdJ(String var1) {
    Set var2 = this.zzaIU;
    synchronized(this.zzaIU) {
        this.zzaIU.remove(var1);
        if(this.zzaIU.size() == 0) {
            this.stopSelf(this.zzaIV);
        }

    }
}

This method is called from the thread that runs the task, after it finishes (a.k.a. after the onRunTask() returns).

var1 is the tag that is assigned to the task by the developer when creating it, and zzaIU is the list of tasks that need to be run by this service. So, as we can see the task that finished is removed from the list, and if there are no more tasks left to run the service is stopped.

Possible solution:

There is a possible solution to run async tasks inside a GcmTaskService however. To do this, we need to override the onStartCommand() method, to prevent the GcmTaskService from ever starting the task in another thread.

The code will look like this:

private boolean taskRunning = false;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    String intentAction = intent.getAction();
    if (SERVICE_ACTION_EXECUTE_TASK.equals(intentAction)) {
        taskRunning = true;

        // Run your async tasks. Make sure to stop the service when they end.
    } else if (SERVICE_ACTION_INITIALIZE.equals(intentAction)) {
        // Initialize tasks if needed (most likely not needed if they are running asynchronously)

        // If this step is not needed, make sure to stop the service if the tasks already run (this could be called after 
        // the service run all the tasks, and if we don't stop the service it'll stay running on the background without doing 
        // anything)
        if (!taskRunning) {
            stopSelf();
        }
    }

    return START_NOT_STICKY;
}


@Override
public int onRunTask(TaskParams taskParams) {
    // IMPORTANT: This method will not be run, since we have overridden the onStartCommand() to handle the tasks run ourselves,
    // which was needed because our tasks are asynchronous

    return GcmNetworkManager.RESULT_SUCCESS;
}

This will only work if the service is developed to run 1 task, if it is needed to run multiple tasks you'll need to use a list instead of the taskRunning boolean, and check the size to see if more tasks need to be run before stopping the service (like the original GcmTaskService code does).

Even though this is a solution, it is not future proof, as the code on the GcmTaskService might change radically on future Google Play services versions, in which case it might break this functionality (unlikely, but possible). So I think I'll just go with the AlarmManager instead just to be safe.

Comments