別のクラスで非同期タスクを開始し、結果を待つ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(...)
に入れてみました。動作するようです。
しかし、それは間違いなくクリーンなソリューションではないようです。たぶんそれでいくつかの深刻な問題があります。
より良い解決策はありますか?
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();
}
}
非同期コールバックにインテントサービスを使用する方法を探している場合は、次のように待機して、スレッドで通知することができます。
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();
}
}
}
私のお気に入りのオプションは、たとえば次の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();
}
_
次に、IntentService
でgetDogsSync()
を呼び出すことができます。これは、すでにバックグラウンドスレッドにあるためです。
あなたはMyOtherClass
を変更せずに運命づけられています。
そのクラスを変更すると、2つのオプションがあります。
IntentService
はすでに背景Thread
を生成しています。Thread
をrunAsynchronousTask()
に返し、join()
を呼び出します。同意しますが、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()
を定義し、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
}
}
_