web-dev-qa-db-ja.com

AndroidのIntentServiceで非同期コールバックを待機しています

別のクラスで非同期タスクを開始し、結果を待つIntentServiceがあります。

問題は、IntentServiceonHandleIntent(...)メソッドの実行が完了するとすぐに完了することです。

つまり、通常、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(...)に入れてみました。動作するようです。

しかし、それは間違いなくクリーンなソリューションではないようです。たぶんそれでいくつかの深刻な問題があります。

より良い解決策はありますか?

28
caw

Serviceの代わりに標準のIntentServiceクラスを使用し、onStartCommand()コールバックから非同期タスクを開始し、完了コールバックを受け取ったらServiceを破棄します。

Serviceが既に実行されている間に再度開始された結果としてタスクが同時に実行された場合、Serviceの破棄を正しく処理することが問題になります。このケースを処理する必要がある場合は、実行中のカウンターまたはコールバックのセットをセットアップし、Serviceがすべて完了したときにのみ破棄する必要がある場合があります。

18
corsair992

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

}
12
Matt Accola

非同期コールバックにインテントサービスを使用する方法を探している場合は、次のように待機して、スレッドで通知することができます。

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

私のお気に入りのオプションは、たとえば次の2つの類似したメソッドを公開することです。

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

次に、IntentServicegetDogsSync()を呼び出すことができます。これは、すでにバックグラウンドスレッドにあるためです。

2
Matt Logan

あなたはMyOtherClassを変更せずに運命づけられています。

そのクラスを変更すると、2つのオプションがあります。

  1. 同期呼び出しを行います。 IntentServiceはすでに背景Threadを生成しています。
  2. 新しく作成したThreadrunAsynchronousTask()に返し、join()を呼び出します。
1
flx

同意しますが、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);
    }
}
_

AbstractFutureget()を定義し、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
        }
    }
_
0
Tom Lubitz