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.
4 Réponses :
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 :)
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.
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 code> 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.
À 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éthodeonStop ()
se termine avantonStart ()
, gardant le composant en vie plus longtemps que nécessaire.
@AlexanderWoodblock Certaines de ces questions sont-elles utiles? Avez-vous plus d'informations que vous pourriez donner?
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(); }
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 .