0

Here is what I do now:

  1. I have a service that runs in the background and reads user location. Each time a valid location is read (there are some parameters like distance and time) an IntentService is started to send that location to a web server

  2. The app, that uses the tracking service, also has some web calls, depending on what options the user presses. Right now, the app simply calls the web service in a asynctask.

Some code: The location service fires the IntentService, each time a good location is received, like this:

Intent intentService = new Intent(LocationLoggerService.this, LocationManagerIntentService.class);
intentService.putExtra(Constants.MESSAGE_LOCATION, readLocation);
startService(intentService);

The intentservice processes the intent:

@Override
protected void onHandleIntent(Intent intent) {
     LocationInfo location = intent.getExtras().getParcelable(Constants.MESSAGE_LOCATION);
   .... //Do the web call and broadcast result to app
}

Here are the changes I need to make:

  1. The IntentService and the app must not call web server in the same time. As the implementation is made now, this is not possible, as they act independent. I was thinking of passing all the web calls from the app, to the IntentService by creating intents for all of them. Would this work? In case that there was a location web sending, a new intent from an app call would be put in queue and executed right after current call?

  2. If there are multiple location sending in queue because of low network speed, the app call, needs to be put in front of queue and not wait for all existing intents to finish, just for the current one. Is there a way to put an intent on top of queue?

Thank you.

Later edit:

Here are the changes I've made.

  1. Created a custom intent service
public abstract class PriorityIntentService extends Service {
      private final AtomicInteger     intentsCount    = new AtomicInteger();

  protected void intentFinished() {
      intentsCount.decrementAndGet();
  }

  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }

      public final boolean sendPriorityMessage(Message msg) {

          intentsCount.incrementAndGet();

          int priority = msg.arg2;

          if (priority == 0) {
              return sendMessageAtFrontOfQueue(msg);
          } else {
              return sendMessage(msg);
          }

      }

      @Override
      public void handleMessage(Message msg) {
          onHandleIntent((Intent) msg.obj);

          if (intentsCount.get() == 0) {
              // stopSelf(msg.arg1);
              stopSelf();
          }
      }
  }


  @Override
  public void onStart(Intent intent, int startId) {

      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      msg.obj = intent;

      if (intent.getExtras().getInt(Constants.MESSAGE_PRIORITY) == 0) {
          msg.arg2 = 0;
      } else {
          msg.arg2 = 1;
      }

      mServiceHandler.sendPriorityMessage(msg);
      // mServiceHandler.sendMessage(msg);
  }
}
  1. And the other service:
public class LocationManagerIntentService extends PriorityIntentService {

    @Override
      protected void onHandleIntent(Intent intent) {

      intentFinished();
      }
}
Alin
  • 14,809
  • 40
  • 129
  • 218

1 Answers1

3

What you want to do is not possible because of the way IntentService is built, but, IntentService is a very simple code and is open source, so you can just create your own version of it with a few modifications.

The original code is here.

In the onCreate() method you can see where Service creates a HandlerThread and a Handler that dispatch all the onHandleIntent calls.

So all you have to do is:

  • Create a Singleton HandlerThread and Handler for your whole application to use. Every network call or CustomIntentService should call this single Handler. That's part 1. You just forced all the network calls to never happen concurrently.

  • Now that you have all the calls through the same Thread and Handler, it's just a matter of calling Handler.postAtFrontOfQueue to prioritize certain executions.

Happy coding.

Edit:

New edit:

The reason it's not executing is here:

stopSelf(msg.arg1);

This stops the service once all the messages being executed, check the docs:

Stop the service if the most recent time it was started was startId. This is the same as calling stopService(Intent) for this particular service but allows you to safely avoid stopping if there is a start request from a client that you haven't yet seen in onStart(Intent, int).

Be careful about ordering of your calls to this function. If you call this function with the most-recently received ID before you have called it for previously received IDs, the service will be immediately stopped anyway. If you may end up processing IDs out of order (such as by dispatching them on separate threads), then you are responsible for stopping them in the same order you received them.

So you'll have to find a different way of checking if all the messages have been executed before finishing. Maybe an AtomicInt counting the number of messages in the queue, or a map to hold the startId.

Budius
  • 39,391
  • 16
  • 102
  • 144
  • Thank you @Budius, I don't fully understand what you meant but I will closely investigate what you say there and try to implement it. – Alin Feb 27 '15 at 12:22
  • I put some extra comments and extra links on what each object does. It's the base for Android threading model. In my opinion regardless of the current code you're trying to do, it's very important to understand those concepts to be able to proper handle threading. PS.: don't forget to check the executors too: https://developer.android.com/reference/java/util/concurrent/Executors.html – Budius Feb 27 '15 at 12:29
  • please take a look on what I did so far and give your thoughts. Thank you, really appreciate it. – Alin Mar 09 '15 at 10:58
  • please take a look on the new edit I made on my answer – Budius Mar 09 '15 at 11:14
  • I really don't understand. Isn't `IntentService` supposed to process all the intents it receives in a queue? So if I send 5 intents, all should be executed one after another. From what I see, it only takes care of first intent, the rest are ignored which does not make any sense to me. – Alin Mar 09 '15 at 12:05
  • yes, but after the queue is empty the service have to stop. And on `IntentService` it stops by calling `stopSelf(int)` the int is the `startId` received on the last call to `start(Intent, int)`. All this makes the normal `IntentService` only stop after the last intent on the queue. Problem is that on your custom service, the order the Intents are received is not the same as they are processed. So you have to find a new way to track the queue status, to know when to stop the service. – Budius Mar 09 '15 at 12:09
  • I'm not sure I know how to do this.. I'll dig a bit more. Thanks for your precious time and info. – Alin Mar 09 '15 at 13:53
  • quick answer: remove `stopSelf(msg.arg1);` and find another way to call `stopSelf();` after ALL the work is done. – Budius Mar 09 '15 at 13:54
  • I wonder if I store in a variable in CustomIntentService the last startId, put this startId to each intent as an extra and then on LocationManagerIntentService after onHandleIntent if both values are the same, call stopSelf... – Alin Mar 09 '15 at 15:17
  • stopping the service is responsibility of the `CustomIntentService` (which I would rename to `PriorityIntentService`). I would suggest using an `AtomicInteger` (https://developer.android.com/reference/java/util/concurrent/atomic/AtomicInteger.html) and simply increment and decrement the value. If `value == 0` the queue is empty and the service can stop. – Budius Mar 09 '15 at 15:24
  • You say something like this: having `public static AtomicInt` in `PriorityIntentService` which gets incremented each time `sendPriorityMessage` is called. Then from my IntentService after a handle is executed, decrement this value? Where should I check if `value==0` on `handleMessage` ? – Alin Mar 10 '15 at 10:07
  • yes. Except I wouldn't make it `static`, just a `private final AtomicInt` – Budius Mar 10 '15 at 10:17
  • I've updated my solution. it does seem to work from my preliminary testing. Do you see any problem with my implementation ? Thanks – Alin Mar 11 '15 at 13:31
  • hehehehe... I feel like I'm doing code review here with you. Imagine you have 10 different classes extending `PriorityIntentService`, and then in one of them you forget to call `intentFinished()` this would cause a problem right? So why not, remove method `intentFinished();` and replace the `if (intentsCount.get() == 0) {` bit with `if (decrementAndGet.get() == 0) {`. – Budius Mar 11 '15 at 13:54