Flutter: No se puede llamar a un método de canal de plataforma desde otro aislado

Creado en 5 ene. 2018  ·  133Comentarios  ·  Fuente: flutter/flutter

Cuando trato de invocar un método de canal de plataforma desde un aislado generado personalizado, la aplicación falla gravemente (tanto en iOS como en Android). Estoy tratando de averiguar si esto se espera o no.

Si no es así, probablemente valga la pena mencionarlo en alguna parte.

De todos modos, creo que esto puede ser potencialmente una fuerte limitación. ¿Hay alguna manera de poder llamar a los complementos de la plataforma desde un aislamiento secundario?

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

Comentario más útil

Tengo varios casos de uso, por ejemplo:

  • Descargue y analice una gran cantidad de datos (los métodos dart:convert no son asíncronos), luego guárdelos en una base de datos sqlite usando el complemento sqflite (que usa enlaces de plataforma);
  • Precargar y analizar datos antes de entregarlos a la interfaz de usuario;
  • Cifrar/descifrar y leer/escribir datos de/a un archivo (las cosas criptográficas se realizan a través de un canal de método para usar las bibliotecas de seguridad de la plataforma);

En general, puede suceder que use dependencias que declaran algunos métodos públicos que realmente no sabe si eventualmente usarán código específico de la plataforma para lograr sus cosas (estoy pensando en flutterfire, por ejemplo); usarlos en mi humilde opinión debería ser más un detalle de implementación que puede cambiar con el tiempo en lugar de algo escrito en piedra.

Su solución parece estar bien por el momento y relativamente fácil de implementar, ya que los datos ya están codificados de manera que se pasen al canal del método y, por esta razón, pasarán fácilmente también a través de un puerto aislado, sin embargo, supongo que el rendimiento sería subóptimo. .

Sin embargo, me quedé un poco atascado durante la implementación: ¿puede proporcionar un ejemplo de una manera fácil de invocar un método estático en el aislamiento principal de uno secundario?

Gracias

Todos 133 comentarios

CC @mravn-google para clasificación

El código del motor anula la referencia a nulo y se bloquea al intentar enviar un mensaje de plataforma desde el aislamiento secundario. Todavía no he señalado exactamente dónde; Tengo una lápida, pero necesito aprender a interpretar tal cosa.

Como solución alternativa (torpe), el aislamiento secundario podría pedirle al principal que envíe el mensaje.

@sroddy ¿Puedo preguntar qué está tratando de lograr con el aislamiento secundario?

Tengo varios casos de uso, por ejemplo:

  • Descargue y analice una gran cantidad de datos (los métodos dart:convert no son asíncronos), luego guárdelos en una base de datos sqlite usando el complemento sqflite (que usa enlaces de plataforma);
  • Precargar y analizar datos antes de entregarlos a la interfaz de usuario;
  • Cifrar/descifrar y leer/escribir datos de/a un archivo (las cosas criptográficas se realizan a través de un canal de método para usar las bibliotecas de seguridad de la plataforma);

En general, puede suceder que use dependencias que declaran algunos métodos públicos que realmente no sabe si eventualmente usarán código específico de la plataforma para lograr sus cosas (estoy pensando en flutterfire, por ejemplo); usarlos en mi humilde opinión debería ser más un detalle de implementación que puede cambiar con el tiempo en lugar de algo escrito en piedra.

Su solución parece estar bien por el momento y relativamente fácil de implementar, ya que los datos ya están codificados de manera que se pasen al canal del método y, por esta razón, pasarán fácilmente también a través de un puerto aislado, sin embargo, supongo que el rendimiento sería subóptimo. .

Sin embargo, me quedé un poco atascado durante la implementación: ¿puede proporcionar un ejemplo de una manera fácil de invocar un método estático en el aislamiento principal de uno secundario?

Gracias

@sroddy Gracias por proporcionar la información de fondo.

Acerca de la invocación de métodos estáticos en el aislado principal: no se admite directamente y debe implementarse mediante puertos. Este paquete puede proporcionar esa funcionalidad, por ejemplo aquí . Aunque no lo he probado yo mismo.

Pero deberíamos buscar una solución más general aquí, una que funcione también cuando la comunicación del canal de la plataforma se realiza como parte de la implementación de un complemento u otra biblioteca.

Todavía no tenemos los ganchos para esto, pero si podemos suponer por un momento que tiene un conjunto conocido de nombres de canales que se usarán desde el aislado secundario y no desde el principal, debería poder genéricamente Vuelva a configurar el manejo de mensajes de la plataforma para esos canales, implementando de manera transparente el reenvío necesario de mensajes binarios y respuestas entre sus dos aislados. Los complementos nunca notarían la diferencia.

Solución esbozada a continuación.

Suponga que configura los puertos M1 y R1 en su aislado principal y M2, R2 en su aislado secundario. M para mensaje, R para respuesta.

  • En su aislado secundario, use BinaryMessages.setMockMessageHandler para cada canal para reenviar mensajes para la plataforma a M1 (transparentemente al complemento que usa BinaryMessage.send con ese canal). Almacene las devoluciones de llamada de respuesta y configure R2 con un controlador que invoque el correcto al recibir la respuesta del mensaje. Configure M2 con un controlador que reenvíe a BinaryMessages.handlePlatformMessage . Implemente la devolución de llamada de respuesta allí para reenviar las respuestas a R1.
  • Simétricamente en su aislamiento principal. Configure M1 con un controlador que reenvíe mensajes para la plataforma a BinaryMessages.send . Establezca una devolución de llamada de respuesta que reenvíe las respuestas desde la plataforma al R2. También llame a BinaryMessages.setMessageHandler para cada uno de los canales para configurar un controlador de mensajes entrantes desde la plataforma que reenvía a M2. Almacene las devoluciones de llamada de respuesta y configure R1 con un controlador que invoque el correcto al recibir una respuesta del aislado secundario.

@Hixie , sugiero pasar al siguiente hito. Parece que existe una solución. Una buena solución requerirá algo de trabajo de diseño e iteración de la API.

Tengo el mismo problema, alguna noticia sobre este tema?

@ mravn-google ¿Tiene alguna actualización sobre esto? Me encontré con el mismo problema (necesito generar, por ejemplo, un par de claves RSA 2048 en el lado de la plataforma, lo que lleva un tiempo en dispositivos más antiguos o cifrar/descifrar datos). Prefiero evitar la creación de subprocesos o servicios, ya que eso duplicaría mi trabajo en el lado de la plataforma, ya que se implementará para las plataformas Android e iOS. ¿Tenemos alguna manera fácil de ejecutar esas operaciones específicas de la plataforma de forma asíncrona desde Dart? Su solución parece estar bien, sin embargo, parece que tengo algunos problemas para implementar eso, ya que soy bastante nuevo en Flutter y Dart (no estoy seguro de cómo configurar un controlador que reenvíe mensajes para un Isolate, etc.), lo siento sobre eso.

Tengo el mismo problema.

Necesito generar un PDF en el lado de Android/iOS, lo que lleva un tiempo y el método de computación (...) bloquea la aplicación si ejecuto una llamada desde allí.

Tengo este problema, y ​​ahora estoy atascado.

Quiero leer accessToken, desde las preferencias compartidas, en el aislamiento de fondo, aunque la aplicación no está en primer plano (es posible que el aislamiento principal no se esté ejecutando, no estoy muy seguro).

Entonces, ¿alguien puede decirme una solución para leer datos persistentes del aislamiento de fondo?
Estoy realmente en problemas ahora.

/cc @bkonyi Esto parece estar relacionado con algo en lo que estás trabajando.

