Flutter: Impossible d'appeler une méthode de canal de plate-forme à partir d'un autre isolat

Créé le 5 janv. 2018  ·  133Commentaires  ·  Source: flutter/flutter

Lorsque j'essaie d'invoquer une méthode de canal de plate-forme à partir d'un isolat généré personnalisé, l'application se bloque gravement (à la fois sur iOS et Android). J'essaie de comprendre si c'est prévu ou non.

Si ce n'est pas le cas, cela vaut probablement la peine de le mentionner quelque part.

Quoi qu'il en soit, je pense que cela peut potentiellement être une forte limitation. Existe-t-il un moyen de pouvoir appeler des plugins de plate-forme à partir d'un isolat secondaire ?

P5 annoyance crowd gold engine framework passed first triage plugin crash new feature

Commentaire le plus utile

J'ai divers cas d'utilisation, par exemple:

  • Téléchargez et analysez une énorme quantité de données (les méthodes dart:convert ne sont pas asynchrones), puis stockez-la dans une base de données sqlite à l'aide du plug-in sqflite (qui utilise des liaisons de plate-forme);
  • Prérécupérer et analyser les données avant de les servir à l'interface utilisateur ;
  • Chiffrer/déchiffrer et lire/écrire des données depuis/vers un fichier (les opérations de chiffrement sont effectuées via un canal de méthode afin d'utiliser les bibliothèques de sécurité de la plate-forme) ;

En général, il peut arriver que vous utilisiez des dépendances qui déclarent certaines méthodes publiques dont vous ne savez pas vraiment si elles utiliseront éventuellement du code spécifique à la plate-forme pour accomplir leur travail (je pense à flutterfire par exemple) ; les utiliser à mon humble avis devrait être plus un détail de mise en œuvre qui peut changer avec le temps au lieu de quelque chose d'écrit dans la pierre.

Votre solution de contournement semble correcte pour le moment et relativement simple à mettre en œuvre, car les données sont déjà codées de manière à être transmises au canal de méthode, et pour cette raison passeront facilement également via un port isolé, mais je suppose que les performances seraient sous-optimales .

Cependant, je me suis un peu bloqué lors de l'implémentation : pouvez-vous fournir un exemple d'un moyen simple d'invoquer une méthode statique sur l'isolat principal à partir d'un isolat secondaire ?

Merci

Tous les 133 commentaires

cc @mravn-google pour le triage

Le code du moteur déréférence null et se bloque lors de la tentative d'envoi d'un message de plate-forme à partir de l'isolat secondaire. Je n'ai pas encore identifié exactement où; Je reçois une pierre tombale, mais j'ai besoin d'apprendre à interpréter une telle chose.

Comme solution de contournement (maladroite), l'isolat secondaire pourrait demander au principal d'envoyer le message.

@sroddy Puis-je vous demander ce que vous essayez d'accomplir avec l'isolat secondaire ?

J'ai divers cas d'utilisation, par exemple:

  • Téléchargez et analysez une énorme quantité de données (les méthodes dart:convert ne sont pas asynchrones), puis stockez-la dans une base de données sqlite à l'aide du plug-in sqflite (qui utilise des liaisons de plate-forme);
  • Prérécupérer et analyser les données avant de les servir à l'interface utilisateur ;
  • Chiffrer/déchiffrer et lire/écrire des données depuis/vers un fichier (les opérations de chiffrement sont effectuées via un canal de méthode afin d'utiliser les bibliothèques de sécurité de la plate-forme) ;

En général, il peut arriver que vous utilisiez des dépendances qui déclarent certaines méthodes publiques dont vous ne savez pas vraiment si elles utiliseront éventuellement du code spécifique à la plate-forme pour accomplir leur travail (je pense à flutterfire par exemple) ; les utiliser à mon humble avis devrait être plus un détail de mise en œuvre qui peut changer avec le temps au lieu de quelque chose d'écrit dans la pierre.

Votre solution de contournement semble correcte pour le moment et relativement simple à mettre en œuvre, car les données sont déjà codées de manière à être transmises au canal de méthode, et pour cette raison passeront facilement également via un port isolé, mais je suppose que les performances seraient sous-optimales .

Cependant, je me suis un peu bloqué lors de l'implémentation : pouvez-vous fournir un exemple d'un moyen simple d'invoquer une méthode statique sur l'isolat principal à partir d'un isolat secondaire ?

Merci

@sroddy Merci d'avoir fourni les informations de base.

À propos de l'appel de méthodes statiques sur l'isolat principal : il n'est pas directement pris en charge et doit être implémenté à l'aide de ports. Ce paquet peut fournir cette fonctionnalité, par exemple here . Je ne l'ai pas essayé moi-même cependant.

Mais nous devrions rechercher ici une solution plus générale, une solution qui fonctionne également lorsque la communication du canal de la plate-forme se fait dans le cadre de la mise en œuvre d'un plugin ou d'une autre bibliothèque.

Nous n'avons pas encore tout à fait les crochets pour cela, mais si nous pouvons supposer un instant que vous avez un ensemble connu de noms de canaux qui doivent être utilisés à partir de l'isolat secondaire et non du principal, vous devriez pouvoir génériquement reconfigurez vous-même la gestion des messages de la plate-forme pour ces canaux, en implémentant de manière transparente le transfert nécessaire des messages binaires et des réponses entre vos deux isolats. Les plugins ne connaîtraient jamais la différence.

Solution esquissée ci-dessous.

Supposons que vous configurez les ports M1 et R1 sur votre isolat principal et M2, R2 sur votre isolat secondaire. M pour message, R pour réponse.

  • Dans votre isolat secondaire, utilisez BinaryMessages.setMockMessageHandler pour chaque canal pour transférer les messages de la plate-forme vers M1 (de manière transparente vers le plug-in qui utilise BinaryMessage.send avec ce canal). Stockez les rappels de réponse et configurez R2 avec un gestionnaire qui appelle le bon à la réception de la réponse pour le message. Configurez M2 avec un gestionnaire qui transmet à BinaryMessages.handlePlatformMessage . Implémentez-y le rappel de réponse pour transférer les réponses à R1.
  • Symétriquement dans votre isolat principal. Configurez M1 avec un gestionnaire qui transfère les messages pour la plate-forme à BinaryMessages.send . Définissez un rappel de réponse qui transfère les réponses de la plateforme vers R2. Appelez également BinaryMessages.setMessageHandler pour chacun des canaux afin de configurer un gestionnaire de messages entrants de la plate-forme qui les transmet à M2. Stockez les rappels de réponse et configurez R1 avec un gestionnaire qui appelle le bon à la réception d'une réponse de l'isolat secondaire.

@Hixie Je suggère de passer à la prochaine étape. Une solution de contournement semble exister. Une bonne solution nécessitera un travail de conception et d'itération d'API.

J'ai le même problème, des nouvelles sur ce problème ?

@mravn-google Avez-vous des nouvelles à ce sujet ? J'ai rencontré le même problème (j'ai besoin de générer, par exemple, une paire de clés RSA 2048 côté plate-forme, ce qui prend un certain temps sur les appareils plus anciens ou les données de cryptage/décryptage). Je préfère éviter de créer des threads ou des services car cela doublerait mon travail côté plate-forme puisqu'il doit être implémenté à la fois pour les plates-formes Android et iOS. Avons-nous un moyen simple d'exécuter ces opérations spécifiques à la plate-forme de manière asynchrone à partir de Dart ? Votre solution de contournement semble être bonne, mais il semble que j'ai quelques problèmes avec l'implémentation car je suis assez nouveau pour Flutter et Dart (je ne sais pas comment configurer un gestionnaire qui transfère les messages pour un Isolate et ainsi de suite), désolé à propos de ça.

J'ai le même problème.

J'ai besoin de générer un PDF du côté Android/iOS, ce qui prend un certain temps et la méthode compute(...) plante l'application si j'exécute un appel à partir de là.

J'ai eu ce problème, et maintenant je suis bloqué.

Je veux lire accessToken, à partir des préférences partagées, dans l'isolat d'arrière-plan, même si l'application n'est pas au premier plan (l'isolat principal n'est peut-être pas en cours d'exécution, je ne suis pas très sûr).

Alors, quelqu'un peut-il me dire une solution pour lire les données persistantes à partir de l'isolat d'arrière-plan.
J'ai vraiment des ennuis maintenant.

/cc @bkonyi Cela semble lié à quelque chose sur lequel vous travaillez.

J'ai le même problème. notre base de produits sur CPU-bound. nous devons utiliser la méthode de la plate-forme dans l'isolat. Pourriez-vous s'il vous plaît nous dire une autre façon de résoudre ce problème?

@Thomson-Tsui J'ai eu un problème similaire, et sur la base d'une distillation du travail de @bkonyi et de son échantillon FlutterGeofencing, j'ai réussi à faire fonctionner quelque chose dans un isolat que j'utilise avec flutter_blue et d'autres plugins. C'est assez non testé, mais s'il vous plaît, essayez-le.

J'ai le même problème, je pense que c'est important

Quelqu'un at-il résolu ce problème?

J'ai besoin de lire de l'audio à des intervalles variables en arrière-plan. La façon la plus simple de le faire serait d'avoir un Isolate séparé pour gérer l'audio, mais sans cette fonctionnalité, il faudra un travail vraiment étrange avec les ports.

Je suppose que lorsque vous exécutez un isolat à partir de fléchettes, les bibliothèques natives ne sont pas liées.

Par exemple, lors de l'exécution du code Flutter à partir de Java, cette méthode est appelée :

   private native void nativeRunBundleAndSnapshotFromLibrary (
       long nativePlatformViewId,
       <strong i="7">@NonNull</strong> String [] prioritizedBundlePaths,
       <strong i="8">@Nullable</strong> String entrypointFunctionName,
       <strong i="9">@Nullable</strong> String pathToEntrypointFunction,
       <strong i="10">@NonNull</strong> AssetManager manager
   );

Dans lequel toute la magie se produit)

Ceci est vérifié par le fait que si vous créez plusieurs FlutterNativeView, et à chaque run :

  public void runFromBundle (FlutterRunArguments args)

puis nous obtenons quelques "isolats de fléchettes"

Nous sommes également confrontés à ce problème. Nous devons collecter des données à partir d'isolats d'arrière-plan (par exemple, emplacement, bruit, etc.) qui reposent tous sur des canaux de plate-forme. @mravn-google - des nouvelles sur une mise à jour de l'API qui pourrait résoudre ce problème ?

C'est vraiment un problème sérieux dans les applications d'outils ou de médias.
Le fil de plate-forme n'est pas un moyen facile.

J'ai également rencontré le même problème et je suis bloqué en ce moment.

+1 (mon projet a aussi cruellement besoin de canaux de plate-forme dans les isolats)

J'ai besoin de rendre des images en arrière-plan. Cela doit être fait dans un isolat car chaque image prend quelques secondes et bloque l'interface utilisateur si elle est effectuée dans le thread principal. Je suis bloqué au premier appel natif de dart:ui.PictureRecorder et je suppose que chaque appel graphique (qui utilise des fonctions natives) ne fonctionnera pas non plus. Comme j'utilise beaucoup de fonctions dans le canevas, il serait pénible de travailler avec des ports de rappel.

Une solution est très appréciée.

+1 J'ai eu ce problème

Il semble que @mravn-google ait cessé de travailler pour Google à Aarhus. Je me demande s'il y a quelqu'un d'autre de l'équipe Google Flutter qui se penche sur cela. IMO, il s'agit d'un événement majeur pour une utilisation sérieuse de Flutter pour la construction d'applications Flutter résilientes et robustes....

Peut-être que @sethladd peut donner un statut ?

+1

des mises à jour à ce sujet ?

+1

Le jour où vous rencontrez un bogue que vous savez résoudre par vous-même, mais attendez seulement est le dernier espoir jusqu'à ce que je devienne ingénieur flutter.

Nous avons besoin d'une explication plus détaillée de la solution de contournement pour les nouveaux utilisateurs de flutter/dart. Je n'ai commencé à utiliser des isolats qu'il y a deux jours, et flutter/dart la semaine dernière, et il semble que je vais devoir utiliser le thread principal pour certaines tâches très laborieuses, ce qui fait de l'apprentissage du flutter une mauvaise solution. Sans multithreading utilisable, je ferais mieux d'apprendre Kotlin et de créer deux fois mes applications. J'espère que vous pourrez tous rassembler quelque chose d'intelligible pour les débutants, j'aimerais vraiment pouvoir justifier l'apprentissage du flutter auprès de mes employeurs afin que je puisse l'utiliser au travail.
Sinon ici, peut-être sur StackOverflow, j'ai posté une question ici : https://stackoverflow.com/q/57466952/6047611

+1 j'espère que ça sera vite réparé

+1 Mon projet a besoin de canaux de plate-forme dans des isolats.

J'ai rencontré ce même problème et j'ai créé un package ( Isolate Handler ) en utilisant une solution de contournement similaire à celle publiée par @mravn-google. Cependant, Isolate Handler vous permet d'utiliser des canaux à la fois de l'isolat principal et d'autres isolats. Il prend en charge les appels directs MethodChannel depuis les isolats, mais actuellement les flux EventChannel ne sont pas pris en charge. J'examinerai la possibilité d'ajouter un support pour eux s'il s'avère que c'est quelque chose dont les gens ont besoin.

L'un des inconvénients de la solution de contournement est la nécessité de fournir au gestionnaire d'isolement des noms de canaux. Ce n'est pas un problème dans mon projet car j'écris moi-même mon code natif, mais sinon vous devez parcourir le code source de tout plugin que vous utilisez pour les trouver.

Une autre option qui a déjà été publiée ci-dessus est FlutterIsolate , qui utilise une approche différente et mérite également d'être examinée.

Espérons que les développeurs de Flutter fourniront bientôt une solution complète et appropriée à ce problème.

Éditer:

J'ai finalement résolu mon problème plus proprement en démarrant l'isolat du côté natif à la place en utilisant une approche similaire au plugin Flutter first-party Android Alarm Manager .

La seule chose que j'avais à faire était de convertir mon code natif en plugins afin qu'ils puissent être enregistrés avec l'application native, ce qui était une migration relativement indolore. Si vous utilisez des plugins de pub.dev, c'est encore plus simple car ils devraient fonctionner de manière transparente.

+1 une mise à jour ici ?

+1

Chose intéressante que je remarque ici:
Plusieurs plugins semblent être écrits avec l'hypothèse que l'appel de canal de méthode transmet des messages de Dart à un thread d'arrière-plan ; dans le même ordre d'idées, certaines personnes très confuses ont suivi cet exemple de géorepérage bg et se demandent maintenant comment revenir sur le fil principal ...
Ce code de plugin ne causera pas de "jank" s'il exécute une tâche courte, mais il s'exécute par défaut sur le thread de la plate -forme, ce qu'on appelait auparavant "le thread de l'interface utilisateur" sur Android ; cela peut absolument entraîner la suppression de gestes pendant une charge de travail élevée, et je comprends que cela bloquerait également tous les autres canaux de messagerie.
Vous êtes alors à la merci de l'ingénierie de l'auteur du plugin ; ce n'est que s'ils ont déplacé leur travail lourd vers un autre thread dans le code natif que vous exécuterez du code qui attend sur les canaux de méthode ; ces canaux de messages doivent s'exécuter sur le thread de la plate-forme.
Bien que je comprenne le raisonnement que le bon chinmaygarde rapporte ici , convenant que :

  • la plupart des appels d'API de plate-forme sont rapides et doivent souvent être effectués sur le thread principal ;
  • il est plus logique de tenir compte des threads dans le code natif
  • retrouver le chemin du "thread principal" dans un remplacement de méthode de framework est évidemment déroutant pour les personnes habituées à Android Java

    • Je ne peux qu'imaginer comment cela se multiplierait lors de leurs premiers appels d'API de plate-forme uniquement.

Je pense que l'architecture résultante a ouvert un anti-modèle significatif par défaut pour les auteurs de plugins ; à première vue, je pense que même le plugin firebase ml utilise le thread de la plate-forme pour son traitement.
J'aimerais donc qu'il y ait une sorte d'avertissement fort qui serait donné aux personnes qui soulèvent des objets lourds ; peut-être quelque chose dans l'exemple d'application par défaut qui génère des erreurs à l'écran si les appels du canal de message prennent plus de 20 ms pour revenir, peut-être quelque chose de plus visible pour tout le monde ; honnêtement, tout ce qui convainc ces personnes qui font des appels db &c. pour aller chercher un fil déjà.

Oi, il n'y a pas de repas gratuit, c'est sûr...

J'ai fini par implémenter une lourde charge de travail avec les threads de la plate-forme native en utilisant Java et Swift. Aucune solution de contournement trouvée.

@AndruByrne C'est en effet une information très intéressante et je l'ignorais, aussi évidente qu'elle semble maintenant avec le recul. J'ai déjà un problème lié à mon plugin où un utilisateur essayait de l'utiliser comme moyen d'effectuer une tâche lourde en arrière-plan et cela a verrouillé l'interface utilisateur.

Je suis d'accord qu'il devrait y avoir un avertissement et j'en ajouterai un à mon plugin.

+1 besoin de ça

Malheureusement - Flutter n'est pas prêt pour les heures de grande écoute car il lui manque quelque chose de si basique et pourtant si critique. Toutes les applications Flutter ne peuvent pas s'en tirer avec une simple synchronisation/attente. Pour le gros du travail, les isolats sont essentiels et les plugins qui ne peuvent pas autoriser les appels de méthode de canal de plate-forme à partir d'un autre isolat les rendent inutiles et redondants. Xamarin a intégré cela dans le cadre dès le premier jour - cela devrait sûrement être voté pour une solution maintenant.

+1 besoin de ça

+1

J'ai également besoin d'appeler une méthode de canal de plate-forme à partir d'un autre isolat !

+1

+1

+1

+1 C'est un must have

+1
J'ai un SDK natif que je dois appeler. Ce serait une bonne chose d'avoir quelque chose comme BackgroundFlutterMethodChannel .

+1

+1

Quelqu'un a-t-il des informations sur ce que _crashes badly_ décrit réellement ? Je travaille sur une preuve de concept qui implémente une bibliothèque native et je rencontre des délais d'attente de chien de garde (0x8badf00d) et d'autres erreurs étranges lors de l'envoi de messages via un canal de méthode.

Je soupçonne que cela est dû à certaines méthodes [et résultats] invoqués à partir d'autres threads, mais je n'ai pas été en mesure de progresser dans l'identification du problème exact.

+1

+1

+1

+1

+1 besoin de ça

+1 besoin de cela aussi !

Je pense que cette question devrait être priorisée au moins comme P2.
Les utilisations des canaux de plate-forme auxquelles je peux penser sont

  • Pour appeler des API spécifiques à la plate-forme
  • Pour exécuter des tâches lourdes spécifiques à la plate-forme en arrière-plan et générer un rapport une fois terminé

Et c'est un bloqueur majeur.

Le plug-in flutter_downloader gère le code d'isolement en arrière-plan. Je pense que ça vaut le coup de le regarder.

J'espérais que cela serait résolu dans la prochaine version, mais le bogue reste toujours dans la version 1.12 !

notre code côté client est fortement de l'autre côté du canal et est écrit en kotlin et l'application se bloque presque pour chaque connexion http

Gooooooooooooooogle?!!!!!!!!!!!!?!?!?!?

Ce plugin pourrait être utile flutter_isolate

+1 nous avons besoin de ça... allez

+1

Le plus gros problème pour moi est que la communication entre Dart et les plates-formes natives se produit sur le fil principal - l'envoi de gros morceaux de données est impossible sans retarder l'interface utilisateur ou écrire du code encombrant sur les données de la page.

+1 besoin de ça

écrire du code lourd dans les données de la page.

@lukaszciastko que voulez-vous dire par écrire du code dans les données de la page. Existe-t-il un moyen de résoudre le retard de l'interface utilisateur lors de l'envoi de gros volumes de données ? ?

@YaredTaddese

En fonction de la complexité de ce que fait votre ISOLATE et de l'intercommunication entre votre interface utilisateur et ISOLATE - Il est possible de contourner ce problème dans votre code d'interface utilisateur en structurant les données dont ISOLATE a besoin pour faire son travail avant d'invoquer ISOLATE. J'ai une génération de PDF riche dans mon application Flutter actuelle - un utilisateur peut sélectionner plusieurs PDF à générer.

Chacun peut générer de 1 à 10 000 pages. Mon interface utilisateur est réactive, lorsque chaque travail est terminé, un TAB est construit montrant le rapport - l'utilisateur est averti lorsqu'un travail se termine via un TOAST et l'interface utilisateur ne s'arrête pas - l'utilisateur ne sait pas qu'il y a des services en arrière-plan occupés générer des PDF enrichis.

Remarque, ma génération de PDF est un ISOLATE. Dans ISOLATE -, je dois également télécharger des images à partir de CLOUD FIRESTORE et les intégrer dans un PDF - tout se passe de manière transparente.

@MsXam mais je ne peux pas appeler une fonction spécifique à la plate-forme à partir d'un autre isolat ... ce qui me fait appeler la fonction spécifique à la plate-forme dans l'isolat principal ... ce qui conduit à une interface utilisateur en retard

J'ai le même problème, je pense que c'est très important

@YaredTaddese

En fonction de la complexité de ce que fait votre ISOLATE et de l'intercommunication entre votre interface utilisateur et ISOLATE - Il est possible de contourner ce problème dans votre code d'interface utilisateur en structurant les données dont ISOLATE a besoin pour faire son travail avant d'invoquer ISOLATE. J'ai une génération de PDF riche dans mon application Flutter actuelle - un utilisateur peut sélectionner plusieurs PDF à générer.

Chacun peut générer de 1 à 10 000 pages. Mon interface utilisateur est réactive, lorsque chaque travail est terminé, un TAB est construit montrant le rapport - l'utilisateur est averti lorsqu'un travail se termine via un TOAST et l'interface utilisateur ne s'arrête pas - l'utilisateur ne sait pas qu'il y a des services en arrière-plan occupés générer des PDF enrichis.

Remarque, ma génération de PDF est un ISOLATE. Dans ISOLATE -, je dois également télécharger des images à partir de CLOUD FIRESTORE et les intégrer dans un PDF - tout se passe de manière transparente.

Appelez-vous un code spécifique à la plate-forme ?

Quelqu'un a-t-il une solution de contournement pour appeler les canaux de la plate-forme à partir d'un isolat? Cela va à l'encontre de l'idée de le faire à partir du fil d'interface utilisateur.

Je suis bloqué sur la même chose.

J'appelle un methodchannel avec un code kotlin et l'API getStream.io, mais c'est lent comme l'enfer, et mon gel de l'interface utilisateur Flutter, je voulais y ajouter du calcul, mais j'ai eu les mêmes erreurs.

Comment puis-je réparer cela?

Je viens de tomber sur ça aussi. Je suis en train de prototyper une nouvelle application, et vraiment ce problème pourrait être un obstacle pour moi et me pousser à rechercher d'autres solutions non-Flutter, ce qui me rendra triste.

Comme l'indique le volume de réponses à ce problème, il s'agit d'un problème MAJEUR et va à l'encontre du principal cas d'utilisation des isolats dans le contexte d'une application Flutter.

Je viens de tomber sur ça aussi. Je suis en train de prototyper une nouvelle application, et vraiment ce problème pourrait être un obstacle pour moi et me pousser à rechercher d'autres solutions non-Flutter, ce qui me rendra triste.

Comme l'indique le volume de réponses à ce problème, il s'agit d'un problème MAJEUR et va à l'encontre du principal cas d'utilisation des isolats dans le contexte d'une application Flutter.

ouais c'est un énorme problème, besoin de réparer dès que possible

J'apprends le flutter depuis 10 jours, et j'adore ça jusqu'à présent, mais après un certain temps d'expérimentation, vous commencez à rencontrer des problèmes et à voir les inconvénients, et CE numéro, ouvert depuis le 5 janvier 2018, oof ...

Veuillez corriger ce bogue.

J'espère soutenir bientôt.

@jpsarda , @klaszlo8207 Ne retenez pas votre souffle. Et oui, il *existait une solution au problème, jusqu'à aujourd'hui : https://pub.dev/packages/isolate_handler. Le programmeur a dû le débrancher aujourd'hui car un changement très récent de Flutter (quelques jours seulement) a rendu impossible de suivre cette route.

Vous devez savoir, cependant, que cela ne signifiait pas vraiment le parallélisme. Cela aurait pu apparaître ainsi et l'apparence est importante dans les applications mobiles, mais le code de la plate-forme s'exécute de toute façon sur le thread principal, donc déléguer une longue tâche de l'isolat au thread principal n'a vraiment rien accompli.

Ce serait bien que Dart gère le multithreading par défaut chaque fois que vous utilisez une fonction asynchrone.

@spiderion Async n'a jamais été conçu pour être multithread, comme sur de nombreuses autres plates-formes. Ne vous méprenez pas, je ne dirai pas que la limitation du canal de la plate-forme n'est pas un gros problème car c'est le cas (mon application fonctionnait avec le plugin que j'ai mentionné jusqu'à hier et maintenant je dois chercher des solutions de contournement), mais async/ attendent n'a jamais été sur le multithreading dès le premier jour.

@zoechi Nous aimerions savoir s'il existe une mise à jour sur ce problème ou s'il existe un plan pour le résoudre ?
Merci.

@spiderion Ils ne peuvent pas vraiment le réparer, cela découle du fonctionnement des isolats. Ils n'ont pas de moteur d'interface utilisateur derrière eux, il n'y a donc pas de communication par canal de plate-forme. Cependant, il existe deux plugins pour vous aider :

  • https://pub.dev/packages/flutter_isolate fournit un isolat de remplacement qui peut communiquer avec les plugins car il crée son propre support d'interface utilisateur (rien que vous voyez ou que vous ayez à gérer, juste techniquement),

  • https://pub.dev/packages/isolate_handler que nous venons de modifier pour s'appuyer sur le package ci-dessus car la manière précédente dont il était utilisé était rendue impossible par un changement récent de Flutter. L'avantage d'utiliser ce package plutôt que flutter_isolate lui-même est que cela ajoute des capacités de gestion, vous pouvez démarrer plusieurs isolats, en garder une trace et vous n'avez pas à configurer votre propre communication entre l'isolat et le principal thread (quelque chose que vous devez faire manuellement avec le stock d'origine Isolate et FlutterIsolate ) car il est abstrait et facilement disponible.

J'utilise le second avec une parfaite réussite. En fait, je l'utilise depuis un certain temps et lorsque le changement de Flutter est arrivé, j'ai aidé son programmeur à passer à la nouvelle solution simplement parce que mon application était également tombée en panne. :-)

Donc, je ne pense pas qu'il soit raisonnable d'attendre la solution du noyau, en particulier que la plupart des cas d'utilisation n'appellent pas de communication par canal de plate-forme, donc le moteur d'interface utilisateur de support n'est pas quelque chose qu'ils voudraient ajouter à chaque isolat. Utilisez simplement les plugins existants lorsque vous avez besoin de cette fonctionnalité supplémentaire.

Salut @deakjahn merci pour votre réponse rapide.

Les solutions que vous proposez semblent très utiles. Cependant, je ne sais pas si cela pourrait résoudre mon problème. J'ai actuellement une situation dans l'application où l'utilisateur crée une histoire disons similaire à Instagram. L'application, dans ce cas, doit télécharger sur le stockage Firebase une liste de vidéos et de fichiers qui peuvent prendre très longtemps à télécharger (environ 15 minutes si la connexion est lente), puis une fois les fichiers téléchargés, l'application doit être créée. un document sur le cloud firebase, Firestore. Le problème que je rencontre est que si l'utilisateur tue l'application pendant le téléchargement des fichiers, la tâche complète n'est pas entièrement exécutée.

Ce n'est pas vraiment la responsabilité de l'isolat, il fait juste ce que vous lui dites de faire. Firebase peut reprendre les déchargements arrêtés, pour autant que je sache, même si je ne l'ai jamais utilisé moi-même.

J'essaie d'exécuter l'exemple getBatteryLevel :
https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab
et il se bloque à chaque fois que j'essaie de l'exécuter sur un appareil Android. Puis-je avoir une aide s'il vous plait? Je ne sais même pas comment vérifier l'erreur, car il n'y a pas de journaux et il continue de fonctionner dans une boucle infinie et j'utilise exactement le même code.

Étiqueter ce problème pour P5 ??? 🥵

S'il vous plaît, consultez les commentaires précédents. Il semble très peu probable que cela soit modifié dans le cadre car cela nécessite des mécanismes dont la plupart des isolats n'ont pas besoin, il est donc inutile d'ajouter cette surcharge. Pour les cas où vous en avez besoin, il existe des packages pour le résoudre.

Les utilisateurs ne se soucient pas du mécanisme ; à quel point c'est dur ou pas. Ils ont juste besoin de résultats. Cela pourrait être impossible à faire. Mais si les utilisateurs en ont besoin, Flutter devrait le fournir...

Je comprends que l'implémentation a des limites et peut prendre des tonnes de temps pour ajouter cette fonctionnalité à l'isolat existant ou pour créer une nouvelle API avec cette fonctionnalité. Je pense juste que cela devrait avoir une priorité plus élevée que P5.

De plus, j'ai lu tous vos commentaires et j'ai l'impression que vous ne faites que promouvoir le package que vous avez créé.

Non, ce n'est pas moi qui l'ai créé, mais j'en ai accepté la propriété il y a quelques semaines, oui. Je ne pense pas que cela ait été gardé secret dans ce fil. En plus de cela, j'ai mentionné deux packages (dans un seul message, en fait, une seule fois) qui sont tous deux capables de fournir ce que vous recherchez et je n'ai aucune affiliation avec le second autre que de m'appuyer sur son excellent travail effectué jusqu'à présent.

Si vous le lisez, la seule raison pour laquelle je les ai utilisés moi-même en premier lieu était que j'avais besoin de la même fonctionnalité que vous avez demandée. Et mes applications s'y fient depuis plus d'un an maintenant, sans problème. De plus, devenir plus familier avec le fonctionnement interne des isolats qu'un développeur occasionnel qui les utiliserait (en plus que j'ai commencé à travailler sur leur homologue dans Flutter Web), me fait penser que ce que j'ai écrit était vrai : cette fonctionnalité ne sera pas incorporé dans le cadre parce qu'il y a pas mal d'arguments contre son inclusion. Pas parce que cela prendrait des tonnes de temps, pas du tout. Cela prendrait relativement peu de temps. Tout simplement parce que cela ajouterait une complexité de backend à tous les isolats dont seuls quelques -uns auraient réellement besoin et qu'ils utiliseraient.

@deakjahn merci pour ces conseils, l'utilisation de isolate_handler m'aidera-t-elle à invoquer une méthode et à envoyer des données du côté fléchettes chaque fois que je reçois une notification du côté natif? (J'utilise les SDK natifs de Twilio et je reçois des notifications push du côté natif)

Si cela signifie que vous avez un plug-in de plateforme que vous utilisez pour communiquer via un canal de plate-forme, alors oui, l'un des deux packages vous aidera. Si cela fonctionne à partir de code normal, ces packages le feront également fonctionner à partir d'un isolat.

@deakjahn Merci pour la réponse rapide ! Mon problème est que je ne pouvais pas le faire avec un canal de plate-forme régulier, je ne suis pas sûr, mais je pense que le sdk twilio gère l'écoute des notifications push dans un isolat séparé et non sur le fil principal, et je pense que c'est mon problème. Je voulais savoir si cela fonctionnerait si j'invoquais la méthode chaque fois que je reçois une notification en utilisant l'un des 2 packages ? Merci beaucoup. (J'envisage de passer à des applications natives si ce n'est pas possible)

Je ne sais même pas ce qu'est Twilio (OK, je l'ai googlé :-) ), donc je ne peux vraiment pas en être sûr. Mais dans tous les cas, si vous souhaitez interfacer n'importe quel code natif, vous devez de toute façon utiliser un canal de plate-forme. Soit vous utilisez un plugin déjà existant que quelqu'un a créé pour utiliser ce Twilio, soit vous le créez vous-même (auquel cas soit vous créez pratiquement le même plugin, mais ne le publiez pas et y faites référence localement), soit vous copiez simplement le plugin pertinent code dans votre propre application (ce ne sera pas une dépendance externe mais à part ça, il aura pratiquement le même code, de toute façon).

Et, si vous avez un canal de plate-forme, la réponse précédente s'applique : si vous pouvez déjà utiliser le canal de plate-forme et le plug-in à partir du fil principal d'une application Flutter standard, vous pouvez l'utiliser à partir d'un isolat en utilisant l'un des deux colis.

Donc, ce problème existe depuis le 5 janvier 2018 et ce n'est toujours pas possible avec un effort supplémentaire. Les solutions fournies sont intéressantes - mais rien ne fonctionne encore sur le bureau. Donc, pour tous ceux qui veulent essayer de savoir si le flutter fonctionne bien sur le bureau et voir s'il est possible pour un futur produit, ils seront totalement bloqués ici.
Presque toutes les applications ont besoin d'isolats, car il y a toujours des calculs énormes, comment est-il possible qu'il ne se passe rien sur des défauts de conception d'API aussi importants ?

J'ai ajouté une issue pour cela, dans mon plugin System Alert Window

La solution est mentionnée ici Isoler la communication .
Bien que l'exemple fournisse des discussions spécifiques au plugin de fenêtre d'alerte système, il peut être facilement reproduit pour d'autres plugins. Et il ne nécessite aucun autre plugin sophistiqué pour le faire fonctionner !

Cela peut encore être assez problématique. Cela signifie qu'il n'y a aucun moyen d'appeler un plugin directement à partir d'un isolat qui s'exécute en arrière-plan/au premier plan, car vous devriez envoyer un message à la portée de l'application pour pouvoir faire quoi que ce soit avec un plugin. Donc, si votre application ne fonctionne pas, il n'y a aucun moyen de faire quoi que ce soit avec un plugin en arrière-plan.

Ainsi, par exemple, si vous souhaitez faire quoi que ce soit avec les préférences partagées dans le backgroundMessageHandler du plugin FCM lorsque l'application est fermée, il lancera une MissingPluginException.

Ou si vous ne voulez pas ouvrir l'application avec une fenêtre d'alerte système. La fenêtre d'alerte s'exécute sur un isolat différent. Vous devrez donc exécuter le code dans la portée de votre application. C'est problématique, car l'application est actuellement fermée.

Et il y a probablement beaucoup d'autres scénarios où c'est un énorme problème.

@michael-ottink les scénarios que vous décrivez sont précisément ceux où il ne devrait pas y avoir de problème. Les plugins qui exécutent du code en arrière-plan instancient généralement un tout nouveau FlutterEngine avec son propre registre de plugins et l'isolat qui s'y exécute pourra communiquer directement via les canaux de la plate-forme avec ces plugins.

Incidemment, cela signifie que si vous avez besoin d'un isolat pour utiliser des plugins, le moyen le plus simple consiste simplement à envelopper cet isolat dans un FlutterEngine et il héritera comme par magie de ces pouvoirs. C'est ce que le package flutter_isolate fait pour vous sur Android et iOS (bien que je convienne qu'il serait préférable d'avoir un correctif approprié plutôt qu'une solution de contournement. Les solutions de contournement disponibles aujourd'hui présentent des goulots d'étranglement, des frais généraux ou peuvent être sous-maintenues toutes les plateformes.)

@ryanheise donc flutter_isolate le fait pour moi ? Comment je ne peux pas vraiment dire à partir des docs. Que dois-je faire pour que cela fonctionne pour mon FCM onBackgroundMessage ? J'ai donc besoin de créer un nouvel isolat dans mon onBackgroundMessageHandler ? Mais je ne peux pas utiliser de plugins là-bas, alors comment utiliser le flutter_isolate alors?

Votre problème est que firebase_messaging n'a pas été mis à jour depuis longtemps et est loin derrière les dernières API d'exécution en arrière-plan. S'ils mettaient à jour leur plugin, vous n'auriez plus ce problème. Juste pour clarifier à nouveau, le type de scénario que vous décrivez est précisément celui où il ne devrait pas y avoir de problème car les isolats d'arrière-plan devraient déjà avoir accès aux plugins s'ils sont correctement implémentés. le plugin firebase_messaging n'est pas implémenté selon les dernières API et c'est pourquoi il ne fonctionne pas pour vous. Vous pouvez soumettre un rapport de bogue avec ce projet.

J'ai un besoin urgent d'aide avec ça. Est-il possible que je puisse faire cela. J'ai mon premier accord sérieux demain et je n'arrive pas à comprendre.

Faites tout pour vous assurer que les plugins sont en cours de chargement. Je ne sais pas si vous avez suivi les instructions du README sur la façon d'activer les messages d'arrière-plan, mais vous devez créer une classe Application personnalisée qui se connecte à l'initialisation de l'arrière-plan FlutterNativeView du plugin. Si vous ne l'avez pas fait, aucun des plugins ne sera disponible pour vous. Si cela ne fonctionne toujours pas, vous pouvez essayer de rétrograder votre projet vers l'ancienne architecture de plugin de style v1 (et risquer de casser d'autres plugins qui nécessitent la v2).

Je configure mon Application.kt comme ceci

import `in`.jvapps.system_alert_window.SystemAlertWindowPlugin
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService

import android.os.Build
import android.app.NotificationManager
import android.app.NotificationChannel

public class Application: FlutterApplication(), PluginRegistrantCallback {

   override fun onCreate() {
     super.onCreate()
     FlutterFirebaseMessagingService.setPluginRegistrant(this)
     createNotificationChannels()
     SystemAlertWindowPlugin.setPluginRegistrant(this)
   }

   override fun registerWith(registry: PluginRegistry) {
     FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
     SystemAlertWindowPlugin.registerWith(registry.registrarFor("in.jvapps.system_alert_window"))
   }

   fun createNotificationChannels() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = "groupChannel"
        val descriptionText = "This is the group channel"
        val importance = NotificationManager.IMPORTANCE_HIGH
        val mChannel = NotificationChannel("59054", name, importance)
        mChannel.description = descriptionText
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
    }
  }
}

Il est préférable de publier ceci sur la page des problèmes de firebase_messaging car il n'est pas lié à ce problème.

Je peux faire un PR, qui aura une nouvelle méthode 'spawnIsolate' pour 'SchedulerBinding'.

Ensuite, il sera possible d'appeler des méthodes de plate-forme dans cet isolat.

Cela ne vous aidera que si vous devez appeler une méthode de plate-forme et obtenir une réponse.

import 'dart:async';
import 'dart:isolate';

import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart';

Future<void> _test(SendPort sendPort) async {
  final dir = await getTemporaryDirectory();
  sendPort.send(dir);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final Completer<Object> completer = Completer<Object>();
  final RawReceivePort receivePort = RawReceivePort(completer.complete);

  final Isolate isolate = await ServicesBinding.instance.spawnIsolate(
    _test,
    receivePort.sendPort,
  );

  print(await completer.future);

  receivePort.close();
  isolate.kill();
}

Enregistrer

Performing hot restart...
Syncing files to device Pixel 4...
Restarted application in 722ms.
I/flutter (11705): Directory: '/data/user/0/com.example.bug/cache'

Je peux faire un PR, qui aura une nouvelle méthode 'spawnIsolate' pour 'SchedulerBinding'.

Ensuite, il sera possible d'appeler des méthodes de plate-forme dans cet isolat.

Cela ne vous aidera que si vous devez appeler une méthode de plate-forme et obtenir une réponse.

SchedulerBinding.instance.spawnIsolate

Une chance de tester ça ? Si cela correspond à mes besoins etc..

@Nailik
Remplacer ce fichier (Réel pour la version 1.17.5)


reliure.dart

```dart // Copyright 2014 Les auteurs Flutter. Tous les droits sont réservés.
// L'utilisation de ce code source est régie par une licence de type BSD qui peut être
// trouvé dans le fichier LICENSE.

importer ' dard:async ' ;
importer ' fléchette:isoler ';
importer ' dard:typed_data ' ;
importer ' dart:ui ' en tant qu'interface utilisateur ;

import ' package:flutter/foundation.dart ';

importer 'asset_bundle.dart' ;
importer 'binary_messenger.dart' ;
importer 'system_channels.dart' ;

/// Écoute les messages de la plate-forme et les dirige vers le [defaultBinaryMessenger].
///
/// Le [ServicesBinding] enregistre également un [LicenseEntryCollector] qui expose
/// les licences présentes dans le fichier LICENSE stocké à la racine du bien
/// bundle et implémente l'extension de service ext.flutter.evict (voir
/// [expulser]).
mixin ServicesBinding on BindingBase {
@passer outre
void initInstances() {
super.initInstances();
_instance = ceci ;
_defaultBinaryMessenger = createBinaryMessenger();
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage ;
initLicences();
SystemChannels.system.setMessageHandler(handleSystemMessage);
}

/// Le [ServicesBinding] actuel, s'il a été créé.
static ServicesBinding get instance => _instance ;
statique ServicesBinding _instance ;

/// L'instance par défaut de [BinaryMessenger].
///
/// Ceci est utilisé pour envoyer des messages de l'application à la plateforme, et
/// garde une trace des gestionnaires qui ont été enregistrés sur chaque canal afin
/// il peut envoyer des messages entrants au gestionnaire enregistré.
BinaryMessenger obtenir defaultBinaryMessenger => _defaultBinaryMessenger ;
BinaryMessenger _defaultBinaryMessenger ;

/// Crée une instance [BinaryMessenger] par défaut qui peut être utilisée pour l'envoi
/// messages de la plate-forme.
@protégé
BinaryMessenger createBinaryMessenger() {
return const _DefaultBinaryMessenger._();
}

/// Gestionnaire appelé pour les messages reçus sur [SystemChannels.system]
/// canal de messages.
///
/// D'autres liaisons peuvent remplacer ceci pour répondre aux messages système entrants.
@protégé
@mustCallSuper
FuturhandleSystemMessage(Object systemMessage) async { }

/// Ajoute les licences pertinentes au [LicenseRegistry].
///
/// Par défaut, l'implémentation de [initLicenses] par [ServicesBinding] ajoute
/// toutes les licences collectées par l'outil flutter lors de la compilation.
@protégé
@mustCallSuper
void initLicences() {
LicenseRegistry.addLicense(_addLicenses);
}

Flux_addLicenses() asynchrone* {
// Nous utilisons des temporisateurs ici (plutôt que scheduleTask de la liaison du planificateur)
// car la couche de services ne peut pas utiliser la liaison du planificateur (le planificateur
// la liaison utilise la couche de services pour gérer ses événements de cycle de vie). Minuteries
// sont ce que scheduleTask utilise sous le capot de toute façon. La seule différence est
// que ceux-ci s'exécuteront ensuite, au lieu d'être prioritaires par rapport à
// les autres tâches qui pourraient être en cours d'exécution. Utiliser _quelque chose_ ici pour casser
// ceci en deux parties est important car les isolats mettent du temps à se copier
// données en ce moment, et si nous recevons les données dans la même boucle d'événement
// itération au fur et à mesure que nous envoyons les données au prochain isolat, nous sommes définitivement
// va manquer des images. Une autre solution serait d'avoir le travail tout
// se produit dans un isolat, et nous pouvons y aller éventuellement, mais nous sommes d'abord
// va voir si la communication isolée peut être rendue moins chère.
// Voir : https://github.com/dart-lang/sdk/issues/31959
// https://github.com/dart-lang/sdk/issues/31960
// TODO(ianh) : supprimer cette complexité une fois ces bogues corrigés.
finalisterawLicences = Compléteur();
Timer.run(() asynchrone {
rawLicenses.complete(rootBundle.loadString('LICENSE', cache : false));
});
attendre rawLicenses.future ;
finaliste> parsedLicences = Completer>();
Timer.run(() asynchrone {
parsedLicenses.complete(compute(_parseLicenses, attendre rawLicenses.future, debugLabel : 'parseLicenses'));
});
attendre parsedLicenses.future ;
rendement* Flux.fromIterable (attendez parsedLicenses.future);
}

// Ceci est exécuté dans un autre isolat créé par _addLicenses ci-dessus.
Liste statique_parseLicenses(String rawLicences) {
Chaîne finale _licenseSeparator = '\n' + ('-' * 80) + '\n' ;
liste finalerésultat =[] ;
liste finalelicences = rawLicenses.split(_licenseSeparator);
for (final String license in licenses) {
final int split = license.indexOf('\n\n');
si (divisé >= 0) {
result.add(LicenseEntryWithLineBreaks(
licence.substring(0, split).split('\n'),
license.substring(split + 2),
));
} autre {
result.add(LicenseEntryWithLineBreaks(const[], Licence));
}
}
retourner le résultat ;
}

@passer outre
void initServiceExtensions() {
super.initServiceExtensions();

assert(() {
  registerStringServiceExtension(
    // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
    // the rootBundle cache and cause the entire image cache to be cleared.
    // This is used by hot reload mode to clear out the cache of resources
    // that have changed.
    name: 'evict',
    getter: () async => '',
    setter: (String value) async {
      evict(value);
    },
  );
  return true;
}());

}

/// Appelé en réponse à l'extension de service ext.flutter.evict .
///
/// Ceci est utilisé par l'outil flutter lors du rechargement à chaud afin que toutes les images
/// qui ont changé sur le disque sont effacés des caches.
@protégé
@mustCallSuper
void evict(String asset) {
rootBundle.evict(asset);
}

FuturspawnIsolate(
FuturOupoint d'entrée (message T),
Message T, {
bool mis en pause = faux,
booléen errorAreFatal,
SendPort à la sortie,
SendPort en cas d'erreur,
Nom de débogage de la chaîne,
}) {
affirmer(
_isMainIsolate,
"Impossible de créer plusieurs niveaux d'isolats",
);

final RawReceivePort messageReceiver = RawReceivePort(
  (Object receivedMessage) async {
    if (receivedMessage is SendPort) {
      receivedMessage.send(
        _IsolateStarter<T>(
          ui.PluginUtilities.getCallbackHandle(entryPoint),
          message,
        ),
      );
    } else if (receivedMessage is _Message) {
      final ByteData result = await defaultBinaryMessenger.send(
        receivedMessage.channel,
        receivedMessage.message,
      );
      receivedMessage.sendPort.send(result);
    }
  },
);
RawReceivePort onExitReceiver;
onExitReceiver = RawReceivePort(
  (Object message) {
    onExit?.send(message);

    onExitReceiver.close();
    messageReceiver.close();
  },
);

return Isolate.spawn(
  _startIsolate,
  messageReceiver.sendPort,
  paused: paused,
  errorsAreFatal: true,
  onExit: onExitReceiver.sendPort,
  onError: onError,
  debugName: debugName,
);

}
}

Futur_startIsolate(SendPort sendPort) asynchrone {
_sendPortToMainIsolate = sendPort ;
_IsolateBinding();

Compléteur final<_IsolateStarter> compléteur =
Compléter<_IsolateStarter>();

final RawReceivePort receivePort = RawReceivePort(
(Objet isolateStarter) {
assert(isolateStarter est _IsolateStarter);
completer.complete(isolateStarter as _IsolateStarter);
},
);

sendPort.send(receivePort.sendPort);

final _IsolateStarterisolateStarter = attendre completer.future ;

recevoirPort.close();

Fonction finale fonction =
ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

fonction d'attente (isolateStarter.message);
}

SendPort _sendPortToMainIsolate ;

booléen obtenir _isMainIsolate => _sendPortToMainIsolate == null ;

classe _IsolateStarter{
_IsolateStarter(this.callbackHandle, this.message);

ui final.CallbackHandle callbackHandle ;
message T final ;
}

classe _Message {
_Message(
ce.canal,
ce message,
this.sendPort,
);

canal String final ;
message ByteData final ;
SendPort final SendPort ;
}

la classe _IsolateBinding étend BindingBase avec ServicesBinding {}

/// L'implémentation par défaut de [BinaryMessenger].
///
/// Ce messager envoie des messages du côté de l'application au côté de la plate-forme et
/// distribue les messages entrants du côté de la plate-forme au bon
/// gestionnaire.
la classe _DefaultBinaryMessenger étend BinaryMessenger {
const _DefaultBinaryMessenger._();

// Gestionnaires pour les messages entrants des plugins de plate-forme.
// Ceci est statique afin que cette classe puisse avoir un constructeur const.
Carte finale statique_handlers =
{} ;

// Des gestionnaires fictifs qui interceptent et répondent aux messages sortants.
// Ceci est statique afin que cette classe puisse avoir un constructeur const.
Carte finale statique_mockHandlers =
{} ;

Futur_sendPlatformMessage (canal de chaîne, message ByteData) {
finalistecompléteur = compléteur();

if (_isMainIsolate) {
  // ui.window is accessed directly instead of using ServicesBinding.instance.window
  // because this method might be invoked before any binding is initialized.
  // This issue was reported in #27541. It is not ideal to statically access
  // ui.window because the Window may be dependency injected elsewhere with
  // a different instance. However, static access at this location seems to be
  // the least bad option.
  ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
    try {
      completer.complete(reply);
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context:
            ErrorDescription('during a platform message response callback'),
      ));
    }
  });
} else {
  RawReceivePort receivePort;
  receivePort = RawReceivePort(
    (Object message) async {
      assert(message is ByteData);
      completer.complete(message as ByteData);
      receivePort.close();
    },
  );
  _sendPortToMainIsolate.send(
    _Message(channel, message, receivePort.sendPort),
  );
}

return completer.future;

}

@passer outre
FuturhandlePlatformMessage(
Chaîne de chaîne,
Données ByteData,
rappel ui.PlatformMessageResponseCallback,
) asynchrone {
Réponse ByteData ;
essayer {
gestionnaire final de MessageHandler = _handlers[canal] ;
si (gestionnaire != null) {
réponse = attendre le gestionnaire (données);
} autre {
ui.channelBuffers.push(canal, données, rappel);
rappel = null ;
}
} catch (exception, pile) {
FlutterError.reportError(FlutterErrorDetails(
exception : exception,
pile : pile,
bibliothèque : 'bibliothèque de services',
contexte : ErrorDescription('lors d'un rappel de message de la plate-forme'),
));
} finalement {
si (rappel != null) {
rappel (réponse);
}
}
}

@passer outre
Futurenvoyer (canal chaîne, message ByteData) {
gestionnaire final de MessageHandler = _mockHandlers[canal] ;
si (gestionnaire != null)
gestionnaire de retour (message);
return _sendPlatformMessage(canal, message);
}

@passer outre
void setMessageHandler (canal de chaîne, gestionnaire de MessageHandler) {
si (gestionnaire == null)
_handlers.remove(canal);
autre
_handlers[canal] = gestionnaire ;
ui.channelBuffers.drain (canal, (données ByteData, rappel ui.PlatformMessageResponseCallback) async {
attendre handlePlatformMessage (canal, données, rappel);
});
}

@passer outre
void setMockMessageHandler (canal de chaîne, gestionnaire de MessageHandler) {
si (gestionnaire == null)
_mockHandlers.remove(canal);
autre
_mockHandlers[canal] = gestionnaire ;
}
}
```

J'ai flutter 1.21 donc j'ai essayé de mettre à jour tout le code à compiler.
Il reste maintenant un problème car _IsolateBinding manque de nombreuses implémentations des méthodes BindingBase ...

je reçois qc comme

Error: The non-abstract class '_IsolateBinding' is missing implementations for these members:
 - BindingBase with ServicesBinding.SchedulerBinding.addPersistentFrameCallback

environ 30 fois.

A la fin la console imprime

/C:/flutter/packages/flutter/lib/src/services/binding.dart:341:7: Error: 'BindingBase' doesn't implement 'SchedulerBinding' so it can't be used with 'ServicesBinding'.
 - 'BindingBase' is from 'package:flutter/src/foundation/binding.dart' ('/C:/flutter/packages/flutter/lib/src/foundation/binding.dart').
 - 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('/C:/flutter/packages/flutter/lib/src/scheduler/binding.dart').
 - 'ServicesBinding' is from 'package:flutter/src/services/binding.dart' ('/C:/flutter/packages/flutter/lib/src/services/binding.dart').
class _IsolateBinding extends BindingBase with ServicesBinding {}

Je ne suis pas sûr à 100% de ce que fait l'appel de _IsolateBinding() dans _startIsolate mais sans que j'obtienne l'erreur commune ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. .

J'ai essayé de résoudre ce problème, mais je pense que ma connaissance des mixins n'est pas encore très bonne.
Toute idée de comment résoudre ce problème?


Le nouveau fichier ressemble à ceci (jusqu'à présent)

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// <strong i="24">@dart</strong> = 2.8

import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';

import 'asset_bundle.dart';
import 'binary_messenger.dart';
import 'restoration.dart';
import 'system_channels.dart';

/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
///
/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
/// the licenses found in the `LICENSE` file stored at the root of the asset
/// bundle, and implements the `ext.flutter.evict` service extension (see
/// [evict]).
mixin ServicesBinding on BindingBase, SchedulerBinding {
  <strong i="25">@override</strong>
  void initInstances() {
    super.initInstances();
    _instance = this;
    _defaultBinaryMessenger = createBinaryMessenger();
    _restorationManager = createRestorationManager();
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    SystemChannels.system.setMessageHandler(handleSystemMessage);
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
  }

  /// The current [ServicesBinding], if one has been created.
  static ServicesBinding get instance => _instance;
  static ServicesBinding _instance;

  /// The default instance of [BinaryMessenger].
  ///
  /// This is used to send messages from the application to the platform, and
  /// keeps track of which handlers have been registered on each channel so
  /// it may dispatch incoming messages to the registered handler.
  BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
  BinaryMessenger _defaultBinaryMessenger;

  /// Creates a default [BinaryMessenger] instance that can be used for sending
  /// platform messages.
  <strong i="26">@protected</strong>
  BinaryMessenger createBinaryMessenger() {
    return const _DefaultBinaryMessenger._();
  }

  /// Called when the operating system notifies the application of a memory
  /// pressure situation.
  ///
  /// This method exposes the `memoryPressure` notification from
  /// [SystemChannels.system].
  <strong i="27">@protected</strong>
  <strong i="28">@mustCallSuper</strong>
  void handleMemoryPressure() { }

  /// Handler called for messages received on the [SystemChannels.system]
  /// message channel.
  ///
  /// Other bindings may override this to respond to incoming system messages.
  <strong i="29">@protected</strong>
  <strong i="30">@mustCallSuper</strong>
  Future<void> handleSystemMessage(Object systemMessage) async {
    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
    final String type = message['type'] as String;
    switch (type) {
      case 'memoryPressure':
        handleMemoryPressure();
        break;
    }
    return;
  }

  /// Adds relevant licenses to the [LicenseRegistry].
  ///
  /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
  /// all the licenses collected by the `flutter` tool during compilation.
  <strong i="31">@protected</strong>
  <strong i="32">@mustCallSuper</strong>
  void initLicenses() {
    LicenseRegistry.addLicense(_addLicenses);
  }

  Stream<LicenseEntry> _addLicenses() async* {
    // We use timers here (rather than scheduleTask from the scheduler binding)
    // because the services layer can't use the scheduler binding (the scheduler
    // binding uses the services layer to manage its lifecycle events). Timers
    // are what scheduleTask uses under the hood anyway. The only difference is
    // that these will just run next, instead of being prioritized relative to
    // the other tasks that might be running. Using _something_ here to break
    // this into two parts is important because isolates take a while to copy
    // data at the moment, and if we receive the data in the same event loop
    // iteration as we send the data to the next isolate, we are definitely
    // going to miss frames. Another solution would be to have the work all
    // happen in one isolate, and we may go there eventually, but first we are
    // going to see if isolate communication can be made cheaper.
    // See: https://github.com/dart-lang/sdk/issues/31959
    //      https://github.com/dart-lang/sdk/issues/31960
    // TODO(ianh): Remove this complexity once these bugs are fixed.
    final Completer<String> rawLicenses = Completer<String>();
    scheduleTask(() async {
      rawLicenses.complete(await rootBundle.loadString('NOTICES', cache: false));
    }, Priority.animation);
    await rawLicenses.future;
    final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
    scheduleTask(() async {
      parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
    }, Priority.animation);
    await parsedLicenses.future;
    yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
  }

  // This is run in another isolate created by _addLicenses above.
  static List<LicenseEntry> _parseLicenses(String rawLicenses) {
    final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
    final List<LicenseEntry> result = <LicenseEntry>[];
    final List<String> licenses = rawLicenses.split(_licenseSeparator);
    for (final String license in licenses) {
      final int split = license.indexOf('\n\n');
      if (split >= 0) {
        result.add(LicenseEntryWithLineBreaks(
          license.substring(0, split).split('\n'),
          license.substring(split + 2),
        ));
      } else {
        result.add(LicenseEntryWithLineBreaks(const <String>[], license));
      }
    }
    return result;
  }

  <strong i="33">@override</strong>
  void initServiceExtensions() {
    super.initServiceExtensions();

    assert(() {
      registerStringServiceExtension(
        // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
        // the rootBundle cache and cause the entire image cache to be cleared.
        // This is used by hot reload mode to clear out the cache of resources
        // that have changed.
        name: 'evict',
        getter: () async => '',
        setter: (String value) async {
          evict(value);
        },
      );
      return true;
    }());
  }

  /// Called in response to the `ext.flutter.evict` service extension.
  ///
  /// This is used by the `flutter` tool during hot reload so that any images
  /// that have changed on disk get cleared from caches.
  <strong i="34">@protected</strong>
  <strong i="35">@mustCallSuper</strong>
  void evict(String asset) {
    rootBundle.evict(asset);
  }

  Future<Isolate> spawnIsolate<T>(
      FutureOr<void> entryPoint(T message),
      T message, {
        bool paused = false,
        bool errorsAreFatal,
        SendPort onExit,
        SendPort onError,
        String debugName,
      }) {
    assert(
    _isMainIsolate,
    'Can\'t make multiple levels of isolates',
    );

    final RawReceivePort messageReceiver = RawReceivePort(
          (Object receivedMessage) async {
        if (receivedMessage is SendPort) {
          receivedMessage.send(
            _IsolateStarter<T>(
              ui.PluginUtilities.getCallbackHandle(entryPoint),
              message,
            ),
          );
        } else if (receivedMessage is _Message) {
          final ByteData result = await defaultBinaryMessenger.send(
            receivedMessage.channel,
            receivedMessage.message,
          );
          receivedMessage.sendPort.send(result);
        }
      },
    );
    RawReceivePort onExitReceiver;
    onExitReceiver = RawReceivePort(
          (Object message) {
        onExit?.send(message);

        onExitReceiver.close();
        messageReceiver.close();
      },
    );

    return Isolate.spawn(
      _startIsolate,
      messageReceiver.sendPort,
      paused: paused,
      errorsAreFatal: true,
      onExit: onExitReceiver.sendPort,
      onError: onError,
      debugName: debugName,
    );
  }



  // App life cycle

  /// Initializes the [lifecycleState] with the [Window.initialLifecycleState]
  /// from the window.
  ///
  /// Once the [lifecycleState] is populated through any means (including this
  /// method), this method will do nothing. This is because the
  /// [Window.initialLifecycleState] may already be stale and it no longer makes
  /// sense to use the initial state at dart vm startup as the current state
  /// anymore.
  ///
  /// The latest state should be obtained by subscribing to
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  <strong i="36">@protected</strong>
  void readInitialLifecycleStateFromNativeWindow() {
    if (lifecycleState != null) {
      return;
    }
    final AppLifecycleState state = _parseAppLifecycleMessage(window.initialLifecycleState);
    if (state != null) {
      handleAppLifecycleStateChanged(state);
    }
  }

  Future<String> _handleLifecycleMessage(String message) async {
    handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message));
    return null;
  }

  static AppLifecycleState _parseAppLifecycleMessage(String message) {
    switch (message) {
      case 'AppLifecycleState.paused':
        return AppLifecycleState.paused;
      case 'AppLifecycleState.resumed':
        return AppLifecycleState.resumed;
      case 'AppLifecycleState.inactive':
        return AppLifecycleState.inactive;
      case 'AppLifecycleState.detached':
        return AppLifecycleState.detached;
    }
    return null;
  }

  /// The [RestorationManager] synchronizes the restoration data between
  /// engine and framework.
  ///
  /// See the docs for [RestorationManager] for a discussion of restoration
  /// state and how it is organized in Flutter.
  ///
  /// To use a different [RestorationManager] subclasses can override
  /// [createRestorationManager], which is called to create the instance
  /// returned by this getter.
  RestorationManager get restorationManager => _restorationManager;
  RestorationManager _restorationManager;

  /// Creates the [RestorationManager] instance available via
  /// [restorationManager].
  ///
  /// Can be overriden in subclasses to create a different [RestorationManager].
  <strong i="37">@protected</strong>
  RestorationManager createRestorationManager() {
    return RestorationManager();
  }
}

