[英]Unregistering a BroadcastReceiver from a non-Activity on Android
[英]Unregistering BroadcastReceiver registered in Activity context results in IllegalArgumentException crash
坠机是非常奇怪的一次。
在onStart
中,注册了存储在 Activity 字段中的 BroadcastReceiver。 在onStop
中,此 BroadcastReceiver 未注册。 当 BroadcastReceiver 注册成功时,我也将isRegistered
字段设置为true
,在我注销接收器之前,我检查这个字段,看看我们是否需要
但是,在 Crashlytics 中,我看到有时这会失败,并且整个应用程序因IllegalArgumentException
与Receiver not registered
消息而崩溃,该消息源自android.app.LoadedApk#forgetReceiverDispatcher
。 考虑到我检查了标志,这很奇怪,对吧?
在我研究了处理注册/取消注册的ContextImpl
和LoadedApk
类并添加了一些基于反射的诊断之后,它变得更加神秘。 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) 中取消注册,因为如果用户移回历史堆栈,则不会调用它。
所以请分别在onResume
和onPause
中注册和注销。
让我知道这是否解决了它:)
您是否在任何时候都从与主应用程序线程不同的线程读取或写入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.