Ожидание асинхронного обратного вызова в 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
.
С изменением этого класса у вас есть два варианта:
- Сделайте синхронный вызов.
IntentService
уже нерест фоновой Thread
для вас. - Вернуть созданную
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
}
}