Future<void> _startIsolate<T>(SendPort sendPort) async {
  _sendPortToMainIsolate = sendPort;
  _IsolateBinding();

  final Completer<_IsolateStarter<T>> completer =
  Completer<_IsolateStarter<T>>();

  final RawReceivePort receivePort = RawReceivePort(
        (Object isolateStarter) {
      assert(isolateStarter is _IsolateStarter<T>);
      completer.complete(isolateStarter as _IsolateStarter<T>);
    },
  );

  sendPort.send(receivePort.sendPort);

  final _IsolateStarter<T> isolateStarter = await completer.future;

  receivePort.close();

  final Function function =
  ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

  await function(isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

class _IsolateStarter<T> {
  _IsolateStarter(this.callbackHandle, this.message);

  final ui.CallbackHandle callbackHandle;
  final T message;
}

class _Message {
  _Message(
      this.channel,
      this.message,
      this.sendPort,
      );

  final String channel;
  final ByteData message;
  final SendPort sendPort;
}

//TODO not working
class _IsolateBinding extends BindingBase with ServicesBinding {}

/// The default implementation of [BinaryMessenger].
///
/// This messenger sends messages from the app-side to the platform-side and
/// dispatches incoming messages from the platform-side to the appropriate
/// handler.
class _DefaultBinaryMessenger extends BinaryMessenger {
  const _DefaultBinaryMessenger._();

  // Handlers for incoming messages from platform plugins.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _handlers =
  <String, MessageHandler>{};

  // Mock handlers that intercept and respond to outgoing messages.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _mockHandlers =
  <String, MessageHandler>{};

  Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();

    if (_isMainIsolate) {
      // ui.window is accessed directly instead of using ServicesBinding.instance.window
      // because this method might be invoked before any binding is initialized.
      // This issue was reported in #27541. It is not ideal to statically access
      // ui.window because the Window may be dependency injected elsewhere with
      // a different instance. However, static access at this location seems to be
      // the least bad option.
      ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
        try {
          completer.complete(reply);
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'services library',
            context:
            ErrorDescription('during a platform message response callback'),
          ));
        }
      });
    } else {
      RawReceivePort receivePort;
      receivePort = RawReceivePort(
            (Object message) async {
          assert(message is ByteData);
          completer.complete(message as ByteData);
          receivePort.close();
        },
      );
      _sendPortToMainIsolate.send(
        _Message(channel, message, receivePort.sendPort),
      );
    }

    return completer.future;
  }

  <strong i="38">@override</strong>
  Future<void> handlePlatformMessage(
      String channel,
      ByteData data,
      ui.PlatformMessageResponseCallback callback,
      ) async {
    ByteData response;
    try {
      final MessageHandler handler = _handlers[channel];
      if (handler != null) {
        response = await handler(data);
      } else {
        ui.channelBuffers.push(channel, data, callback);
        callback = null;
      }
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context: ErrorDescription('during a platform message callback'),
      ));
    } finally {
      if (callback != null) {
        callback(response);
      }
    }
  }

  <strong i="39">@override</strong>
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }

  <strong i="40">@override</strong>
  void setMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _handlers.remove(channel);
    else
      _handlers[channel] = handler;
    ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {
      await handlePlatformMessage(channel, data, callback);
    });
  }

  <strong i="41">@override</strong>
  void setMockMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _mockHandlers.remove(channel);
    else
      _mockHandlers[channel] = handler;
  }

  <strong i="42">@override</strong>
  bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;

  <strong i="43">@override</strong>
  bool checkMockMessageHandler(String channel, MessageHandler handler) => _mockHandlers[channel] == handler;
}

