在Android的IntentService中等待异步回调

28

我有一个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();
    }

}

如何让上面的代码片段工作?我已经尝试在启动任务后在onHandleIntent(...)中放置Thread.sleep(15000)(任意持续时间)。它似乎有效。

但这肯定不是一个干净的解决方案。也许还有一些严重的问题。

有更好的解决方案吗?


1
你为什么不制作一个同步版本的相同功能呢? - ianhanniballake
你可以使用bindservice来实现。http://developer.android.com/guide/components/bound-services.html 这会对我很有帮助。 - mcd
@ianhanniballake:MyOtherClass实际上是第三方组件的一部分,我不太想更改它。该类中的方法始终通过回调传递其结果,而不是同步传递。 - caw
@mcd:绑定服务仅适用于“服务”向另一个应用程序组件(通常是“Activity”)报告的用例,不是吗? - caw
可以使用服务类而不是直接使用。抱歉没有正确阅读您的问题。 - mcd
可能是一个重复的问题,参考如何保持IntentService的线程活动? - corsair992
6个回答

19

请使用标准的Service类,而非IntentService类,从onStartCommand()回调函数开始执行异步任务,并在接收到完成回调函数后销毁Service

如果同时运行多个任务导致在Service已经运行时再次启动它,则需要正确处理Service的销毁。如果您需要处理这种情况,则可能需要设置一个运行计数器或一组回调函数,并仅在它们全部完成后销毁Service


9
同意。IntentService 并不适用于这种情况。 - CommonsWare
@CommonsWare为什么GCM的谷歌示例项目在IntentService中设置了一个http调用(sendRegistrationToServer方法)?当发送gcm令牌时,99.9%的人肯定希望等待来自其服务器的响应,对吧?但是,在IntentService内部等待http调用的回调并不是IntentService的设计目的。 - Adam Johns
2
@AdamJohns:“在 IntentService 中等待 http 调用的回调不是 IntentService 的设计目的”-- 我不知道你在这种情况下所说的“回调”是什么意思。如果一个 IntentService 同步地执行 HTTP I/O,那么这是完全合理的。如果您对此有其他疑虑,我建议您提出一个单独的 Stack Overflow 问题。 - CommonsWare
@CommonsWare 当我说回调时,我的意思是我想向我的服务器发送一个带有GCM令牌的HTTP请求,“回调”将是从服务器获取响应。请参见我的问题:http://stackoverflow.com/q/36501842/1438339 - Adam Johns

14

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

}

这个有什么缺点吗?看起来还是一个相当直接的选项? - Kevin R
为了使其更具生产质量,您可能希望使用除新线程之外的其他东西,例如Executor,但使用CountDownLatch的基本思想非常基础。 - Matt Accola

6

如果你仍然在寻找使用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();
        }   
    }
}

2
请您能否更详细地解释一下这个问题? - KOTIOS

3

我最喜欢的选项是暴露出两种类似的方法,例如:

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()因为它已经在后台线程上。

谢谢,这正是我用来解决这个问题的方法 :) - caw

1

如果不改变MyOtherClass,你注定会失败。

如果改变该类,你有两个选项:

  1. 进行同步调用。 IntentService已经为您生成了后台Thread
  2. runAsynchronousTask()中返回新创建的Thread并调用join()

0

我同意,直接使用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
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接