Tengo el mismo problema. nuestra base de productos en CPU-bound. Necesitamos usar el método de la plataforma de forma aislada. ¿Podría decirnos otra forma de resolver este problema?

@ Thomson-Tsui Tuve un problema similar y, en base a una destilación del trabajo de @bkonyi y su muestra de FlutterGeofencing, logré que algo funcionara en un aislamiento que estoy usando con flutter_blue y otros complementos. Es bastante no probado, pero por favor pruébalo.

Tengo el mismo problema, creo que es importante.

Ha resuelto alguien este problema?

Necesito reproducir audio a intervalos variables en segundo plano. La forma más sencilla de hacerlo sería tener un Isolate separado que administre el audio, pero sin esta función, se necesitará un trabajo realmente extraño con los puertos.

Supongo que cuando ejecuta un aislado de dart, las bibliotecas nativas no se vinculan.

Por ejemplo, cuando se ejecuta código flutter desde Java, este método se llama:

   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
   );

En el que sucede toda la magia)

Esto se verifica por el hecho de que si creas varios FlutterNativeView, y en cada ejecución:

  public void runFromBundle (FlutterRunArguments args)

luego obtenemos algunos "aislados de dardos"

También nos enfrentamos a este problema. Necesitamos recopilar datos de antecedentes aislados (por ejemplo, ubicación, ruido, etc.) que dependen de los canales de la plataforma. @mravn-google: ¿alguna noticia sobre una actualización de la API que pueda solucionar este problema?

Es realmente un problema grave en aplicaciones de herramientas o medios.
El hilo de la plataforma no es una manera fácil.

También me he enfrentado a un problema similar y me he quedado atascado en este momento.

+1 (mi proyecto también necesita canales de plataforma aislados)

Necesito renderizar imágenes en segundo plano. Tiene que hacerse de forma aislada porque cada imagen tarda unos segundos y bloquea la interfaz de usuario si se hace en el hilo principal. Estoy atascado en la primera llamada nativa de dart:ui.PictureRecorder y asumo que todas y cada una de las llamadas gráficas (que usan funciones nativas) tampoco funcionarán. Dado que estoy usando muchas funciones en el lienzo, sería una molestia trabajar con puertos de devolución de llamada.

Una solución es muy apreciada.

+1 tengo este problema

Parece que @mravn-google dejó de funcionar para Google en Aarhus. Me pregunto si hay alguien más del equipo de Google Flutter que esté investigando esto. En mi opinión, este es un obstáculo importante para el uso serio de Flutter para crear aplicaciones de Flutter resistentes y robustas....

¿Quizás @sethladd puede dar un estado?

+1

alguna actualización sobre esto?

+1

El día en que te encontraste con un error que sabes cómo resolver por tu cuenta, pero solo esperar es la última esperanza hasta que me convierta en ingeniero de aleteo.

Necesitamos una explicación más detallada de la solución para los nuevos usuarios de flutter/dart. Solo comencé a usar aislamientos hace dos días, y aleteo/dardo la semana pasada, y parece que voy a tener que usar el subproceso principal para algunas tareas que requieren mucha mano de obra, lo que hace que aprender aleteo sea una mala solución. Sin subprocesos múltiples utilizables, es mejor aprender Kotlin y compilar mis aplicaciones dos veces. Espero que todos puedan obtener algo que sea inteligible para los principiantes, realmente me gustaría poder justificar el aprendizaje de flutter ante mis empleadores para poder usarlo en el trabajo.
Si no está aquí, tal vez en StackOverflow, publiqué una pregunta aquí: https://stackoverflow.com/q/57466952/6047611

+1 espero que se solucione pronto

+1 Mi proyecto necesita canales de plataforma en aislamientos.

Me encontré con este mismo problema e hice un paquete ( Isolate Handler ) usando una solución similar a la publicada por @mravn-google. Sin embargo, Isolate Handler le permite usar canales tanto del aislado principal como de otros aislados. Admite llamadas directas MethodChannel desde aislados, pero actualmente no se admiten las transmisiones EventChannel . Buscaré agregar soporte para ellos si resulta ser algo que la gente necesita.

Una desventaja de la solución es la necesidad de proporcionar nombres de canales al controlador aislado. No es un problema en mi proyecto, ya que estoy escribiendo mi código nativo yo mismo, pero de lo contrario, debe buscar en el código fuente de cualquier complemento que esté utilizando para encontrarlos.

Otra opción que ya se publicó anteriormente es FlutterIsolate , que utiliza un enfoque diferente y también vale la pena analizarlo.

Con suerte, los desarrolladores de Flutter brindarán una solución completa y adecuada a este problema pronto.

Editar:

Eventualmente resolví mi problema de manera más limpia al iniciar el aislamiento desde el lado nativo en lugar de usar un enfoque similar al complemento Flutter First-party Android Alarm Manager .

Lo único que tenía que hacer era convertir mi código nativo en complementos para que pudieran registrarse con la aplicación nativa, que fue una migración relativamente sencilla. Si usa complementos de pub.dev, es aún más simple, ya que deberían funcionar sin problemas.

+1 alguna actualización aquí?

+1

Cosa interesante que estoy notando aquí:
Varios complementos parecen estar escritos con la suposición de que la llamada del canal del método está pasando mensajes de Dart a un hilo de fondo; relacionado, hay algunas personas muy confundidas que siguieron el ejemplo de bg geofencing y ahora se preguntan cómo volver al hilo principal...
Ese código de complemento no causará "bloqueo" si están realizando una tarea corta, pero se ejecutan de manera predeterminada en el subproceso de la plataforma , lo que solía llamarse "el subproceso de UI" en Android; esto puede causar que los gestos se eliminen durante una gran carga de trabajo, y entiendo que también bloquearía todos los demás canales de mensajes.
Entonces estás a merced de la ingeniería del autor del plugin; solo si movieron su trabajo pesado a otro subproceso en el código nativo, ejecutará un código que espera en los canales del método; esos canales de mensajes tienen que ejecutarse en el subproceso de la plataforma.
Si bien entiendo el fundamento que el buen chinmaygarde relata aquí , está de acuerdo en que:

  • la mayoría de las llamadas a la API de la plataforma son rápidas y, a menudo, deben realizarse en el hilo principal;
  • tiene más sentido tener en cuenta los subprocesos en el código nativo
  • encontrar el camino de regreso al "hilo principal" en una anulación del método de marco es obviamente confuso para las personas acostumbradas a Android Java

    • Solo puedo imaginar cómo se multiplicaría eso al hacer sus primeras llamadas API de solo subprocesos de plataforma.

Siento que la arquitectura resultante abrió un antipatrón significativo como predeterminado para los autores de complementos; a primera vista, creo que incluso el complemento firebase ml usa el subproceso de la plataforma para su procesamiento.
Así que desearía que hubiera algún tipo de advertencia fuerte que se les diera a las personas que realizan trabajos pesados; tal vez algo en la aplicación de ejemplo predeterminada que genera errores en pantalla si las llamadas del canal de mensajes tardan más de ~ 20 ms en regresar, tal vez algo más en la cara de todos; honestamente, lo que sea que convenza a esas personas que hacen llamadas de db, etc. ir a buscar un hilo ya.

Oi, no hay almuerzo gratis, eso es seguro...

Terminé implementando una gran carga de trabajo con los subprocesos de la plataforma nativa usando Java y Swift. No se encontró ninguna solución.

@AndruByrne De hecho, esta es información muy interesante y no estaba al tanto, tan obvio como ahora parece en retrospectiva. Ya tengo un problema relacionado con mi complemento en el que un usuario intentaba usarlo como una forma de realizar una tarea pesada en segundo plano y bloqueó la interfaz de usuario.

