繁体   English   中英

取消注册在 Activity 上下文中注册的 BroadcastReceiver 会导致 IllegalArgumentException 崩溃

[英]Unregistering BroadcastReceiver registered in Activity context results in IllegalArgumentException crash

坠机是非常奇怪的一次。

onStart中,注册了存储在 Activity 字段中的 BroadcastReceiver。 onStop中,此 BroadcastReceiver 未注册。 当 BroadcastReceiver 注册成功时,我也将isRegistered字段设置为true ,在我注销接收器之前,我检查这个字段,看看我们是否需要

但是,在 Crashlytics 中,我看到有时这会失败,并且整个应用程序因IllegalArgumentExceptionReceiver not registered消息而崩溃,该消息源自android.app.LoadedApk#forgetReceiverDispatcher 考虑到我检查了标志,这很奇怪,对吧?

在我研究了处理注册/取消注册的ContextImplLoadedApk类并添加了一些基于反射的诊断之后,它变得更加神秘。 In short, on every crash like that I will extract the map of existing Context to BroadcastReceiver (see ContextImpl.java:1590 and LoadedApk.java:1361 ).

当它正常注销时,没有崩溃,我可以看到这样的 map:

com.mypackage.myapp.MyAppContext instance -> list of instances that are registered app wide
com.mypackage.myapp.MyActivity1 instance -> list of instances that are registered for Activity1
com.mypackage.myapp.MyActivity2 instance -> list of instances that are registered for Activity2
...

然而,如果发生崩溃,这个 map 看起来像这样:

com.mypackage.myapp.MyAppContext instance -> list of instances that are registered app wide

即没有注册我的BroadcastReceiver的活动,即使我从未调用unregisterReceiver

它只发生在一个特定的活动中,所以我的第一个猜测是这个活动以某种方式泄露, onDestroy生命周期被调用,活动条目从接收器 map 中删除,然后显然当我们尝试取消注册时接收器将不存在。 但是,如果是这样,那么为什么在onDestroy之后调用onStop呢? 为什么接收者列表完全是空的?

如果不是泄漏,那么什么会导致这种令人费解的行为? 我真的希望其他人经历过这样的奇怪事件,并且可以帮助我,因为现在我完全没有想法。

已编辑

只能说我感受到了痛苦。 为了我自己的问题,我刚刚找到了一个解决方法,但是,为了回答你的问题,我真的深入研究了文档,所以更老的问题甚至编写了简单的代码并自己检查了一些生命周期测试(虽然我已经找到了答案我错了)。 我找不到此事件发生的原因,但要修复它,我建议将 register/unRegister 包装在 try catch 块中:

private void registerBroadcastReceiver() {
    try {
        appUpdateReceiver = new AppUpdateReceiver();
        registerReceiver(appUpdateReceiver, appUpdateIntentFilter);
    } catch (IllegalArgumentException e) {
        // already registered
    }
}

private void unRegisterBroadCastReceiver() {
    try {
        unregisterReceiver(appUpdateReceiver);
    } catch (IllegalArgumentException e) {
        // already unregistered
    }
}

请提供一些代码,以便社区可以更深入地研究这个问题。 感谢您提及 LoadedApk class。

在 onStart 中,注册了存储在 Activity 字段中的 BroadcastReceiver。 在 onStop 中,这个 BroadcastReceiver 是未注册的。

onPause state 中可能会发生很多事情。 应用程序进程可能会被终止,它可能会在onCreate重新启动。

文档中引用

要停止接收广播,请调用 unregisterReceiver(android.content.BroadcastReceiver)。 当您不再需要接收器或上下文不再有效时,请务必取消注册接收器。

请注意在哪里注册和取消注册接收器,例如,如果您使用活动的上下文在 onCreate(Bundle) 中注册接收器,则应在 onDestroy() 中取消注册它以防止将接收器泄漏到活动上下文之外。 如果你在 onResume() 中注册了一个接收器,你应该在 onPause() 中取消注册它以防止它被多次注册(如果你不想在暂停时接收广播,这可以减少不必要的系统开销)。 不要在 onSaveInstanceState(Bundle) 中取消注册,因为如果用户移回历史堆栈,则不会调用它。

所以请分别在onResumeonPause中注册和注销。

让我知道这是否解决了它:)

您是否在任何时候都从与主应用程序线程不同的线程读取或写入isRegistered ,例如在不由主应用程序线程执行的回调中? 如果是这样,由于可能的 memory 一致性错误,这可能会导致读取过时的值和其他奇怪的行为。 There can be very weird behaviour if memory consistency is broken, see for instance http://jcip.net/ and http://jcip.net/listings/Holder.java . 另外,您是否在主应用程序线程中注册了BroadcastReceiver

您是否尝试过以各种方式检查生命周期以查看是否可以自己重现错误,例如将手机侧向并以这种方式导致生命周期更改,或者以其他方式强制生命周期更改?

调用unregisterReceiver后是否将isRegistered设置为 false ? 我想知道这是否相关,例如如果onStart ,注册成功,稍后onStop ,取消注册成功,稍后onStart但注册不成功,然后onStop尽管没有注册BroadcastReceiver由于isRegistered没有被重置。 我不知道这是否可能,但是您描述“当 BroadcastReceiver 成功注册时” ,所以我想知道是否可能存在问题。 注册不成功的处理。 我还想知道如果不是onStart中的所有分支都注册广播接收器,是否会发生这种情况。

我可以猜测以下链接可能是相关的,但是您已经使用变量来检查它是否已注册,所以我猜不是: https://developer.android.com/topic/libraries/architecture/lifecycle (不过,我不确定我是否喜欢或信任该文本中的措辞,它使用的示例包括回调)。

此外,不能保证组件在活动或片段停止之前启动。 如果我们需要执行长时间运行的操作,例如onStart()中的一些配置检查,则尤其如此。 这可能会导致onStop()方法在onStart()之前完成的竞争条件,从而使组件的存活时间超过所需时间。

我以这种方式完成了实现:

在 onDestroy 事件中释放接收者

@Override
public void onDestroy() {

    // ...releasing more objects

    LocalBroadcastManager.getInstance(this).unregisterReceiver(testReceiver);
    super.onDestroy();
}
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // ... more initializations

    initializeBroadcastReceivers();
}


private void initializeBroadcastReceivers()
{
    LocalBroadcastManager.getInstance(this)
      .registerReceiver(testReceiver, 
        new IntentFilter("testReceiver"));
}

private BroadcastReceiver testReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // some code
    }
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM