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?

23
demandé sur caw 2014-02-23 08:54:33

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.

17
répondu corsair992 2014-02-23 06:24:46

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

}
10
répondu Matt Accola 2015-02-26 15:08:58

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();
        }   
    }
}
6
répondu keerthy 2014-04-11 02:39:43

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.

2
répondu Matt Logan 2014-12-18 17:53:29

vous êtes condamné sans changer MyOtherClass.

avec la modification de cette classe vous avez deux options:

  1. faites un appel synchrone. IntentService est déjà fraie un arrière-plan Thread pour vous.
  2. retourner le nouveauThreadrunAsynchronousTask() et appeler join().
1
répondu flx 2014-02-23 06:17:09

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
        }
    }
0
répondu Tom Lubitz 2016-08-02 22:49:37