Acepto que debería haber una advertencia y agregaré una a mi complemento.

+1 necesito esto

Desafortunadamente, Flutter no está listo para el horario de máxima audiencia por carecer de algo tan básico pero tan crítico. No todas las aplicaciones de Flutter pueden salirse con la suya con un simple async/await. Para el trabajo pesado, los aislamientos son esenciales y los complementos que no pueden permitir llamadas de método de canal de plataforma desde otro aislamiento los hacen inútiles y redundantes. Xamarin tenía esto integrado en el marco desde el día 1; seguramente esto debería ser votado para una solución ahora.

+1 necesito esto

+1

¡También necesito llamar a un método de canal de plataforma desde otro aislado!

+1

+1

+1

+1 Esto es imprescindible

+1
Tengo un SDK nativo al que debo llamar. Sería bueno tener algo como BackgroundFlutterMethodChannel .

+1

+1

¿Alguien tiene alguna información sobre lo que _crashes badly_ realmente describe? Estoy trabajando en una prueba de concepto que implementa una biblioteca nativa y me encuentro con tiempos de espera de vigilancia (0x8badf00d) y otros errores extraños al enviar mensajes de ida y vuelta a través de un canal de método.

Sospecho que esto se debe a que algunos métodos [y resultados] se invocan desde otros subprocesos, pero no he podido hacer ningún progreso para identificar el problema exacto.

+1

+1

+1

+1

+1 necesito esto

¡+1 también necesito esto!

Creo que este problema debe priorizarse como mínimo como P2.
Los usos de los canales de la plataforma que se me ocurren son

  • Para llamar a las API específicas de la plataforma
  • Para ejecutar tareas pesadas específicas de la plataforma en segundo plano e informar cuando haya terminado

Y este es un gran bloqueador.

El complemento flutter_downloader maneja el código de aislamiento de fondo. Creo que vale la pena mirarlo.

Esperaba que esto se resolviera en la próxima versión, ¡pero el error aún permanece en la versión 1.12!

nuestro código del lado del cliente está fuertemente en el otro lado del canal y está escrito en kotlin y la aplicación casi se atasca para cada conexión http individual

Goooooooooooooogle?!!!!!!!!!!!!?!?!?!?

Este complemento podría ser útil flutter_isolate

+1 necesitamos esto...vamos

+1

El mayor problema para mí es que el hecho de que la comunicación entre Dart y las plataformas nativas ocurre en el hilo principal: es imposible enviar grandes cantidades de datos sin retrasar la interfaz de usuario o escribir un código engorroso en los datos de la página.

+1 necesito esto

escribir código engorroso para datos de página.

@lukaszciastko , ¿qué quiere decir con escribir código en los datos de la página? ¿Hay alguna forma de resolver el retraso de la interfaz de usuario al enviar grandes cantidades de datos?

@YaredTaddese

Dependiendo de la complejidad de lo que hace su ISOLATE y la intercomunicación entre su UI e ISOLATE, es posible solucionar este problema en su código de UI al estructurar qué datos requiere ISOLATE para hacer su trabajo por adelantado antes de invocar ISOLATE. Tengo una generación de PDF enriquecida en mi aplicación Flutter actual: un usuario puede seleccionar varios PDF para generar.

Cada uno puede generar de 1 a 10.000 páginas. Mi interfaz de usuario responde, cuando cada trabajo se completa, se crea una TAB que muestra el informe: el usuario recibe una notificación cuando se completa un trabajo a través de TOAST y la interfaz de usuario no se detiene; el usuario no sabe que hay servicios en segundo plano ocupados. generar PDF enriquecidos.

Tenga en cuenta que mi generación de PDF es un AISLADO. En ISOLATE, también necesito descargar imágenes de CLOUD FIRESTORE e incrustarlas en un PDF; todo esto sucede a la perfección.

@MsXam pero no puedo llamar a una función específica de la plataforma desde otro aislado... lo que me hace llamar a la función específica de la plataforma en el aislado principal... lo que conduce a una interfaz de usuario retrasada

Tengo el mismo problema, creo que es muy importante.

@YaredTaddese

Dependiendo de la complejidad de lo que hace su ISOLATE y la intercomunicación entre su UI e ISOLATE, es posible solucionar este problema en su código de UI al estructurar qué datos requiere ISOLATE para hacer su trabajo por adelantado antes de invocar ISOLATE. Tengo una generación de PDF enriquecida en mi aplicación Flutter actual: un usuario puede seleccionar varios PDF para generar.

Cada uno puede generar de 1 a 10.000 páginas. Mi interfaz de usuario responde, cuando cada trabajo se completa, se crea una TAB que muestra el informe: el usuario recibe una notificación cuando se completa un trabajo a través de TOAST y la interfaz de usuario no se detiene; el usuario no sabe que hay servicios en segundo plano ocupados. generar PDF enriquecidos.

Tenga en cuenta que mi generación de PDF es un AISLADO. En ISOLATE, también necesito descargar imágenes de CLOUD FIRESTORE e incrustarlas en un PDF; todo esto sucede a la perfección.

¿Estás llamando a un código específico de la plataforma?

¿Alguien tiene una solución para llamar a los canales de la plataforma desde un aislamiento? Derrota la idea de hacerlo desde el subproceso de la interfaz de usuario.

Estoy atascado en lo mismo.

Estoy llamando a un canal de método con un código kotlin y la API getStream.io, pero eso es muy lento, y mi interfaz de usuario de Flutter se congela, quería agregarle cómputo, pero obtuve los mismos errores.

¿Cómo puedo arreglar esto?

Acabo de encontrarme con esto también. Estoy creando un prototipo de una nueva aplicación, y realmente este problema podría ser un problema para mí y hacer que busque otras soluciones que no sean de Flutter, lo que me entristecerá.

Como indica el volumen de respuestas a este problema, este es un problema IMPORTANTE y prácticamente derrota el caso de uso principal de los aislamientos en el contexto de una aplicación Flutter.

Acabo de encontrarme con esto también. Estoy creando un prototipo de una nueva aplicación, y realmente este problema podría ser un problema para mí y hacer que busque otras soluciones que no sean de Flutter, lo que me entristecerá.

Como indica el volumen de respuestas a este problema, este es un problema IMPORTANTE y prácticamente derrota el caso de uso principal de los aislamientos en el contexto de una aplicación Flutter.

sí, este es un gran problema, debe solucionarse lo antes posible

Llevo 10 días aprendiendo flutter y hasta ahora me encanta, pero después de experimentar un rato, empiezas a tener problemas y ves las desventajas, y ESTE número, abierto desde el 5 de enero de 2018, uf...

Por favor corrija este error.

Espero apoyar pronto.

@jpsarda , @klaszlo8207 No aguantes la respiración. Y sí, *había una solución al problema, hasta hoy: https://pub.dev/packages/isolate_handler. El programador tuvo que desconectarlo hoy porque un cambio muy reciente en Flutter (solo unos pocos días) hizo que ya no pudiera seguir esa ruta.

Sin embargo, debe saber que esto no significa realmente paralelismo. Podría haber aparecido de esa manera y el aspecto es importante en las aplicaciones móviles, pero el código de la plataforma se ejecuta en el subproceso principal, de todos modos, por lo que delegar una tarea larga del aislamiento al subproceso principal realmente no logró nada.

Sería bueno que Dart maneje el subprocesamiento múltiple de manera predeterminada cada vez que use una función asíncrona.

