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

у меня есть 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(...) после запуска задач. Это должно сработать.

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

решение получше?

6 ответов


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

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


Я согласен с 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();
    }

}

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

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

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

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() потому что он уже находится в фоновом потоке.


вы обречены, не меняясь MyOtherClass.

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

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

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