1
votes

La désinscription de BroadcastReceiver enregistré dans le contexte d'activité entraîne un plantage d'IllegalArgumentException

Le crash est assez étrange.

Dans onStart , BroadcastReceiver qui est stocké dans le champ Activity est enregistré. Dans onStop , ce BroadcastReceiver n'est pas enregistré. Lorsque BroadcastReceiver a été enregistré avec succès, j'ai également défini le champ isRegistered sur true , et avant de désinscrire le récepteur, je vérifie ce champ pour voir si nous en avons besoin

Cependant, dans Crashlytics, je vois que parfois cela échoue et que toute l'application plante avec IllegalArgumentException avec le message Receiver non enregistré , qui provient de android.app.LoadedApk #forgetReceiverDispatcher . Ce qui est assez étrange, étant donné que je vérifie l'indicateur, n'est-ce pas?

Après avoir examiné les classes ContextImpl et LoadedApk qui gèrent l'inscription / désinscription et ajouté certains diagnostics basés sur la réflexion, il est devenu encore plus mystérieux. En bref, à chaque crash comme celui-là, je vais extraire la carte du Context existant vers BroadcastReceiver (voir ContextImpl.java:1590 et LoadedApk.java:1361 ).

Lorsqu'il se désenregistre normalement, sans plantage, je peux voir une carte comme celle-ci:

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

Cependant, en cas de plantage, cette carte ressemble à ceci:

XXX

ie il n'y a aucune activité dans laquelle mon BroadcastReceiver est enregistré, même si je n'ai jamais appelé unregisterReceiver !

Cela ne se produit que dans une activité spécifique, donc ma première Je suppose que cette activité est divulguée d'une manière ou d'une autre, le cycle de vie de onDestroy est appelé, l'entrée d'activité est supprimée de la carte des récepteurs, et alors évidemment le récepteur ne sera pas là lorsque nous essayons de l'annuler. Cependant, si tel est le cas, pourquoi est-ce que onStop est appelé après onDestroy alors? Et pourquoi alors la liste des destinataires est complètement vide?

Et si ce n'est pas la fuite, qu'est-ce qui peut conduire à un comportement aussi déroutant? J'espère vraiment que quelqu'un d'autre a vécu un événement aussi étrange et pourra peut-être m'aider, car pour le moment, je n'ai plus d'idées.


8 commentaires

Voir si l'un des threads existants aide? stackoverflow.com/questions/32259429/… < / a>, stackoverflow.com/questions/29542941/… , stackoverflow.com/questions/4978167/...


@TarunLalwani J'aurais aimé que ce soit aussi simple qu'un de ces fils. Malheureusement, ce sont des cas triviaux que j'ai déjà vérifiés en premier.


Cela se produit-il sur des appareils ou une marque spécifiques?


@TarunLalwani non, cela se passe sur différentes versions d'Android (à partir de 8, au moins et jusqu'à 10), et les marques sont très différentes - il y a des appareils Samsung, Huawei, même des Pixel ...


Pourriez-vous aussi ajouter du code?


pouvez-vous envoyer votre code à mon adresse e-mail? Peut-être je peux vous aider


@AlexanderWoodblock Avez-vous regardé ma réponse? Cela vous a-t-il été utile?


Cette question est plutôt abstraite. Veuillez consulter: Comment créer un exemple minimal et reproductible .


4 Réponses :


0
votes

Dans onStart, BroadcastReceiver qui est stocké dans le champ Activité est inscrit. Dans onStop, ce BroadcastReceiver n'est pas enregistré.

Beaucoup de choses peuvent se produire dans l'état onPause . Le processus d'application peut être tué et redémarrer à onCreate .

Citations des documents

Pour arrêter de recevoir des émissions, appelez unregisterReceiver (android.content.BroadcastReceiver). Assurez-vous de désenregistrez le récepteur lorsque vous n'en avez plus besoin ou que le contexte est n'est plus valide.

Soyez conscient de l'endroit où vous vous inscrivez et désenregistrez le destinataire, car exemple, si vous enregistrez un récepteur dans onCreate (Bundle) à l'aide du le contexte de l'activité, vous devez l'annuler dans onDestroy () pour éviter la fuite du récepteur hors du contexte d'activité. Si vous enregistrez un récepteur dans onResume (), vous devez le désinscrire dans onPause () pour éviter de l'enregistrer plusieurs fois (si vous ne souhaitez pas recevoir diffuse en pause, ce qui peut réduire le système inutile aérien). Ne désinscrivez pas dans onSaveInstanceState (Bundle), car ceci n'est pas appelé si l'utilisateur revient dans la pile d'historique.

Veuillez donc vous inscrire et vous désinscrire respectivement dans onResume et onPause .

Faites-moi savoir si cela résout le problème :)


0 commentaires

