En attente d'un rappel asynchrone dans IntentService D'Android
j'ai un IntentService
qui commence une tâche asynchrone dans une autre classe et devrait ensuite attendre le résultat.
le problème est que Le IntentService
terminera dès que le onHandleIntent(...)
la méthode a fini de courir, Non?
cela signifie, normalement, le IntentService
arrêter immédiatement après le démarrage de la tâche asynchrone et ne seront 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 le snippet ci-dessus? J'ai déjà essayé mettre Thread.sleep(15000)
(durée arbitraire) dans onHandleIntent(...)
après avoir commencé la tâche. Itseems de travail.
mais il ne semble certainement pas être la solution propre. Peut-être qu'il y a même de sérieux problèmes avec ça.
une meilleure solution?
6 réponses
utilisez la norme Service
classe au lieu de IntentService
, commencez votre tâche asynchrone à partir du onStartCommand()
rappel, et de détruire l' Service
lorsque vous recevez le rappel d'achèvement.
Le problème avec ce serait de gérer correctement la destruction de l' Service
dans le cas de l'exécution simultanée de tâches à la suite de la Service
être relancé alors qu'il était déjà en cours d'exécution. Si vous avez besoin de gérer cette affaire, alors vous pourriez avoir besoin de mettre en place un compteur courant ou un ensemble de rappels, et de détruire l' Service
seulement, quand ils sont tous terminés.
je suis d'accord avec corsair992 que d'habitude vous ne devriez pas avoir à faire des appels asynchrones à partir D'un IntentService parce Qu'IntentService fait déjà son travail sur un fil ouvrier. Toutefois, si vous le faire, 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 êtes toujours à la recherche de façons d'utiliser le Service intention pour un rappel asynchrone, vous pouvez avoir un wait and notify sur le thread 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 les deux méthodes similaires, par exemple:
public List<Dog> getDogsSync();
public void getDogsAsync(DogCallback dogCallback);
Ensuite, la mise en œuvre pourrait être comme suit:
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();
}
Puis dans votre IntentService
vous pouvez appeler getDogsSync()
parce que c'est déjà sur un fil d'arrière-plan.
vous êtes condamné sans changer MyOtherClass
.
avec la modification de cette classe vous avez deux options:
- faites un appel synchrone.
IntentService
est déjà fraie un arrière-planThread
pour vous. - retourner le nouveau
Thread
runAsynchronousTask()
et appelerjoin()
.
je suis d'accord, il a probablement fait plus de sens d'utiliser Service
directement plutôt que IntentService
, mais si vous utilisez Goyave, vous pouvez implémenter un AbstractFuture
comme votre gestionnaire de rappel, qui vous permet commodément d'ignorer 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'à la set()
ou setException()
méthodes sont appelées, et renvoie une valeur ou lève une exception, respectivement.
Puis 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
}
}