Ожидание асинхронного обратного вызова в Android IntentService

У меня есть IntentService который запускает асинхронную задачу в другом классе и должен ждать результата.

Проблема в том, что IntentService завершит работу, как только метод onHandleIntent(...) закончен, правильно?

Это означает, что, как правило, IntentService немедленно отключится после запуска асинхронной задачи и больше не будет получать результаты.

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

}

Как я могу сделать сниппет выше работы? Я уже пытался поставить Thread.sleep(15000) (произвольная продолжительность) в onHandleIntent(...) после запуска задачи. Itseems работать.

Но это определенно не кажется чистым решением. Возможно, есть и некоторые серьезные проблемы.

Любое лучшее решение?

Ответ 1

Используйте стандартный класс Service вместо IntentService, запустите свою асинхронную задачу из обратного вызова onStartCommand() и уничтожьте Service когда вы получите обратный вызов завершения.

Проблема с этим заключается в том, чтобы правильно обрабатывать уничтожение Service в случае одновременных запущенных задач в результате повторного запуска Service во время его работы. Если вам нужно обработать этот случай, вам может потребоваться настроить счетчик запусков или набор обратных вызовов и уничтожить Service только тогда, когда все будет завершено.

Ответ 2

Я согласен с corsair992, что обычно вам не нужно делать асинхронные вызовы от IntentService, потому что IntentService уже выполняет свою работу над рабочим потоком. Однако, если вы должны это сделать, вы можете использовать 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();
    }

}

Ответ 3

Если вы все еще ищете способы использования Intent Service для асинхронного обратного вызова, вы можете ожидать и уведомлять о потоке следующим образом:

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

Ответ 4

Мой любимый вариант состоит в том, чтобы выставить два похожих метода, например:

public List<Dog> getDogsSync();
public void getDogsAsync(DogCallback dogCallback);

Тогда реализация может быть следующей:

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

Затем в вашем IntentService вы можете вызвать getDogsSync() потому что он уже находится в фоновом потоке.

Ответ 5

Вы обречены без изменения MyOtherClass.

С изменением этого класса у вас есть два варианта:

  1. Сделайте синхронный вызов. IntentService уже нерест фоновой Thread для вас.
  2. Вернуть созданную Thread в runAsynchronousTask() и вызвать join() на нем.

Ответ 6

Я согласен, вероятно, имеет смысл использовать Service напрямую, а не IntentService, но если вы используете Guava, вы можете реализовать AbstractFuture качестве обработчика обратного вызова, что позволяет легко игнорировать детали синхронизации:

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 определяет get() который блокирует до тех пор, пока не будут setException() методы set() или setException(), и возвращает значение или вызывает исключение, соответственно.

Затем ваш onHandleIntent становится:

    @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
        }
    }