Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IntentService not firing

My application synchronizes data with a remote database via web service calls. I make these calls in an IntentService so they can run in the background (I call it SyncService).

The code to launch my IntentService looks like so:

Intent intent = new Intent();
intent.setClass(appContext, SyncService.class);

// place additional values in intent
intent.putExtra("data_type", SyncService.ITEM_TRACKING);
intent.putExtra("user_id", intUserId);

// call SyncService
appContext.startService(intent);

This, normally, looks great. However, one of my friends, who is also a user of my app, often tells me his data doesn't sync and get displayed on our website. His device happened to be displaying the symptoms while I was around. I plugged his device into my computer and here is what I found:

  • The code to launch SyncService (ie: the code above) was hit.
  • I had a breakpoint inside the onHandleIntent method of my IntentService and it never gets hit.
  • I checked his device's list of running services and SyncService was there and running. Interestingly, it had been running for about 20 minutes. I was under the impression that IntentService killed itself when it was all out of Intents to process.
  • I force stopped the SyncService (not the app) and, all of the sudden, onHandleIntent started getting hit over and over. It was like all the Intents were queued up somewhere on the device and were just now getting thrown at the SyncService.

Does anyone have any ideas as to what may be the problem? Do you think it's an issue with my app? With Android?

Again, I am handing a message to Android saying, "Start this IntentService or send the message to the already running IntentService." At that point, I have no control. The message never gets to the IntentService. Once I force quit the app, the messages get sent to the IntentService and it does its job.

UPDATE: I think this code is fine, but I'll put it up since a lot of you may want to see it.

Every Intent that comes in to the IntentService has an Extra denoting what "type" of call is to me made (ie: do I call this web service or that web service, etc). When an Intent comes in to the IntentService, I check the "type" and, if there is already an Intent in the queue for that type, I add an Extra to it called "skip" so, when it is reached, I don't execute the search (basically the IntentService can build up lots of Intents and it makes no sense to call this web service when this webservice was called 20 seconds ago). It basically protects the app from spamming the website.

It is important to note that none of this code is hit anyway (once the problem starts occurring). onStartCommand does not get called until the app is killed

    @Override
    public int onStartCommand (Intent intent, int flags, int startId) {
        // here be dragons
        // overriding this method and adding your own code is dangerous. i've wrapped
        // my code in a try/catch because it is essential that the super method be called
        // every time this method is entered. any errors in my code should not prevent this
        // or the app will explode.
        try {
            if (flags == 0 && intent != null && intent.hasExtra("data_type")) {
                Integer intDataType = intent.getExtras().getInt("data_type");

                    if (!mCurrentTypes.containsKey(intDataType)
                            || !mCurrentTypes.get(intDataType)) {
                        mCurrentTypes.put(intDataType, true);  // put this type in the list and move on
                    }
                    else {
                        intent.putExtra("skip", true);  // mark this Intent to be skipped
                    }
            }
        }
        catch (Exception e) {
            // Log.e("Error onStartCommand", "error: " + e);
        }

        return super.onStartCommand(intent, flags, startId);
    }


private void processIntent(Intent intent) {
        // do stuff if no "skip" Extra
        mCurrentTypes.put(intDataType, false);
    }
like image 503
Andrew Avatar asked Jul 05 '12 14:07

Andrew


2 Answers

There is definitly something that keeps your service running on your friend's device. If so all subsequent call to this intent service are queued until the current one finishes. If it doesn't finish then you will get what you have : next services won't start.

You should double check that :

  • you give proper timeouts to nework operations
  • you give proper timeouts to nework connections operations
  • there is no race condition between threads.
  • you log any exception that can occur inside the service, you don't wanna loose that kind of information.

Afterwards, if you think everything is green : just log what the service does and use some bug reporting mechanism to get it automatically sent from your friends device. A simple solution could be to use bugsense or equivalent.

Next, put in place some kind of watchdog : a thread that will go on running until your service stops (you just tell your thread to stop when service is stopped). The thread will have to stop your service after some time limit has been passed.

This watchdog thread could be put inside the service itself, or outside, although this may be more complex to put in place.

like image 140
Snicolas Avatar answered Nov 07 '22 12:11

Snicolas


This answer suggests a solution that worked for me in similar situations. It doesn't fix your current code but suggests another, perhaps simpler (and easier to debug) option:

  1. Add a BroadcastReceiver to your calling Activity that listens for SUCCESS Intents from the IntentService.

  2. In your calling Activity, include the logic for when to start the IntentService (and don't include it in the IntentService). The logic is:

    • Call startService() and set a flag in the calling Activity to CANNOT_CALL.
    • If the Activity's BroadcastReceiver has not received a SUCCESS broadcast from the IntentService, then startService() can not be called again.
    • When the Activity does receive a SUCCESS intent, set the flag to CAN_CALL, and startService() can be called when the timer hits again.
  3. In your IntentService, write your onStartCommand() like so:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }
    
  4. In you IntentService, when you've received, parsed and stores the web service response, call sendBroadcast() with an Intent with custom action SUCCESS.

This logic is just an outline and has to be fine-tuned for error messages from the web service that have to be broadcast from IntentService to the listening Activity.

Hope this helps.

like image 1
onosendai Avatar answered Nov 07 '22 12:11

onosendai