@Nailik
Pour le moment, je ne peux pas mettre à jour Flutter pour vous aider

Je me demande pourquoi les autres outils de flutter mis en œuvre par l'équipe de flutter (comme par exemple devtools) n'ont jamais touché ce bloqueur.
Ils semblent ne corriger les bogues que s'ils sont bloqués par eux.

Ce n'est pas une nouvelle fonctionnalité sévère, c'est un bloqueur de framework.
Donc, selon ce https://github.com/flutter/flutter/issues/18761#issuecomment -639248761, il devrait s'agir d'un P3

Je me demande pourquoi les autres outils de flutter mis en œuvre par l'équipe de flutter (comme par exemple devtools) n'ont jamais touché ce bloqueur.
Ils semblent ne corriger les bogues que s'ils sont bloqués par eux.

Pour être honnête, parce que je pense que l'impact de ce problème dans les applications du monde réel est surestimé. Je surveille simplement le problème car cela faciliterait les choses, mais il existe des "solutions de contournement". Et de nombreuses applications et plugins en production utilisent des fonctionnalités d'arrière-plan.

Je ne sais pas non plus si certains des +1 ne comprennent tout simplement pas de quoi il s'agit. (Comme https://github.com/flutter/flutter/issues/13937#issuecomment-635683123 ). Imo la documentation officielle pour les isolats d'arrière-plan et l'exécution sans tête (et comment la gérer) fait encore un peu défaut et la messagerie firebase est un cauchemar, mais cela n'a rien à voir avec le framework, le moteur 🤷‍♂️

