28

I have an IntentService that starts an asynchronous task in another class and should then be waiting for the result.

The problem is that the IntentService will finish as soon as the onHandleIntent(...) method has finished running, right?

That means, normally, the IntentService will immediately shut down after starting the asynchronous task and will not be there anymore to receive the results.

public class MyIntentService extends IntentService implements MyCallback {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected final void onHandleIntent(Intent intent) {
        MyOtherClass.runAsynchronousTask(this);
    }

}

public interface MyCallback {

    public void onReceiveResults(Object object);

}

public class MyOtherClass {

    public void runAsynchronousTask(MyCallback callback) {
        new Thread() {
            public void run() {
                // do some long-running work
                callback.onReceiveResults(...);
            }
        }.start();
    }

}

How can I make the snippet above work? I've already tried putting Thread.sleep(15000) (arbitrary duration) in onHandleIntent(...) after starting the task. Itseems to work.

But it definitely doesn't seem to be clean solution. Maybe there are even some serious problems with that.

Any better solution?

caw
  • 30,999
  • 61
  • 181
  • 291
  • 1
    What is stopping you from making a synchronous version of the same thing? – ianhanniballake Feb 23 '14 at 04:59
  • you can use bindservice for it . http://developer.android.com/guide/components/bound-services.html this will help me a lot . – mcd Feb 23 '14 at 05:04
  • @ianhanniballake: `MyOtherClass` is actually part of a third-party component that I rather don't like to change. The method in that class always delivers its results via callbacks, not synchronously. – caw Feb 23 '14 at 05:24
  • @mcd: Bound services are only for the use case where the `Service` reports back to another application component, usually an `Activity`, aren't they? – caw Feb 23 '14 at 05:47
  • yup but you can user service class instead sorry for not read your question properly – mcd Feb 23 '14 at 13:46
  • possible duplicate of [How do I keep the Thread of an IntentService alive?](http://stackoverflow.com/questions/11700288/how-do-i-keep-the-thread-of-an-intentservice-alive) – corsair992 May 26 '15 at 04:16

6 Answers6

19

Use the standard Service class instead of IntentService, start your asynchronous task from the onStartCommand() callback, and destroy the Service when you receive the completion callback.

The issue with that would be to correctly handle the destruction of the Service in the case of concurrently running tasks as a result of the Service being started again while it was already running. If you need to handle this case, then you might need to set up a running counter or a set of callbacks, and destroy the Service only when they are all completed.

corsair992
  • 3,050
  • 22
  • 33
  • 9
    Agreed. `IntentService` is not designed for this scenario. – CommonsWare Feb 23 '14 at 06:32
  • @CommonsWare why does the [google sample project](https://github.com/googlesamples/google-services/blob/master/android/gcm/app/src/main/java/gcm/play/android/samples/com/gcmquickstart/RegistrationIntentService.java) for GCM set up an http call in an IntentService (sendRegistrationToServer method). Surely 99.9% of people will want to wait for the response from their server when sending the gcm token right? Yet waiting for a callback from an http call inside an IntentService is not what IntentService is designed for. – Adam Johns Apr 08 '16 at 20:52
  • 2
    @AdamJohns: "Yet waiting for a callback from an http call inside an IntentService is not what IntentService is designed for" -- I don't know what you mean by "callback" in this case. Doing HTTP I/O is a perfectly reasonable thing for an `IntentService` to do, so long as it does so synchronously. If you have additional concerns in this area, I suggest that you ask a separate Stack Overflow question. – CommonsWare Apr 08 '16 at 21:01
  • @CommonsWare when I say callback I mean I want to send an http request to my server with the gcm token and the "callback" would be getting the response back from the server. See my question here: http://stackoverflow.com/q/36501842/1438339 – Adam Johns Apr 08 '16 at 21:16
14

I agree with corsair992 that typically you should not have to make asynchronous calls from an IntentService because IntentService already does its work on a worker thread. However, if you must do so you can use CountDownLatch.

public class MyIntentService extends IntentService implements MyCallback {
    private CountDownLatch doneSignal = new CountDownLatch(1);

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected final void onHandleIntent(Intent intent) {
        MyOtherClass.runAsynchronousTask(this);
        doneSignal.await();
    }

}

@Override
public void onReceiveResults(Object object) {
    doneSignal.countDown();
}

public interface MyCallback {

    public void onReceiveResults(Object object);

}

public class MyOtherClass {

    public void runAsynchronousTask(MyCallback callback) {
        new Thread() {
            public void run() {
                // do some long-running work
                callback.onReceiveResults(...);
            }
        }.start();
    }

}
Matt Accola
  • 4,090
  • 4
  • 28
  • 37
  • Are there any downsides to this? Still seems like a pretty straight forward option? – Kevin R Sep 15 '17 at 13:27
  • To make it more production quality you'd probably want to use something other than a new Thread, perhaps an Executor but the basic idea of using a CountDownLatch is pretty basic. – Matt Accola Sep 15 '17 at 19:18
6

If you are still looking for ways to use Intent Service for asynchronous callback, you can have a wait and notify on thread as follows,

private Object object = new Object();

@Override
protected void onHandleIntent(Intent intent) {
    // Make API which return async calback.

    // Acquire wait so that the intent service thread will wait for some one to release lock.
    synchronized (object) {
        try {
            object.wait(30000); // If you want a timed wait or else you can just use object.wait()
        } catch (InterruptedException e) {
            Log.e("Message", "Interrupted Exception while getting lock" + e.getMessage());
        }
    }
}

// Let say this is the callback being invoked
private class Callback {
    public void complete() {
        // Do whatever operation you want

        // Releases the lock so that intent service thread is unblocked.
        synchronized (object) {
            object.notifyAll();
        }   
    }
}
keerthy
  • 236
  • 1
  • 9
3

My favorite option is to expose two similar methods, for example:

public List<Dog> getDogsSync();
public void getDogsAsync(DogCallback dogCallback);

Then the implementation could be as follows:

public List<Dog> getDogsSync() {
    return database.getDogs();
}

public void getDogsAsync(DogCallback dogCallback) {
    new AsyncTask<Void, Void, List<Dog>>() {
        @Override
        protected List<Dog> doInBackground(Void... params) {
            return getDogsSync();
        }

        @Override
        protected void onPostExecute(List<Dog> dogs) {
            dogCallback.success(dogs);
        }
    }.execute();
}

Then in your IntentService you can call getDogsSync() because it's already on a background thread.

Matt Logan
  • 5,886
  • 5
  • 31
  • 48
1

You are doomed without changing MyOtherClass.

With changing that class you have two options:

  1. Make an synchronous call. IntentService is already spawning a background Thread for you.
  2. Return the newly created Thread in runAsynchronousTask() and call join() on it.
flx
  • 14,146
  • 11
  • 55
  • 70
0

I agree, it probably makes more sense to use Service directly rather than IntentService, but if you are using Guava, you can implement an AbstractFuture as your callback handler, which lets you conveniently ignore the details of synchronization:

public class CallbackFuture extends AbstractFuture<Object> implements MyCallback {
    @Override
    public void onReceiveResults(Object object) {
        set(object);
    }

    // AbstractFuture also defines `setException` which you can use in your error 
    // handler if your callback interface supports it
    @Override
    public void onError(Throwable e) {
        setException(e);
    }
}

AbstractFuture defines get() which blocks until the set() or setException() methods are called, and returns a value or raises an exception, respectively.

Then your onHandleIntent becomes:

    @Override
    protected final void onHandleIntent(Intent intent) {
        CallbackFuture future = new CallbackFuture();
        MyOtherClass.runAsynchronousTask(future);
        try {
            Object result = future.get();
            // handle result
        } catch (Throwable t) {
            // handle error
        }
    }
Tom Lubitz
  • 636
  • 6
  • 7