1
votes

Disons simplement que j'ai ressenti la douleur. Pour mon propre problème, je viens de trouver une solution, mais pour répondre à votre question, j'ai vraiment plongé profondément dans la documentation et des questions tellement plus anciennes ont même écrit du code simple et vérifié moi-même des tests de cycle de vie (dans lesquels je pensais avoir trouvé la réponse qui J'avais tort). Je n'ai pas pu trouver la raison pour laquelle cet incident se produit, mais pour le résoudre, je suggère d'envelopper le registre / unRegister dans un bloc 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
    }
}

Veuillez fournir du code afin que la communauté peut approfondir cette question. Et merci d'avoir mentionné la classe LoadedApk.


6 commentaires

Je ne suis pas certain, mais êtes-vous sûr que votre réponse n'est pas fausse? Vous êtes-vous assuré qu'il n'y a pas deux instances d'Activité2 dans votre pile d'activités et que ce n'est pas la raison pour laquelle onStop n'est apparemment pas appelé? Cela expliquerait également pourquoi vous avez Activity2: onDestroy deux fois. Avez-vous des références API pour votre affirmation selon laquelle "onStop peut être appelé même si onStart ne l'a pas fait"?


Même s'il y a 2 instances d'Activity2, dont je doute, l'une d'elles n'a pas appelé onStart.


Mais vous ne montrez pas non plus la trace entière, la trace commence par "Activity2: onPause (Activity2 onBackpressed)".


Et si une instance d'Activity2 est créée, puis est immédiatement détruite, elle passe de onCreate à onDestroy , et cela correspond parfaitement au fait qu'elle n'appelle ni onStart ou onStop .


Avant cela, il y a une instance d'Activity2 (en raison de l'appel de startActivity à partir d'Activity1) et son onStart a été appelé, je l'ai trouvé non pertinent, je n'ai donc pas montré tous les journaux.


Oui, vous avez raison, je viens d'ajouter un hashcode aux journaux, ces journaux finaux sont onStop et onDestroy de la première instance d'Activity2. Merci. Je vais modifier / supprimer la réponse sous peu.



0
votes

À un moment donné, lisez-vous ou écrivez-vous isRegistered vers / depuis un thread différent du thread principal de l'application, par exemple dans un rappel qui n'est pas exécuté par le thread principal de l'application? Si tel est le cas, en raison d'éventuels bogues de cohérence de la mémoire, cela peut entraîner la lecture de valeurs périmées et d'autres comportements étranges. Il peut y avoir un comportement très étrange si la cohérence de la mémoire est rompue, voir par exemple http://jcip.net/ et < a href = "http://jcip.net/listings/Holder.java" rel = "nofollow noreferrer"> http://jcip.net/listings/Holder.java . En outre, enregistrez-vous le BroadcastReceiver dans le thread principal de l'application?

Définissez-vous isRegistered sur false après avoir appelé unregisterReceiver ? Je me demande si cela pourrait être pertinent, par exemple si onStart est appelé, l'inscription est réussie, plus tard onStop est appelé, la désinscription est réussie, plus tard onStart < / code> est appelé mais l'enregistrement échoue, puis onStop est appelé même si aucun BroadcastReceiver n'est enregistré car isRegistered n'a pas été réinitialisé. Je ne sais pas si cela est possible, mais vous décrivez "Quand BroadcastReceiver a été enregistré avec succès" , donc je me demande s'il pourrait y avoir un problème reg. traitement des inscriptions infructueuses. Je me demande également si cela pourrait arriver si toutes les branches de votre onStart n’enregistrent pas le récepteur de diffusion.

Je suppose que le lien suivant pourrait être pertinent, mais vous utilisez déjà une variable pour vérifier si elle a été enregistrée ou non, donc je suppose que non: https://developer.android.com/topic/libraries/architecture/lifecycle (cependant, je ne sais pas si j'aime ou si j'aime le libellé dans ce texte, l'exemple qu'il utilise inclut un rappel).

De plus, il n'y a aucune garantie que le composant démarre avant que l'activité ou le fragment ne soit arrêté. Cela est particulièrement vrai si nous devons effectuer une opération de longue durée, comme une vérification de la configuration dans onStart () . Cela peut provoquer une condition de concurrence où la méthode onStop () se termine avant onStart () , gardant le composant en vie plus longtemps que nécessaire.


1 commentaires

@AlexanderWoodblock Certaines de ces questions sont-elles utiles? Avez-vous plus d'informations que vous pourriez donner?



0
votes

J'ai fait l'implémentation de cette manière:

Libération du récepteur dans l'événement 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
    }
};
@Override
public void onDestroy() {

    // ...releasing more objects

    LocalBroadcastManager.getInstance(this).unregisterReceiver(testReceiver);
    super.onDestroy();
}


0 commentaires