Peut-être qu'il suffit d'arrêter de dénigrer l'équipe de flutter, il y a des progrès incroyables, et ce n'est pas quelque chose qui ne peut pas être contourné... Et s'il y a un PR, pourquoi ne pas simplement le soumettre à l'équipe de flutter pour qu'il l'examine 🤔

Il y a un problème avec l'implémentation de ce comportement pour les isolats.

Exemple:

Il y a un isolat principal et un moteur.
Si l'isolat a besoin de quelque chose, il demande au moteur.
Si le moteur a besoin de quelque chose, il demande à l'isolat.

Maintenant notre situation est :
Il y a 2 isolats et un moteur.
Si l'isolat a besoin de quelque chose, il demande au moteur.
Si le moteur a besoin de quelque chose, que doit-il faire ?
Demandez les deux ? Alors la réponse à qui prendre?
Ou demander à un seul isolat ? Alors lequel ?

@hpoul Je dois accepter de ne pas être d'accord car beaucoup de gens ici sont des débutants dans la programmation Android/ios et ils ont choisi leur premier cadre pour le développement d'applications pour être flottant.

Il est donc important que quelqu'un s'en rende compte et corrige ou suggère une solution de contournement officielle avec un exemple pour ce type de problèmes où vous êtes bloqué et forcé par le framework à faire des "solutions de contournement" complexes car évidemment les débutants ne sauront pas quoi apprendre ou même par où commencer car il n'y a pas de solutions de contournement bien expliquées dans les commentaires ici ou n'importe où sur stackoverflow ou d'autres blogs. Il n'y a que des éléments pointant vers certains packages qui peuvent ou non avoir implémenté cela correctement.