@spiderion Async nunca tuvo la intención de ser multiproceso, al igual que en muchas otras plataformas. No me malinterpreten, no diré que la limitación del canal de la plataforma no es un gran problema porque lo es (mi aplicación había estado funcionando con el complemento que mencioné hasta ayer y ahora tengo que buscar soluciones), pero async/ await nunca se trató de subprocesos múltiples desde el primer día.

@zoechi Nos encantaría saber si hay alguna actualización sobre este problema o si hay algún plan para solucionarlo.
Gracias.

@spiderion Realmente no pueden arreglarlo, se deduce de la forma en que funcionan los aislamientos. No tienen un motor de interfaz de usuario detrás de ellos, por lo tanto, no hay comunicación de canal de plataforma. Sin embargo, hay dos plugins para ayudarte:

  • https://pub.dev/packages/flutter_isolate proporciona un aislamiento de reemplazo que puede comunicarse con los complementos porque crea su propio respaldo de interfaz de usuario (nada que vea o con lo que tenga que lidiar, solo técnicamente),

  • https://pub.dev/packages/isolate_handler que acabamos de modificar para confiar en el paquete anterior porque la forma anterior se hizo imposible debido a un cambio reciente de Flutter. La ventaja de usar este paquete en lugar de flutter_isolate en sí mismo es que agrega capacidades de manejo, puede iniciar varios aislamientos, realizar un seguimiento de ellos y no tiene que configurar su propia comunicación entre el aislado y el principal. hilo (algo que tiene que hacer manualmente con el stock original Isolate y FlutterIsolate ) porque se abstrae y está fácilmente disponible.

Yo uso el segundo con perfecto éxito. En realidad, lo he estado usando durante bastante tiempo y cuando llegó el cambio de interrupción de Flutter, ayudé a su programador a pasar a la nueva solución solo porque mi aplicación también se estropeó. :-)

Por lo tanto, no creo que sea razonable esperar la solución desde el núcleo, especialmente porque la mayoría de los casos de uso no requieren comunicación de canal de plataforma, por lo que el motor de IU de respaldo no es algo que deseen agregar a cada aislado. Simplemente use los complementos existentes cuando necesite esa funcionalidad adicional.

Hola @deakjahn , gracias por tu rápida respuesta.

Las soluciones que ha proporcionado parecen ser muy útiles. Sin embargo, no sé si podría resolver mi problema. Actualmente tengo una situación en la aplicación en la que el usuario crea una historia, digamos similar a Instagram. La aplicación, en ese caso, debe cargar en el almacenamiento de Firebase una lista de videos y archivos que pueden tardar mucho tiempo en cargarse (alrededor de 15 minutos si la conexión es lenta) y luego de que se hayan cargado los archivos, la aplicación debe crear un documento en la nube de firebase, Firestore. El problema que encuentro es que si el usuario elimina la aplicación mientras se cargan los archivos, la tarea completa no se ejecuta por completo.

Esa no es realmente la responsabilidad del aislado, simplemente hace lo que le dices que haga. Firebase puede reanudar las descargas detenidas, por lo que puedo ver, aunque nunca lo usé.

Estoy tratando de ejecutar el ejemplo de getBatteryLevel:
https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab
y se bloquea cada vez que intento ejecutarlo en un dispositivo Android. ¿Puede ayudarme alguien, por favor? Ni siquiera estoy seguro de cómo verificar el error, ya que no hay registros y sigue ejecutándose en un bucle infinito y estoy usando exactamente el mismo código.

Etiquetando este problema para P5 ??? 🥵

Por favor, echa un vistazo a los comentarios anteriores. Parece muy poco probable que esto cambie en el marco porque requiere mecanismos que la mayoría de los aislamientos no necesitan, por lo que no tiene sentido agregar esa sobrecarga. Para los casos en los que sí lo necesites, existen paquetes para solucionarlo.

A los usuarios no les importa el mecanismo; lo difícil que es o no. Solo necesitan resultados. Esto podría ser imposible de hacer. Pero si los usuarios lo necesitan, Flutter debería proporcionarlo...

Entiendo que la implementación tiene limitaciones y puede llevar mucho tiempo agregar esta función al aislado existente o crear una nueva API con esta función. Solo creo que esto debería tener una prioridad más alta que P5.

Además, leí todos sus comentarios y me parece que solo está promocionando el paquete que creó.

No, no fui yo quien lo creó, pero sí acepté su propiedad hace unas semanas, sí. No creo que se haya mantenido en secreto en este hilo. Además de eso, mencioné dos paquetes (en una sola publicación, en realidad, solo una vez) que pueden proporcionar lo que busca y no tengo afiliación con el segundo más que confiar en su excelente trabajo realizado hasta ahora.

Si lo lees, la única razón por la que los usé en primer lugar fue que necesitaba la misma funcionalidad que pediste. Y mis aplicaciones han estado confiando en él durante más de un año, sin problemas. Además, familiarizarme más con el funcionamiento interno de los aislados de lo que lo estaría un desarrollador ocasional que solo los usaría (además de que comencé a trabajar en su contraparte en Flutter Web), me hace pensar que lo que escribí era cierto: esta funcionalidad no será incorporado en el marco porque hay bastantes argumentos en contra de su inclusión. No porque tomaría toneladas de tiempo, en absoluto. Tomaría relativamente poco tiempo. Simplemente porque agregaría una complejidad de back-end a todos los aislamientos que solo algunos de ellos realmente necesitarían y usarían.

@deakjahn, gracias por estos consejos, ¿el uso de isolated_handler me ayudará a invocar un método y enviar datos al lado del dardo cada vez que reciba una notificación en el lado nativo? (Estoy usando twilio sdks nativos y recibo notificaciones automáticas en el lado nativo)

Si esto significa que tiene un complemento de plataforma que usa para comunicarse a través de un canal de plataforma, entonces sí, cualquiera de los dos paquetes ayudaría. Si funciona a partir de un código normal, estos paquetes también harán que funcione desde un código aislado.

@deakjahn ¡ Gracias por la rápida respuesta! Mi problema es que no pude hacerlo con un canal de plataforma regular, no estoy seguro, pero creo que el twilio sdk maneja la escucha de notificaciones automáticas en un aislamiento separado y no en el hilo principal, y creo que ese es mi problema. Quería saber si funcionaría si invoco el método cada vez que recibo una notificación usando uno de los 2 paquetes, muchas gracias. (Estoy considerando cambiar a aplicaciones nativas si esto no es posible)

Ni siquiera sé qué es Twilio (OK, lo busqué en Google :-)), así que realmente no puedo estar seguro. Pero en cualquier caso, si desea interactuar con cualquier código nativo, debe usar un canal de plataforma de todos modos. O usa un complemento ya existente que alguien creó para usar este Twilio o lo crea usted mismo (en cuyo caso, crea prácticamente el mismo complemento, simplemente no lo publique y haga referencia a él localmente), o simplemente copie el complemento relevante codifique en su propia aplicación (no será una dependencia externa, pero aparte de eso, tendrá prácticamente el mismo código de todos modos).

Y, si tiene un canal de plataforma, se aplica la respuesta anterior: si ya puede usar el canal de plataforma y el complemento del hilo principal de una aplicación Flutter normal, entonces puede usar el mismo desde un aislado usando cualquiera de los dos paquetes

Entonces, este problema existe desde el 5 de enero de 2018 y aún no es posible con un esfuerzo adicional. Las soluciones proporcionadas son buenas, pero todavía no hay nada que funcione en el escritorio. Entonces, para cualquiera que quiera probar qué tan bien funciona flutter en el escritorio y ver si es posible que un producto futuro se quede totalmente atascado aquí.
Casi cualquier aplicación necesita aislamientos, porque siempre hay algunos cálculos enormes, ¿cómo es posible que no suceda nada en fallas de diseño de Api tan importantes?

