簡體   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