Les personnes qui ont des tonnes d'expérience de travail avec les services, les files d'attente de tâches en arrière-plan, etc., à la fois sur Android et iOS, qui peuvent facilement contourner ce problème ne sont pas la cible démographique du flutter.

Et pour en venir aux commentaires hors sujet comme celui-là, je suis sûr qu'il y a de faux +1 dans tous les autres problèmes, ce qui ne change pas beaucoup l'ordre des priorités des problèmes et ce problème reste toujours en tête.

Salut @phanirithvij

flutter_isolate était lié ci-dessus et existe depuis février 2019. Ce n'est pas non plus complexe. Je vais copier le README qui semble simple :

FlutterIsolate permet la création d'un isolat en flutter capable d'utiliser des plugins flutter. Il crée les bits spécifiques à la plate-forme nécessaires (FlutterBackgroundView sur Android et FlutterEngine sur iOS) pour permettre aux canaux de la plate-forme de fonctionner à l'intérieur d'un isolat.

| | Android | iOS | Descriptif |
| :--------------- | :----------------: | :------------------ : | :-------------------------------- |
| FlutterIsolate.spawn(entryPoint,message) | :coche_blanche : | :coche_blanche : | génère un nouveau FlutterIsolate |
| flutterIsolate.pause() | :coche_blanche : | :coche_blanche : | met en pause un isolat en cours d'exécution |
| flutterIsolate.resume() | :coche_blanche : | :coche_blanche : | a repris un isoalte en pause |
| flutterIsolate.kill() | :coche_blanche : | :coche_blanche : | tue un isolat |

