J'ai une IntentService
qui démarre une tâche asynchrone dans une autre classe et qui devrait alors attendre le résultat.
Le problème est que la variable IntentService
se terminera dès que la méthode onHandleIntent(...)
sera terminée, n'est-ce pas?
Cela signifie que, normalement, la IntentService
sera immédiatement arrêtée après le démarrage de la tâche asynchrone et ne sera plus là pour recevoir les résultats.
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();
}
}
Comment puis-je faire fonctionner l'extrait ci-dessus? J'ai déjà essayé de mettre Thread.sleep(15000)
(durée arbitraire) dans onHandleIntent(...)
après le démarrage de la tâche. Il semble fonctionner.
Mais cela ne semble certainement pas être une solution propre. Peut-être que cela pose même de graves problèmes.
Une meilleure solution?
Utilisez la classe standard Service
au lieu de IntentService
, démarrez votre tâche asynchrone à partir du rappel onStartCommand()
et détruisez la Service
lorsque vous recevez le rappel d'achèvement.
Le problème avec cela serait de gérer correctement la destruction de la Service
dans le cas de tâches simultanément exécutées à la suite du redémarrage de la Service
alors qu'elle était déjà en cours d'exécution. Si vous devez gérer ce cas, vous devrez peut-être configurer un compteur en cours d'exécution ou un ensemble de rappels, et ne détruire la Service
que lorsqu'ils seront tous terminés.
Je suis d'accord avec corsair992 pour dire que vous ne devriez généralement pas avoir à faire d'appels asynchrones à partir d'un IntentService, car IntentService fait déjà son travail sur un thread de travail. Cependant, si vous devez, vous pouvez utiliser 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();
}
}
Si vous recherchez toujours des moyens d'utiliser Intent Service pour les rappels asynchrones, vous pouvez attendre et notifier le processus comme suit:
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();
}
}
}
Mon option préférée est d'exposer deux méthodes similaires, par exemple:
public List<Dog> getDogsSync();
public void getDogsAsync(DogCallback dogCallback);
Ensuite, la mise en œuvre pourrait être la suivante:
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();
}
Ensuite, dans votre IntentService
, vous pouvez appeler getDogsSync()
car il se trouve déjà sur un thread en arrière-plan.
Vous êtes condamné sans changer MyOtherClass
.
En changeant cette classe, vous avez deux options:
IntentService
est déjà en train de créer un fond Thread
pour vous.Thread
nouvellement créée dans runAsynchronousTask()
et appeler join()
dessus.Je conviens qu'il est probablement plus logique d'utiliser Service
directement plutôt que IntentService
, mais si vous utilisez Guava , vous pouvez implémenter une AbstractFuture
comme gestionnaire de rappel, ce qui vous permet d'ignorer facilement les détails de la synchronisation:
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
définit get()
qui bloque jusqu'à l'appel des méthodes set()
ou setException()
et renvoie une valeur ou lève une exception, respectivement.
Alors votre onHandleIntent
devient:
@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
}
}