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 IntentSer
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();
}
}
}
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.
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();
}
}
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
}
}
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.
You are doomed without changing MyOtherClass
.
With changing that class you have two options:
IntentService
is already spawning a background Thread
for you.Thread
in runAsynchronousTask()
and call join()
on it.