Je dis simple car ces méthodes ont les mêmes noms que celles de la classe "originale" Isolate donc si vous savez déjà comment utiliser les isolats de la documentation officielle, il ne devrait pas être difficile de comprendre comment utiliser ceci en remplacement immédiat, au moins pour les méthodes énumérées ci-dessus.

(Remarque : moi aussi, je voudrais que ce problème soit résolu officiellement pour les raisons que j'ai déjà énoncées dans un commentaire précédent, dont certaines que je partage avec vous, mais je n'appellerais pas non plus cette solution de contournement "complexe".)

En réponse au commentaire de @nikitadol , flutter_isolate crée un moteur séparé pour chaque isolat afin qu'il ne rencontre pas le même problème. Bien sûr, il n'est pas idéal de devoir créer un nouveau moteur par isolat, c'est une des raisons pour lesquelles je suis sûr que l'équipe Flutter pourrait faire un meilleur travail. De même, la solution alternative consistant à canaliser tous les appels de méthode de plug-in via l'isolat principal créerait un goulot d'étranglement qui irait à l'encontre des objectifs des isolats en premier lieu, donc je suis sûr que l'équipe Flutter pourrait également faire un meilleur travail.

Je suis d'accord avec ceux qui ont dit que l'équipe Flutter pourrait avoir des problèmes plus prioritaires à résoudre en premier, mais sur la base de ce que nous avons vu auparavant avec l'architecture du plugin, il se peut également que la résolution de ce problème nécessite un changement architectural majeur et plusieurs autres problèmes ouverts. nécessitent également un changement architectural majeur, et peser toutes ces différentes exigences pourrait prendre du temps avant même de pouvoir résoudre ce problème particulier.

