web-dev-qa-db-fra.com

En attente de rappel asynchrone dans Android IntentService

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?

26
caw

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.

18
corsair992

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();
    }

}
10
Matt Accola

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();
        }   
    }
}
6
keerthy

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.

2
Matt Logan

Vous êtes condamné sans changer MyOtherClass.

En changeant cette classe, vous avez deux options:

  1. Faire un appel synchrone. IntentService est déjà en train de créer un fond Thread pour vous.
  2. Renvoyer la Thread nouvellement créée dans runAsynchronousTask() et appeler join() dessus.
1
flx

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
        }
    }
0
Tom Lubitz