如何在广播接收器中使用"goAsync"?

29

背景

从Honeycomb(API 11)开始,Android具有一项功能,允许broadcastReceiver以异步方式运行,并为其提供大约10秒的时间,之后它会假定可以杀死其进程,使用一个称为“goAsync"的方法:

应用程序可以在onReceive(Context、Intent)中调用此方法,以使广播在返回该函数后保持活动状态。这不会改变对广播响应相对迅速的期望(10秒内完成),但允许实现将与其相关的工作移动到另一个线程中,以避免由于磁盘IO而导致主UI线程出现故障。

问题

我在很多地方搜索过,但没有找到任何关于如何使用它的示例或教程。

不仅如此,该方法返回一个 PendingIntent 实例,我不确定该怎么处理它:

返回代表活动广播结果的BroadcastReceiver.PendingResult。BroadcastRecord本身不再处于活动状态;所有数据和其他交互必须通过BroadcastReceiver.PendingResult API进行。一旦处理广播完成,必须调用PendingResult.finish()方法。

问题

如何使用这个方法?

返回的PendingIntent是什么,我该怎么处理它?

3个回答

47

您可以在此处找到简短的解释。

如果想要将 BroadcastReceiveronReceive() 方法内的处理移交给其他线程,则可以使用 goAsync()。该方法会使得 onReceive() 方法在新线程中完成。PendingResult 将传递到新线程,您需要调用 PendingResult.finish() 来通知系统可以回收该接收器。

例如:

final PendingResult result = goAsync();
Thread thread = new Thread() {
   public void run() {
      int i;
      // Do processing
      result.setResultCode(i);
      result.finish();
   }
};
thread.start();

4
从 onReceive 方法返回的总时间为 10 秒。这意味着在启动线程之前,您在 onReceive 方法中花费的时间将从最长的可运行线程时间 10 秒中减去。我没有尝试过这段代码,因为它已经存在。 - Broatian
8
setResult方法不是必需的,它只用于在有序广播期间在接收器之间传递数据。你可以省略它。 - Broatian
1
我不理解setResultCode的目的以及如何使用它。你会如何将其传递?另外,你尝试过这段代码吗? - android developer
2
有序广播会按顺序遍历每个已注册的广播接收器。顺序由意图过滤器的优先级属性定义。然后,每个接收器都可以使用像setResult()这样的方法将数据传递给下一个接收器,甚至可以完全停止广播。ACTION_NEW_OUTGOING_CALL是一个有序广播。您可以使用setResult来修改额外的EXTRA_PHONE_NUMBER,从而更改将要拨打的电话号码。 - Broatian
1
setResult方法接受一个整数、字符串和Bundle。在电话号码的情况下,您可以通过传递包含电话号码的Bundle来实现。这与原始问题非常不相关。问题是关于如何使用goAsync,而不是广播接收器的一般工作方式。如果已回答该部分,请标记为已解决。 - Broatian
显示剩余6条评论

11
在Kotlin中,您可以为BroadcastReceiver编写扩展函数:
/**
 * Run work asynchronously from a [BroadcastReceiver].
 */
fun BroadcastReceiver.goAsync(
    coroutineScope: CoroutineScope,
    dispatcher: CoroutineDispatcher,
    block: suspend () -> Unit
) {
    val pendingResult = goAsync()
    coroutineScope.launch(dispatcher) {
        block()
        pendingResult.finish()
    }
}

接下来,在您的广播接收器内,您可以执行以下操作:

class AlarmBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // Code here runs on the main thread

        goAsync(GlobalScope, Dispatchers.Default) {
            // The code here will run on the background by the default dispatcher on the global scope
            // If your code here touches the IO, then you can use Dispatchers.IO instead
        }
    }

对于参数,也许你有一个管理的协程作用域想要在这种情况下使用。不建议使用全局作用域,因为它不支持结构化并发。使用协程简化了代码,你可以使用特定的调度程序立即执行代码而无需等待。 - Mahmoud
是的,不建议使用它,因为它不支持结构化并发。我在这里使用它作为一个例子。https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc - Mahmoud
1
你写了代码,现在你说不建议使用它? - android developer
2
我建议将代码块包装在try finally中,以确保在最后始终调用 finish() - Kirill Rakhman
6
给未来的谷歌搜索者们一个提示,Ian Lake在Muzei应用程序中使用了GlobalScope,所以我认为这应该是可以的。参考链接为https://github.com/muzei/muzei/blob/b66437955dc71d544194170bb7cb710d123ca69c/extensions/src/main/java/com/google/android/apps/muzei/util/BroadcastReceiverExt.kt - Daniel Wilson
显示剩余2条评论

0

最好在try/catch块中使用它

fun BroadcastReceiver.goAsync(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> Unit
) {
    val pendingResult = goAsync()
    CoroutineScope(SupervisorJob()).launch(context) {
        try {
            block()
        } finally {
            pendingResult.finish()
        }
    }
}

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