He agregado una salida para esto, en mi complemento Ventana de alerta del sistema

La solución se menciona aquí Aislar la comunicación .
Aunque el ejemplo proporcionado habla específicamente del complemento de la ventana de alerta del sistema, se puede replicar fácilmente para otros complementos. ¡Y no requiere otros complementos sofisticados para que funcione!

Esto todavía puede ser bastante problemático. Significa que no hay forma de llamar a un complemento directamente desde un aislado que se ejecuta en segundo plano o en primer plano, porque tendría que enviar un mensaje al alcance de la aplicación para poder hacer cualquier cosa con un complemento. Entonces, si su aplicación no se está ejecutando, no hay forma de hacer nada con un complemento en el fondo.

Entonces, por ejemplo, si desea hacer algo con las preferencias compartidas en el backgroundMessageHandler del complemento FCM cuando se cierra la aplicación, se generará una excepción MissingPluginException.

O si desea abrir la aplicación con una ventana de alerta del sistema. La ventana de alerta se está ejecutando en un aislado diferente. Por lo tanto, tendría que ejecutar el código dentro del alcance de su aplicación. Esto es problemático, porque la aplicación está actualmente cerrada.

Y probablemente hay muchos otros escenarios donde este es un gran problema.

@ michael-ottink, los escenarios que describe son precisamente aquellos en los que no debería haber ningún problema. Los complementos que ejecutan código en segundo plano generalmente instancian un FlutterEngine completamente nuevo con su propio registro de complementos y el aislamiento que se ejecuta dentro de él podrá comunicarse directamente a través de los canales de la plataforma con esos complementos.

Por cierto, eso significa que si necesita un aislado para usar complementos, la manera fácil es simplemente envolver ese aislado en un FlutterEngine y mágicamente heredará esos poderes. Esto es lo que el paquete flutter_isolate hace por usted en Android e iOS (aunque estoy de acuerdo en que sería mejor tener una solución adecuada en lugar de una solución alternativa). todas las plataformas).

@ryanheise entonces flutter_isolate lo hace por mí? Cómo realmente no puedo decir de los documentos. ¿Qué debo hacer para que funcione para mi FCM onBackgroundMessage? Entonces, ¿necesito generar un nuevo aislado en mi onBackgroundMessageHandler? Pero no puedo usar complementos allí, entonces, ¿cómo usaría flutter_isolate?

Su problema es que firebase_messaging no se ha actualizado en mucho tiempo y está muy por detrás de las últimas API de ejecución en segundo plano. Si actualizaran su complemento, ya no tendrías este problema. Solo para aclarar nuevamente, el tipo de escenario que describe es precisamente aquel en el que no debería haber un problema porque los aislamientos en segundo plano ya deberían tener acceso a los complementos si se implementan correctamente. el complemento firebase_messaging no está implementado de acuerdo con las últimas API y es por eso que no funciona para usted. Puede enviar un informe de error con ese proyecto.

Estoy en extrema necesidad de ayuda con esto. ¿Hay alguna manera de que pueda hacer esto? Mañana tengo mi primer trato serio y no puedo resolverlo.

Haga todo lo posible para asegurarse de que se carguen los complementos. No sé si siguió las instrucciones en LÉAME sobre cómo habilitar los mensajes de fondo, pero necesita crear una clase de aplicación personalizada que se conecte a la inicialización del fondo FlutterNativeView del complemento. Si no lo ha hecho, ninguno de los complementos estará disponible para su uso. Si eso todavía no funciona, puede intentar degradar su proyecto a la antigua arquitectura de complementos de estilo v1 (y correr el riesgo de romper otros complementos que requieren v2).

Configuré mi Application.kt así

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)
    }
  }
}

Es mejor publicar esto en la página de problemas de firebase_messaging porque no está relacionado con este problema.

Puedo hacer un PR, que tendrá un nuevo método 'spawnIsolate' para 'SchedulerBinding'.

Entonces será posible llamar a métodos de plataforma en este aislado.

Esto solo lo ayudará si necesita llamar a un método de plataforma y obtener una respuesta.

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();
}

Tronco

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

Puedo hacer un PR, que tendrá un nuevo método 'spawnIsolate' para 'SchedulerBinding'.

Entonces será posible llamar a métodos de plataforma en este aislado.

Esto solo lo ayudará si necesita llamar a un método de plataforma y obtener una respuesta.

SchedulerBinding.instance.spawnIsolate

¿Alguna posibilidad de probar esto? Si se ajusta a mis necesidades etc..

@Nailik
Reemplace este archivo (Actual para la versión 1.17.5)


encuadernación.dardo