@ryanheise

flutter_isolate crée un moteur séparé pour chaque isolat

Je suis donc sûr que l'équipe Flutter pourrait également faire un meilleur travail.

Vous dites que l'équipe Flutter peut le faire, mais comment ?
Les questions que j'ai posées ci-dessus restent valables.
Si la solution est de créer un nouveau moteur, alors vous pouvez simplement utiliser votre plugin

(Remarque, flutter_isolate n'est pas mon plugin, mais comme j'avais aussi besoin de cette fonctionnalité, j'ai décidé de contribuer au projet.)

Ce que je voulais dire par "un meilleur travail", c'est qu'il n'est probablement pas nécessaire de faire tourner TOUTES les machines d'un FlutterEngine juste pour communiquer avec des plugins, mais pour le moment, l'infrastructure nécessaire est entièrement liée au moteur, c'est pourquoi actuellement flutter_isolate doit faire tourner tout un moteur juste pour avoir accès aux plugins.

(Remarque, flutter_isolate n'est pas mon plugin, mais comme j'avais aussi besoin de cette fonctionnalité, j'ai décidé de contribuer au projet.)

Désolé, je n'avais pas remarqué qu'il s'agissait d'une fourchette

flutter_isolate doit faire tourner tout un moteur juste pour avoir accès aux plugins.

Si vous spécifiez que 'is_background_view = true', alors le moteur ne démarre pas le rendu - ce qui signifie que le moteur ne démarre pas complètement

L'API précise pour l'exécution sans tête est différente pour iOS et Android v1 et Android v2, mais c'est essentiellement ce que flutter_isolate fait déjà. Cependant, il y a toujours une différence de frais généraux entre un isolat normal et un FlutterEngine, donc réduire davantage les frais généraux n'est que quelque chose que l'équipe Flutter serait capable de faire. Ce n'est pas seulement une surcharge de temps de démarrage, c'est aussi une surcharge d'utilisation de la mémoire.

Alors vous proposez juste d'optimiser la création d'un nouveau moteur pour travailler en arrière-plan ?

Alors cela ne s'applique probablement pas à ce problème

Je ne suggère pas cela, je signale simplement que la solution de contournement flutter_isolate a cette limitation en raison des frais généraux qui ne peuvent être évités. Je ne suggère pas que l'équipe Flutter devrait essayer de rendre la solution de contournement plus efficace, je pense qu'idéalement, il y aurait un changement architectural dans la façon dont les plugins sont chargés afin qu'il ne soit pas nécessaire d'instancier un FlutterEngine.

Si le moteur est 1 et que les isolats sont supérieurs à 1, alors nous revenons à cette question :

https://github.com/flutter/flutter/issues/13937#issuecomment -667314232

Je n'essaie pas de trouver une solution pour cette nouvelle architecture légère, mais si vous me le demandez, je peux penser à deux variantes de solution légère :

  1. Faites en sorte que les canaux de méthode ressemblent un peu aux sockets de serveur en leur faisant accepter les connexions de plusieurs clients. Ainsi, un canal de méthode du côté plate-forme du plug-in pourrait accepter les connexions de plusieurs isolats, puis une fois connecté, il pourrait envoyer des messages au bon isolat.
  2. Créez un nouveau registre de plugins, etc. pour chaque isolat MAIS dans le même FlutterEngine,

si vous me demandez

Oui, je demande

pourrait envoyer des messages au bon isolat.

Je pense que c'est une mauvaise solution car "au bon isolat" est un concept relatif

Créez un nouveau registre de plugins, etc. pour chaque isolat MAIS dans le même FlutterEngine,

Cela pourrait être une bonne option, mais seulement si le moteur enregistre toujours les plugins par lui-même.

Mais cela ne se produit que si tous les plugins du GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

La création de rappels supplémentaires n'est pas un comportement évident.

Je pense que c'est une mauvaise solution car "au bon isolat" est un concept relatif

Cela pourrait être une mauvaise solution, mais cela ne peut pas être mauvais pour cette raison. Le "bon isolat" est sans ambiguïté de la même manière qu'un socket serveur gère les connexions client de manière non ambiguë.

Cela pourrait être une bonne option, mais seulement si le moteur enregistre toujours les plugins par lui-même.

Ce qui serait une bonne chose de s'assurer qu'il arrive toujours. Actuellement, le mécanisme est un peu feuilleté.

Cela pourrait être une mauvaise solution, mais cela ne peut pas être mauvais pour cette raison. Le "bon isolat" est sans ambiguïté de la même manière qu'un socket serveur gère les connexions client de manière non ambiguë.

Ensuite, chaque plugin doit tenir compte du fait qu'il est accessible à partir de différents isolats - mauvais

Ouais.

Permettez-moi d'ajouter mes deux cents à cette discussion... Après avoir travaillé commercialement avec Flutter pendant environ deux ans maintenant, la chose la plus importante que j'ai apprise est que Flutter est une boîte à outils d'interface utilisateur . Il n'y aurait rien de surprenant dans cette déclaration - c'est même dit sur le site Web principal de Flutter - si les gens n'oubliaient pas cela.

Flutter est une boîte à outils d'interface utilisateur, et ce qu'il fait bien, c'est l'interface utilisateur. Si vous essayez de créer une application gourmande en CPU qui repose sur un traitement de données coûteux ou utilise des algorithmes compliqués et consomme de grandes quantités de données, vous ne devez PAS utiliser une boîte à outils d'interface utilisateur à cette fin. La logique métier ne doit pas être mélangée à l'interface utilisateur.

Bien sûr, avec Dart, nous pouvons faire bien plus qu'une simple interface utilisateur, mais n'oublions pas l'objectif principal de cette boîte à outils.

J'ai essayé un certain nombre d'approches, y compris l'utilisation de Flutter sans tête - qui s'est avérée être un désastre total. Pas parce que ça ne marche pas, mais parce que nous avons du mal à expliquer aux autres développeurs comment ça marche. Le fardeau de maintenance qu'il ajoute n'en vaut tout simplement pas la peine. Sans parler de tester une telle solution. La dernière fois que j'ai essayé, Flutter Driver ne le gérerait pas du tout.

Si votre application a vraiment besoin de la puissance de traitement, à mon humble avis, vous avez trois options :

  • déplacez votre logique vers le serveur ;
  • déplacez votre logique vers la plate-forme native - vous pouvez utiliser Kotlin Native pour cela - et utilisez les canaux Event/Method pour fournir uniquement les modèles de vue à votre interface utilisateur (je pense que c'est ce qu'OLX a fait : https://tech.olx.com/fast -prototypes-avec-flutter-kotlin-native-d7ce5cfeb5f1);
  • n'utilisez pas Flutter et recherchez un autre framework (Xamarin/Blazor pourrait être un bon choix pour cela car C # offre un environnement multithreading plus sophistiqué que Dart).

Tout ce qui précède a ses défauts, mais essayer d'étirer une bibliothèque/un cadre pour faire ce pour quoi il n'a pas vraiment été conçu n'est pas idéal non plus.

Le isolate_handler n'avait-il pas résolu ce problème ?

@hasonguo Il utilise flutter_isolate en arrière-plan, il ajoute simplement une couche supplémentaire pour faciliter la gestion des isolats (tous les isolats, en fait) en ajoutant le passe-partout pour la communication afin que vous n'ayez pas besoin de le configurer manuellement chaque temps. Il a utilisé une manière différente de résoudre le problème plus tôt, mais cela a été rendu impossible par des changements dans Flutter lui-même, et a été modifié pour s'appuyer sur flutter_isolate la place.

On tourne en rond. Oui, il existe des solutions, les deux plugins en fait. Comme Ryan l'a souligné, il y a une surcharge impliquée pour rendre les communications par canal possibles. Plus probablement qu'improbable, c'est exactement cette surcharge qui a fait que l'équipe Flutter a évité de l'ajouter automatiquement au Isolate d'origine, car vous n'avez pas besoin du support dans de nombreux (la plupart ?) cas.

Pourtant, ce n'est pas une solution aussi propre qu'elle pourrait l'être. Si l'équipe Flutter était en mesure de fournir une communication par canal sans frais généraux, directement et automatiquement, dès la sortie de la boîte avec n'importe quel Isolate base, toute la discussion serait sans objet. Cependant, l'inverse est également vrai : ils ne recherchent pas vraiment le changement pour le plaisir du changement. S'il existe une solution viable dans un package ou un plugin, pourquoi passer du temps et ajouter du poids au noyau ?

Créez un nouveau registre de plugins, etc. pour chaque isolat MAIS dans le même FlutterEngine,

Cela pourrait être une bonne option, mais seulement si le moteur enregistre toujours les plugins par lui-même.

Mais cela ne se produit que si tous les plugins du GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

La création de rappels supplémentaires n'est pas un comportement évident.

Une autre amélioration de cette deuxième idée est de faire en sorte que les plugins soient chargés paresseusement dans le nouvel isolat. Normalement, si un projet dépend de nombreux plugins, le GeneratedPluginRegistrant sera assez grand et selon l'efficacité de la routine d'initialisation d'un plugin, cela peut contribuer à certains des frais généraux. Exactement comment mettre en œuvre cela est une autre histoire. Les canaux de plate-forme n'appartiennent pas aux plugins, donc l'envoi actuel d'un message sur un canal ne peut pas être utilisé pour déclencher l'instanciation du côté plate-forme d'un plugin. Ainsi, soit les canaux de méthode devraient être associés à des plugins, soit il faudrait un mécanisme supplémentaire que le côté Dart d'un plugin pourrait appeler pour s'initialiser paresseusement avant la création du premier canal de méthode, et ce mécanisme pourrait déclencher l'instanciation du côté plate-forme de ce plugin dans l'isolat.

@TahaTesser

Dans votre exemple, l'erreur n'est pas liée à ce problème

E/flutter ( 7814): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function

Salut @nikitadol
Merci, exemple mis à jour, il ne lève pas d'exception, n'est-ce pas? (je n'ai jamais eu besoin d'utiliser avant)
Pouvez-vous s'il vous plaît fournir un échantillon de code minimal reproductible complet
Merci

@TahaTesser


exemple de code

import 'package:battery/battery.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Future<void> main() async {
  await printBatteryLevel();
  await compute(printBatteryLevel, null);
}

Future<void> printBatteryLevel([dynamic message]) async {
  WidgetsFlutterBinding.ensureInitialized();
  print(await Battery().batteryLevel);
}



journaux

Launching lib/main.dart on Pixel 4 in debug mode...
Running Gradle task 'assembleDebug'...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Waiting for Pixel 4 to report its views...
Debug service listening on ws://127.0.0.1:50709/-SPs_6AmL2Q=/ws
Syncing files to device Pixel 4...
I/flutter ( 8708): 39
E/flutter ( 8708): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: UI actions are only available on root isolate.
E/flutter ( 8708): #0      Window._nativeSetNeedsReportTimings (dart:ui/window.dart:1003:86)
E/flutter ( 8708): #1      Window.onReportTimings= (dart:ui/window.dart:996:29)
E/flutter ( 8708): #2      SchedulerBinding.addTimingsCallback (package:flutter/src/scheduler/binding.dart:272:14)
E/flutter ( 8708): #3      SchedulerBinding.initInstances (package:flutter/src/scheduler/binding.dart:209:7)
E/flutter ( 8708): #4      ServicesBinding.initInstances (package:flutter/src/services/binding.dart:27:11)
E/flutter ( 8708): #5      PaintingBinding.initInstances (package:flutter/src/painting/binding.dart:23:11)
E/flutter ( 8708): #6      SemanticsBinding.initInstances (package:flutter/src/semantics/binding.dart:24:11)
E/flutter ( 8708): #7      RendererBinding.initInstances (package:flutter/src/rendering/binding.dart:32:11)
E/flutter ( 8708): #8      WidgetsBinding.initInstances (package:flutter/src/widgets/binding.dart:257:11)
E/flutter ( 8708): #9      new BindingBase (package:flutter/src/foundation/binding.dart:59:5)
E/flutter ( 8708): #10     new _WidgetsFlutterBinding&BindingBase&GestureBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #11     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #12     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #13     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #14     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #15     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #16     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #17     new WidgetsFlutterBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #18     WidgetsFlutterBinding.ensureInitialized (package:flutter/src/widgets/binding.dart:1229:7)
E/flutter ( 8708): #19     printBatteryLevel (package:bug/main.dart:11:25)
E/flutter ( 8708): #20     _IsolateConfiguration.apply (package:flutter/src/foundation/_isolates_io.dart:83:34)
E/flutter ( 8708): #21     _spawn.<anonymous closure> (package:flutter/src/foundation/_isolates_io.dart:90:65)
E/flutter ( 8708): #22     Timeline.timeSync (dart:developer/timeline.dart:163:22)
E/flutter ( 8708): #23     _spawn (package:flutter/src/foundation/_isolates_io.dart:87:35)
E/flutter ( 8708): #24     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:304:17)
E/flutter ( 8708): #25     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 8708): 



médecin flottant -v

[✓] Flutter (Channel stable, 1.20.4, on Mac OS X 10.15.6 19G2021, locale en-BY)
    • Flutter version 1.20.4 at /Users/nikitadold/development/flutter
    • Framework revision fba99f6cf9 (2 weeks ago), 2020-09-14 15:32:52 -0700
    • Engine revision d1bc06f032
    • Dart version 2.9.2

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
    • Android SDK at /Users/nikitadold/Library/Android/sdk
    • Platform android-30, build-tools 30.0.0
    • Java binary at: /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.0.1, Build version 12A7300
    • CocoaPods version 1.9.3

[!] Android Studio (version 4.0)
    • Android Studio at /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] IntelliJ IDEA Ultimate Edition (version 2020.2.2)
    • IntelliJ at /Users/nikitadold/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
    • Flutter plugin installed
    • Dart plugin version 202.7319.5

@TahaTesser
s'il vous plaît voir ce commentaire

Les messages de plate-forme ne sont pris en charge qu'à partir de l'isolat principal. [...]

Ce comportement est _attendu_ et a pour étiquette severe: new feature
ce problème doit être considéré comme une _demande de fonctionnalité_ ou une _ proposition _ et non comme un _bug_

@iapicca

Dans votre exemple, l'erreur n'est pas liée à ce problème

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

@iapicca

Dans votre exemple, l'erreur n'est pas liée à ce problème

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

tu as raison le mien n'est pas un bon exemple, merci de l'ignorer

Cette page vous a été utile?
0 / 5 - 0 notes