```dart// Copyright 2014 The Flutter Authors. Reservados todos los derechos.
// El uso de este código fuente se rige por una licencia de estilo BSD que se puede
// encontrado en el archivo de LICENCIA.

importar ' dardo: asíncrono ';
importar ' dardo:aislar ';
importar ' dardo:datos_escritos ';
importar ' dardo: ui ' como ui;

import ' paquete:flutter/foundation.dart ';

importar 'paquete_de_activos.dart';
importar 'binary_messenger.dart';
importar 'system_channels.dart';

/// Escucha los mensajes de la plataforma y los dirige al [defaultBinaryMessenger].
///
/// El [ServicesBinding] también registra un [LicenseEntryCollector] que expone
/// las licencias encontradas en el archivo LICENSE almacenado en la raíz del activo
/// paquete e implementa la extensión de servicio ext.flutter.evict (ver
/// [desalojar]).
mezclando ServicesBinding en BindingBase {
@anular
void initInstances() {
super.initInstances();
_instancia = esto;
_defaultBinaryMessenger = createBinaryMessenger();
ventana.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
initLicenses();
SystemChannels.system.setMessageHandler(handleSystemMessage);
}

/// El [ServicesBinding] actual, si se ha creado uno.
static ServicesBinding get instancia => _instancia;
instancia estática de enlace de servicios;

/// La instancia predeterminada de [BinaryMessenger].
///
/// Esto se usa para enviar mensajes desde la aplicación a la plataforma, y
/// realiza un seguimiento de qué controladores se han registrado en cada canal para que
/// puede enviar mensajes entrantes al controlador registrado.
BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
BinaryMessenger _defaultBinaryMessenger;

/// Crea una instancia predeterminada de [BinaryMessenger] que se puede usar para enviar
/// mensajes de la plataforma.
@protegido
BinaryMessenger crearBinaryMessenger() {
volver const _DefaultBinaryMessenger._();
}

/// Manejador llamado para mensajes recibidos en [SystemChannels.system]
/// canal de mensajes.
///
/// Otros enlaces pueden anular esto para responder a los mensajes entrantes del sistema.
@protegido
@MustCallSuper
FuturohandleSystemMessage(Objeto systemMessage) asíncrono { }

/// Agrega licencias relevantes al [LicenseRegistry].
///
/// Por defecto, la implementación de [ServicesBinding] de [initLicenses] agrega
/// todas las licencias recopiladas por la herramienta flutter durante la compilación.
@protegido
@MustCallSuper
void initLicenses() {
LicenseRegistry.addLicense(_addLicenses);
}

Arroyo_addLicenses() asíncrono* {
// Usamos temporizadores aquí (en lugar de scheduleTask del enlace del programador)
// porque la capa de servicios no puede usar el enlace del programador (el programador
// el enlace usa la capa de servicios para administrar sus eventos de ciclo de vida). Temporizadores
// son lo que ScheduleTask usa bajo el capó de todos modos. La única diferencia es
// que estos se ejecutarán a continuación, en lugar de priorizarse en relación con
// las otras tareas que podrían estar ejecutándose. Usando _algo_ aquí para romper
// esto en dos partes es importante porque los aislamientos tardan un tiempo en copiarse
// datos en este momento, y si recibimos los datos en el mismo bucle de eventos
// iteración a medida que enviamos los datos al siguiente aislado, definitivamente estamos
// voy a perder fotogramas. Otra solución sería tener el trabajo todo
// Sucede en un lugar aislado, y eventualmente podemos ir allí, pero primero estamos
// Voy a ver si la comunicación aislada se puede abaratar.
// Ver: https://github.com/dart-lang/sdk/issues/31959
// https://github.com/dart-lang/sdk/issues/31960
// TODO(ianh): eliminar esta complejidad una vez que se solucionen estos errores.
completador finalrawLicenses = Completador();
Timer.run(() asíncrono {
rawLicenses.complete(rootBundle.loadString('LICENCIA', caché: falso));
});
esperar rawLicenses.futuro;
completador final> parsedLicenses = Completador>();
Timer.run(() asíncrono {
parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
});
esperar licencias analizadas.futuro;
rendimiento* corriente.fromIterable(esperar parsedLicenses.future);
}

// Esto se ejecuta en otro aislado creado por _addLicenses arriba.
Lista estática_parseLicenses(String licencias sin procesar) {
Cadena final _licenseSeparator = '\n' + ('-' * 80) + '\n';
lista finalresultado =[];
lista finallicencias = rawLicenses.split(_licenseSeparator);
para (licencia de cadena final en licencias) {
división int final = licencia.indexOf('\n\n');
si (dividir >= 0) {
resultado.add(LicenseEntryWithLineBreaks(
licencia.subcadena(0, split).split('\n'),
licencia.subcadena(dividir + 2),
));
} demás {
result.add(LicenseEntryWithLineBreaks(const[], licencia));
}
}
resultado devuelto;
}

@anular
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;
}());

}

/// Llamado en respuesta a la extensión de servicio ext.flutter.evict .
///
/// Esto es usado por la herramienta flutter durante la recarga en caliente para que cualquier imagen
/// que han cambiado en el disco se borran de los cachés.
@protegido
@MustCallSuper
anular el desalojo (activo de cadena) {
rootBundle.evict(activo);
}

Futuroengendrar aislar(
futuro opunto de entrada (mensaje T),
mensaje T, {
bool en pausa = falso,
errores bool son fatales,
SendPort al salir,
SendPort onError,
Cadena nombreDepuración,
}) {
afirmar(
_isMainIsolate,
'No se pueden hacer múltiples niveles de aislamientos',
);

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,
);

}
}

Futuro_startAislar(SendPort sendPort) asíncrono {
_sendPortToMainIsolate = enviarPort;
_IsolateBinding();

Finalizador final<_IsolateStarter> completador =
Completador<_IsolateStarter>();

Puerto de recepción sin procesar final Puerto de recepción = Puerto de recepción sin procesar (
(Objeto aislarStarter) {
afirmar(isolateStarter es _IsolateStarter);
completer.complete(isolateStarter como _IsolateStarter);
},
);

enviarPuerto.enviar(recibirPuerto.enviarPuerto);

_IsolateStarter finalaislarStarter = esperar completador.futuro;

puertorecibir.close();

función final función =
ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

función de espera (isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

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

interfaz de usuario final.CallbackHandle callbackHandle;
mensaje T final;
}

clase _Mensaje {
_Mensaje(
este.canal,
este mensaje,
este puerto de envío,
);

canal de cadena final;
mensaje final de ByteData;
Puerto de envío final Puerto de envío;
}

clase _IsolateBinding extiende BindingBase con ServicesBinding {}

/// La implementación predeterminada de [BinaryMessenger].
///
/// Este mensajero envía mensajes desde el lado de la aplicación al lado de la plataforma y
/// envía los mensajes entrantes desde el lado de la plataforma al correspondiente
/// manipulador.
clase _DefaultBinaryMessenger extiende BinaryMessenger {
const _DefaultBinaryMessenger._();

// Manejadores para mensajes entrantes de complementos de plataforma.
// Esto es estático para que esta clase pueda tener un constructor const.
Mapa final estático_manejadores =
{};

// Controladores simulados que interceptan y responden a los mensajes salientes.
// Esto es estático para que esta clase pueda tener un constructor const.
Mapa final estático_manipuladores simulados =
{};

Futuro_sendPlatformMessage (canal de cadena, mensaje ByteData) {
completador finalcompletador = completador();

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;

}

@anular
FuturomanejarPlataformaMensaje(
canal de cuerdas,
datos de ByteData,
ui.PlatformMessageResponseCallback devolución de llamada,
) asíncrono {
Respuesta de ByteData;
tratar {
controlador final MessageHandler = _handlers[canal];
si (manejador! = nulo) {
respuesta = esperar controlador (datos);
} demás {
ui.channelBuffers.push(canal, datos, devolución de llamada);
devolución de llamada = nulo;
}
} catch (excepción, pila) {
FlutterError.reportError(FlutterErrorDetalles(
excepción: excepción,
pila: pila,
biblioteca: 'biblioteca de servicios',
context: ErrorDescription('durante una devolución de llamada de mensaje de plataforma'),
));
} por fin {
si (devolución de llamada! = nulo) {
devolución de llamada (respuesta);
}
}
}

@anular
Futuroenviar (canal de cadena, mensaje ByteData) {
controlador final MessageHandler = _mockHandlers[canal];
si (controlador! = nulo)
controlador de devolución (mensaje);
return _sendPlatformMessage(canal, mensaje);
}

@anular
void setMessageHandler (canal de cadena, controlador de MessageHandler) {
si (controlador == nulo)
_handlers.remove(canal);
demás
_handlers[canal] = controlador;
ui.channelBuffers.drain(canal, (datos ByteData, ui.PlatformMessageResponseCallback callback) asíncrono {
esperar handlePlatformMessage (canal, datos, devolución de llamada);
});
}

@anular
void setMockMessageHandler (canal de cadena, controlador de MessageHandler) {
si (controlador == nulo)
_mockHandlers.remove(canal);
demás
_mockHandlers[canal] = controlador;
}
}
```

Tengo flutter 1.21, así que traté de actualizar todo el código para compilar.
Ahora queda un problema porque a _IsolateBinding le faltan muchas implementaciones de BindingBase Methods...

obtengo algo como

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

alrededor de 30 veces.

Al final la consola 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 {}

No estoy 100% seguro de qué hace la llamada de _IsolateBinding() en _startIsolate , pero sin recibir el error común ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. .

Traté de arreglar esto, pero creo que mi conocimiento de mixins aún no es tan bueno.
¿Alguna idea de cómo arreglarlo?


El nuevo archivo se ve así (hasta ahora)

// 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
Por el momento no puedo actualizar flutter para ayudarte

Me pregunto por qué las otras herramientas de flutter implementadas por el equipo de flutter (como, por ejemplo, devtools) nunca golpean este bloqueador.
Parecen estar arreglando errores solo si son bloqueados por ellos.

Esta no es una característica nueva grave, es un bloqueador de marco.
Entonces, de acuerdo con este https://github.com/flutter/flutter/issues/18761#issuecomment -639248761, debería ser un P3

Me pregunto por qué las otras herramientas de flutter implementadas por el equipo de flutter (como, por ejemplo, devtools) nunca golpean este bloqueador.
Parecen estar arreglando errores solo si son bloqueados por ellos.

Para ser honesto, porque creo que el impacto de este problema en las aplicaciones del mundo real está sobrevalorado. Solo estoy observando el problema porque facilitaría las cosas, pero hay "soluciones alternativas". Y muchas aplicaciones y complementos en producción utilizan funciones en segundo plano.

Tampoco estoy seguro de si algunos de los +1 simplemente no entienden de qué se trata el problema. (Como https://github.com/flutter/flutter/issues/13937#issuecomment-635683123). En mi opinión, todavía falta un poco la documentación oficial para los aislamientos de fondo y el tiempo de ejecución sin cabeza (y cómo administrarlo) y la mensajería de Firebase es una pesadilla, pero esto no tiene nada que ver con el marco, el motor 🤷‍♂️

Tal vez solo deje de criticar al equipo de flutter, hay un progreso increíble, y esto no es algo que no se pueda solucionar... Y si hay un PR, ¿por qué no simplemente enviarlo para que el equipo de flutter lo considere 🤔

Hay un problema con la implementación de este comportamiento para aislamientos.

Ejemplo:

Hay un aislamiento principal y un motor.
Si el aislado necesita algo, le pregunta al motor.
Si el motor necesita algo, pregunta al aislado.

Ahora nuestra situación es:
Hay 2 aislados y un motor.
Si el aislado necesita algo, le pregunta al motor.
Si el motor necesita algo, ¿qué debería hacer?
Preguntar a los dos? Entonces la respuesta de quién tomar?
¿O pedir solo un aislado? Entonces cual?

@hpoul Tengo que estar de acuerdo en no estar de acuerdo ya que muchas personas aquí son principiantes en la programación de Android/ios y eligieron su primer marco para el desarrollo de aplicaciones para que sea flutter.

Por lo tanto, es importante que alguien se dé cuenta de esto y arregle o sugiera una solución oficial con un ejemplo para este tipo de problemas en los que el marco lo bloquea y lo obliga a realizar "soluciones alternativas" complejas, ya que obviamente los principiantes no sabrán qué aprender o incluso por dónde empezar porque no hay soluciones alternativas bien explicadas en los comentarios aquí o en cualquier otro lugar en stackoverflow u otros blogs. Solo hay cosas que apuntan a algunos paquetes que pueden o no haber implementado esto correctamente.

Las personas que tienen mucha experiencia trabajando con servicios, colas de tareas en segundo plano, etc., tanto en Android como en iOS, que pueden solucionar esto fácilmente, no son el grupo demográfico objetivo de Flutter.

Y llegando a comentarios fuera de tema como ese, estoy seguro de que hay +1 falsos en todos los demás problemas, lo que no cambia mucho el orden de las prioridades del problema y este problema aún permanece en la parte superior.

Hola @phanirithvij

flutter_isolate se vinculó anteriormente y existe desde febrero de 2019. Tampoco es complejo. Copiaré el README que parece sencillo:

FlutterIsolate permite la creación de un Isolate en flutter que puede usar complementos de flutter. Crea los bits necesarios específicos de la plataforma (FlutterBackgroundView en Android y FlutterEngine en iOS) para permitir que los canales de la plataforma funcionen dentro de un aislado.

| | androide | iOS | Descripción |
| :--------------- | :----------------: | :------------------: | :-------------------------------- |
| FlutterIsolate.spawn(punto de entrada, mensaje) | :marca_de_verificación_blanca: | :marca_de_verificación_blanca: | genera un nuevo FlutterIsolate |
| flutterIsolate.pausa() | :marca_de_verificación_blanca: | :marca_de_verificación_blanca: | hace una pausa en un aislamiento en ejecución |
| flutterIsolate.resume() | :marca_de_verificación_blanca: | :marca_de_verificación_blanca: | retomó un isoalte pausado |
| flutterIsolate.kill() | :marca_de_verificación_blanca: | :marca_de_verificación_blanca: | mata a un aislado |

Digo sencillo ya que estos métodos tienen los mismos nombres que los de la clase Isolate "original", por lo que si ya sabe cómo usar aislados de la documentación oficial, no debería ser difícil entender cómo usarlo esto como un reemplazo directo, al menos para los métodos enumerados anteriormente.

(Nota: a mí también me gustaría que este problema se solucione oficialmente por las razones que ya mencioné en un comentario anterior, algunas de las cuales comparto con ustedes, pero tampoco llamaría a esta solución "compleja").

En respuesta al comentario de @nikitadol , flutter_isolate crea un motor separado para cada aislamiento para que no tenga el mismo problema. Por supuesto, no es ideal tener que crear un nuevo motor por aislado, esa es una de las razones por las que estoy seguro de que el equipo de Flutter podría hacer un mejor trabajo. Del mismo modo, la solución alternativa de canalizar todas las llamadas de métodos de complemento a través del aislado principal crearía un cuello de botella que va en contra de los objetivos de los aislados en primer lugar, por lo que estoy seguro de que el equipo de Flutter también podría hacer un mejor trabajo al respecto.

Estoy de acuerdo con aquellos que han dicho que el equipo de Flutter podría tener problemas de mayor prioridad para abordar primero, pero según lo que hemos visto antes con la arquitectura del complemento, también puede ser que arreglar esto requiera un cambio de arquitectura importante y varios otros problemas abiertos. también requieren un cambio arquitectónico importante, y sopesar todos estos diferentes requisitos podría tomar tiempo antes de que puedan abordar este problema en particular.

@ryanheise

flutter_isolate crea un motor separado para cada aislado

así que estoy seguro de que el equipo de Flutter también podría hacer un mejor trabajo al respecto.

Dices que el equipo de Flutter puede hacer esto, pero ¿cómo?
Las preguntas que hice anteriormente siguen siendo válidas.
Si la solución es crear un nuevo motor, simplemente puede usar su complemento

(Tenga en cuenta que flutter_isolate no es mi complemento, pero como también necesitaba esta función, decidí contribuir al proyecto).

Lo que quise decir con "un mejor trabajo" es que probablemente no sea necesario activar TODA la maquinaria en un FlutterEngine solo para comunicarse con complementos, pero en este momento, la infraestructura necesaria está ligada al motor, por eso actualmente flutter_isolate tiene que poner en marcha un motor completo solo para tener acceso a los complementos.

(Tenga en cuenta que flutter_isolate no es mi complemento, pero como también necesitaba esta función, decidí contribuir al proyecto).

Lo siento, no me di cuenta de que esto es un tenedor.

flutter_isolate tiene que poner en marcha un motor completo solo para tener acceso a los complementos.

Si especifica que 'is_background_view = true', entonces el motor no comienza a renderizar, lo que significa que el motor no se inicia por completo

La API precisa para ejecutar headless es diferente para iOS y Android v1 y Android v2, pero esencialmente esto es lo que ya hace flutter_isolate. Sin embargo, todavía hay una diferencia en los gastos generales entre un aislado normal y un FlutterEngine, por lo que reducir aún más los gastos generales es solo algo que el equipo de Flutter podría hacer. No es solo una sobrecarga en el tiempo de inicio, también es una sobrecarga en el uso de la memoria.

Entonces, ¿solo sugiere optimizar la creación de un nuevo motor para que funcione en segundo plano?

Entonces esto probablemente no se aplica a este problema

No estoy sugiriendo eso, solo estoy señalando que la solución flutter_isolate tiene esta limitación debido a los gastos generales que no se pueden evitar. No estoy sugiriendo que el equipo de Flutter deba intentar hacer que la solución sea más eficiente, creo que idealmente habría un cambio de arquitectura en la forma en que se cargan los complementos para que no sea necesario crear una instancia de FlutterEngine.

Si el motor es 1 y los aislamientos son mayores que 1, entonces volvemos a esta pregunta:

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

No estoy tratando de encontrar una solución para esta nueva arquitectura liviana, pero si me pregunta, puedo pensar en dos variaciones de la solución liviana:

  1. Haga que los canales de método se parezcan un poco a los sockets de servidor al hacer que acepten conexiones de múltiples clientes. Entonces, un canal de método en el lado de la plataforma del complemento podría aceptar conexiones de múltiples aislamientos, y luego, una vez conectado, podría enviar mensajes al aislamiento correcto.
  2. Cree un nuevo registro de complementos, etc. para cada aislamiento PERO dentro del mismo FlutterEngine,

si me estas preguntando

si, pregunto

podría enviar mensajes al aislado correcto.

Creo que esta es una mala solución ya que 'aislar a la derecha' es un concepto relativo

Cree un nuevo registro de complementos, etc. para cada aislamiento PERO dentro del mismo FlutterEngine,

Esta podría ser una buena opción, pero solo si el motor siempre registra complementos por sí mismo.

Pero esto solo sucede si todos los complementos en GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

La creación de devoluciones de llamada adicionales no es un comportamiento obvio.

Creo que esta es una mala solución ya que 'aislar a la derecha' es un concepto relativo

Podría ser una mala solución, pero no puede ser mala por eso. El "aislamiento correcto" es inequívoco de la misma manera que un socket de servidor maneja las conexiones de los clientes de manera inequívoca.

Esta podría ser una buena opción, pero solo si el motor siempre registra complementos por sí mismo.

Lo cual sería bueno para asegurarse de que siempre suceda. Actualmente el mecanismo es un poco escamoso.

Podría ser una mala solución, pero no puede ser mala por eso. El "aislamiento correcto" es inequívoco de la misma manera que un socket de servidor maneja las conexiones de los clientes de manera inequívoca.

Luego, cada complemento debe tener en cuenta que se puede acceder desde diferentes aislamientos: mal

Sí.

Permítanme agregar mis dos centavos a esta discusión... Después de haber trabajado comercialmente con Flutter durante aproximadamente dos años, lo más importante que aprendí fue que Flutter es un conjunto de herramientas de interfaz de usuario . No habría nada sorprendente en esta declaración, incluso lo dice en el sitio web principal de Flutter, si la gente no se olvidara de esto.

Flutter es un conjunto de herramientas de interfaz de usuario, y lo que hace bien es la interfaz de usuario. Si está tratando de crear una aplicación de uso intensivo de CPU que se basa en un procesamiento de datos costoso o utiliza algoritmos complicados y consume grandes cantidades de datos, entonces NO debe usar un kit de herramientas de interfaz de usuario para este propósito. La lógica de negocios no debe mezclarse con la interfaz de usuario.

Por supuesto, con Dart podemos hacer mucho más que solo la interfaz de usuario, pero no olvidemos el objetivo principal de este conjunto de herramientas.

Probé varios enfoques, incluido el uso de Flutter sin cabeza, que resultó ser un desastre total. No porque no funcione, sino porque tenemos problemas para explicar a otros desarrolladores cómo funciona. La carga de mantenimiento que se suma simplemente no vale la pena. Sin mencionar también probar una solución de este tipo. La última vez que lo intenté, Flutter Driver no pudo manejarlo en absoluto.

Si su aplicación realmente necesita la potencia de procesamiento, en mi humilde opinión, tiene tres opciones:

  • mueve tu lógica al servidor;
  • mueva su lógica a la plataforma nativa (puede usar Kotlin Native para esto) y use canales de eventos/métodos para proporcionar solo los modelos de vista a su interfaz de usuario (creo que eso es lo que hizo OLX: https://tech.olx.com/fast -prototipos-con-flutter-kotlin-native-d7ce5cfeb5f1);
  • no use Flutter y busque otro marco (Xamarin/Blazor podría ser una buena opción para esto, ya que C# ofrece un entorno de subprocesos múltiples más sofisticado que Dart).

Todo lo anterior tiene sus defectos, pero tampoco es lo ideal tratar de estirar una biblioteca/marco para hacer aquello para lo que no fue realmente diseñado.

¿Acaso el handler_aislante no había resuelto este problema?

@hasonguo Utiliza flutter_isolate en segundo plano, solo agrega una capa adicional para facilitar el manejo de los aislamientos (cualquier aislamiento, en realidad) al agregar el modelo para la comunicación para que no necesite configurarlo manualmente cada hora. Utilizó una forma diferente de abordar el problema anteriormente, pero eso se hizo imposible debido a los cambios en Flutter y se cambió para depender de flutter_isolate en su lugar.

Vamos en círculos. Sí, hay soluciones, ambos complementos en realidad. Como señaló Ryan, hay una sobrecarga involucrada en hacer posible las comunicaciones del canal. Lo más probable es que sea exactamente esta sobrecarga lo que hizo que el equipo de Flutter evitara agregarlo automáticamente al Isolate original, ya que no necesita el soporte en muchos (¿la mayoría?) de los casos.

Aún así, no es una solución tan limpia como podría ser. Si el equipo de Flutter pudiera proporcionar comunicación de canal sin gastos generales, directa y automáticamente, desde el primer momento con cualquier Isolate básico, toda la discusión sería discutible. Sin embargo, lo contrario también es cierto: en realidad no persiguen el cambio por cambiar. Si hay una solución viable en un paquete o complemento, ¿por qué perder tiempo y agregar peso al núcleo?

Cree un nuevo registro de complementos, etc. para cada aislamiento PERO dentro del mismo FlutterEngine,

Esta podría ser una buena opción, pero solo si el motor siempre registra complementos por sí mismo.

Pero esto solo sucede si todos los complementos en GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

La creación de devoluciones de llamada adicionales no es un comportamiento obvio.

Otra mejora a esta segunda idea es hacer que los complementos se carguen de forma perezosa en el nuevo aislado. Normalmente, si un proyecto depende de muchos complementos, el GeneratedPluginRegistrant será bastante grande y, dependiendo de cuán eficiente sea la rutina de inicialización de un complemento, esto puede contribuir a algunos de los gastos generales. Exactamente cómo implementar esto es otra historia. Los canales de la plataforma no son propiedad de los complementos, por lo que actualmente no se puede usar el envío de un mensaje a través de un canal para activar la instancia del lado de la plataforma de un complemento. Por lo tanto, cualquiera de los canales de método tendría que estar asociado con complementos, o tendría que haber un mecanismo adicional al que el lado Dart de un complemento podría llamar para inicializarse a sí mismo de forma perezosa antes de que se cree el primer canal de método, y ese mecanismo podría desencadenar la creación de instancias. del lado de la plataforma de ese complemento dentro del aislado.

@TahaTesser

En su ejemplo, el error no está relacionado con este problema

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

Hola @nikitadol
Gracias, ejemplo actualizado, no arroja una excepción, ¿es correcto? (Nunca necesité usar antes)
¿Puede proporcionar una muestra de código mínimo reproducible completa?
Gracias

@TahaTesser


ejemplo de código

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);
}



registros

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): 



aleteo doctor -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
por favor vea este comentario

Los mensajes de la plataforma solo se admiten desde el aislado principal. [...]

Este comportamiento es _esperado_ y tiene como etiqueta severe: new feature
este problema debe considerarse como _solicitud de función_ o _propuesta_ y no como _error_

@iapicca

En su ejemplo, el error no está relacionado con este problema

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

En su ejemplo, el error no está relacionado con este problema

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>':.)

tienes razón el mío no es un buen ejemplo, por favor ignóralo

¿Fue útil esta página
0 / 5 - 0 calificaciones