Fundamentos de IA | Programación | Arquitectura de Flutter

Flutter es un conjunto de herramientas de interfaz de usuario multiplataforma que está diseñado para permitir la reutilización de código en sistemas operativos como iOS y Android, al mismo tiempo que permite que las aplicaciones interactúen directamente con los servicios de la plataforma subyacente. El objetivo es permitir que los desarrolladores entreguen aplicaciones de alto rendimiento que se sientan naturales en diferentes plataformas, adoptando las diferencias donde existen mientras comparten la mayor cantidad de código posible.


Durante el desarrollo, las aplicaciones de Flutter se ejecutan en una máquina virtual que ofrece una recarga en caliente con estado de los cambios sin necesidad de una recompilación completa. Para su lanzamiento, las aplicaciones de Flutter se compilan directamente en el código de la máquina, ya sean instrucciones Intel x64 o ARM, o en JavaScript si tienen como objetivo la web. El marco es de código abierto, con una licencia BSD permisiva y tiene un ecosistema próspero de paquetes de terceros que complementan la funcionalidad de la biblioteca principal.


Esta descripción general se divide en varias secciones:


El modelo de capas: las piezas a partir de las cuales se construye Flutter.

Interfaces de usuario reactivas: un concepto central para el desarrollo de interfaces de usuario de Flutter.

Una introducción a los widgets: los bloques de construcción fundamentales de las interfaces de usuario de Flutter.

El proceso de renderizado: cómo Flutter convierte el código de la interfaz de usuario en píxeles.

Una descripción general de los integradores de la plataforma: el código que permite que los sistemas operativos móviles y de escritorio ejecuten aplicaciones de Flutter.

Integración de Flutter con otro código: información sobre las diferentes técnicas disponibles para las aplicaciones de Flutter.

Soporte para la web: Comentarios finales sobre las características de Flutter en un entorno de navegador.



Capas arquitectónicas

Flutter está diseñado como un sistema de capas extensible. Existe como una serie de bibliotecas independientes que dependen de la capa subyacente. Ninguna capa tiene acceso privilegiado a la capa inferior, y cada parte del nivel del marco está diseñada para ser opcional y reemplazable.


Para el sistema operativo subyacente, las aplicaciones Flutter se empaquetan de la misma manera que cualquier otra aplicación nativa. Un embebedor específico de la plataforma proporciona un punto de entrada; se coordina con el sistema operativo subyacente para acceder a servicios como superficies de representación, accesibilidad y entrada; y gestiona el bucle de eventos del mensaje. El embedder está escrito en un lenguaje apropiado para la plataforma: actualmente Java y C ++ para Android, Objective-C / Objective-C ++ para iOS y macOS, y C ++ para Windows y Linux. Con el incrustador, el código de Flutter se puede integrar en una aplicación existente como un módulo, o el código puede ser el contenido completo de la aplicación. Flutter incluye una serie de integradores para plataformas de destino comunes, pero también existen otros integradores.


En el núcleo de Flutter se encuentra el motor Flutter, que está escrito principalmente en C ++ y admite las primitivas necesarias para admitir todas las aplicaciones de Flutter. El motor es responsable de rasterizar escenas compuestas cada vez que se necesita pintar un nuevo marco. Proporciona la implementación de bajo nivel de la API principal de Flutter, que incluye gráficos (a través de Skia), diseño de texto, E / S de archivos y redes, soporte de accesibilidad, arquitectura de complementos y una cadena de herramientas de compilación y tiempo de ejecución de Dart.


El motor se expone al marco Flutter a través de dart: ui, que envuelve el código C ++ subyacente en las clases de Dart. Esta biblioteca expone las primitivas de nivel más bajo, como las clases para controlar los subsistemas de procesamiento de entrada, gráficos y texto.


Por lo general, los desarrolladores interactúan con Flutter a través del marco Flutter, que proporciona un marco reactivo moderno escrito en el lenguaje Dart. Incluye un amplio conjunto de bibliotecas de plataforma, diseño y base, compuesto por una serie de capas. Trabajando de abajo hacia arriba, tenemos:


Clases fundamentales básicas y servicios de bloques de construcción como animación, pintura y gestos que ofrecen abstracciones de uso común sobre la base subyacente.

La capa de renderizado proporciona una abstracción para lidiar con el diseño. Con esta capa, puede construir un árbol de objetos renderizables. Puede manipular estos objetos dinámicamente, con el árbol actualizando automáticamente el diseño para reflejar sus cambios.

La capa de widgets es una abstracción de composición. Cada objeto de renderizado en la capa de renderizado tiene una clase correspondiente en la capa de widgets. Además, la capa de widgets te permite definir combinaciones de clases que puedes reutilizar. Esta es la capa en la que se introduce el modelo de programación reactiva.

Las bibliotecas Material y Cupertino ofrecen conjuntos completos de controles que utilizan las primitivas de composición de la capa del widget para implementar los lenguajes de diseño Material o iOS.



El marco Flutter es relativamente pequeño; muchas características de nivel superior que los desarrolladores pueden usar se implementan como paquetes, incluidos complementos de plataforma como cámara y vista web, así como características independientes de la plataforma como personajes, http y animaciones que se basan en las bibliotecas principales de Dart y Flutter. Algunos de estos paquetes provienen del ecosistema más amplio, y cubren servicios como pagos dentro de la aplicación, autenticación de Apple y animaciones.


El resto de esta descripción general navega ampliamente por las capas, comenzando con el paradigma reactivo del desarrollo de la interfaz de usuario. Luego, describimos cómo los widgets se componen juntos y se convierten en objetos que se pueden renderizar como parte de una aplicación. Describimos cómo Flutter interactúa con otro código a nivel de plataforma, antes de dar un breve resumen de cómo el soporte web de Flutter se diferencia de otros objetivos.


Interfaces de usuario reactivas

En la superficie, Flutter es un marco de interfaz de usuario pseudo-declarativo reactivo, en el que el desarrollador proporciona un mapeo del estado de la aplicación al estado de la interfaz, y el marco asume la tarea de actualizar la interfaz en tiempo de ejecución cuando cambia el estado de la aplicación. Este modelo está inspirado en el trabajo que vino de Facebook para su propio marco React, que incluye un replanteamiento de muchos principios de diseño tradicionales.


En la mayoría de los marcos de IU tradicionales, el estado inicial de la interfaz de usuario se describe una vez y luego se actualiza por separado mediante el código de usuario en tiempo de ejecución, en respuesta a eventos. Un desafío de este enfoque es que, a medida que la aplicación aumenta en complejidad, el desarrollador debe ser consciente de cómo los cambios de estado se producen en cascada en toda la interfaz de usuario. Por ejemplo, considere la siguiente interfaz de usuario:



Hay muchos lugares donde se puede cambiar el estado: el cuadro de color, el control deslizante de tono, los botones de opción. A medida que el usuario interactúa con la interfaz de usuario, los cambios deben reflejarse en todos los demás lugares. Peor aún, a menos que se tenga cuidado, un cambio menor en una parte de la interfaz de usuario puede causar efectos dominó en fragmentos de código aparentemente no relacionados.


Una solución a esto es un enfoque como MVC, en el que se introducen cambios de datos en el modelo a través del controlador, y luego el modelo empuja el nuevo estado a la vista a través del controlador. Sin embargo, esto también es problemático, ya que la creación y actualización de elementos de la interfaz de usuario son dos pasos separados que pueden desincronizarse fácilmente.


Flutter, junto con otros marcos reactivos, adopta un enfoque alternativo para este problema, al desacoplar explícitamente la interfaz de usuario de su estado subyacente. Con las API de estilo React, solo crea la descripción de la interfaz de usuario, y el marco se encarga de usar esa configuración para crear y / o actualizar la interfaz de usuario según corresponda.


En Flutter, los widgets (similares a los componentes de React) están representados por clases inmutables que se utilizan para configurar un árbol de objetos. Estos widgets se utilizan para administrar un árbol de objetos separado para el diseño, que luego se usa para administrar un árbol de objetos separado para la composición. Flutter es, en esencia, una serie de mecanismos para caminar de manera eficiente las partes modificadas de los árboles, convertir árboles de objetos en árboles de objetos de nivel inferior y propagar cambios a través de estos árboles.


Un widget declara su interfaz de usuario anulando el método build (), que es una función que convierte el estado en UI:


content_copy

UI = f (estado)

Por diseño, el método build () es rápido de ejecutar y debería estar libre de efectos secundarios, lo que permite que el marco lo invoque cuando sea necesario (potencialmente tan a menudo como una vez por fotograma renderizado).


Este enfoque se basa en ciertas características del tiempo de ejecución de un lenguaje (en particular, instanciación y eliminación rápida de objetos). Afortunadamente, Dart es particularmente adecuado para esta tarea.



Widgets

Como se mencionó, Flutter enfatiza los widgets como una unidad de composición. Los widgets son los componentes básicos de la interfaz de usuario de una aplicación Flutter, y cada widget es una declaración inmutable de parte de la interfaz de usuario.


Los widgets forman una jerarquía basada en la composición. Cada widget se anida dentro de su padre y puede recibir contexto del padre. Esta estructura llega hasta el widget raíz (el contenedor que aloja la aplicación Flutter, normalmente MaterialApp o CupertinoApp).


Las aplicaciones actualizan su interfaz de usuario en respuesta a eventos (como una interacción del usuario) diciéndole al marco que reemplace un widget en la jerarquía con otro widget. Luego, el marco compara los widgets nuevos y antiguos y actualiza de manera eficiente la interfaz de usuario.


Flutter tiene sus propias implementaciones de cada control de interfaz de usuario, en lugar de diferir de las proporcionadas por el sistema: por ejemplo, hay una implementación de Dart pura tanto del control Switch de iOS como del equivalente de Android.


Este enfoque proporciona varios beneficios:


Proporciona una extensibilidad ilimitada. Un desarrollador que quiera una variante del control Switch puede crear una de cualquier forma arbitraria y no se limita a los puntos de extensión proporcionados por el sistema operativo.

Evita un cuello de botella de rendimiento significativo al permitir que Flutter componga toda la escena a la vez, sin cambiar de un lado a otro entre el código de Flutter y el código de la plataforma.

Desacopla el comportamiento de la aplicación de las dependencias del sistema operativo. La aplicación se ve y se siente igual en todas las versiones del sistema operativo, incluso si el sistema operativo cambió las implementaciones de sus controles.

Composición

Los widgets generalmente se componen de muchos otros widgets pequeños de un solo propósito que se combinan para producir efectos poderosos.


Siempre que sea posible, el número de conceptos de diseño se mantiene al mínimo y permite que el vocabulario total sea amplio. Por ejemplo, en la capa de widgets, Flutter usa el mismo concepto central (un widget) para representar el dibujo en la pantalla, el diseño (posicionamiento y tamaño), la interactividad del usuario, la gestión del estado, la temática, las animaciones y la navegación. En la capa de animación, un par de conceptos, animaciones y preadolescentes, cubren la mayor parte del espacio de diseño. En la capa de renderizado, RenderObjects se utilizan para describir el diseño, la pintura, las pruebas de posicionamiento y la accesibilidad. En cada uno de estos casos, el vocabulario correspondiente termina siendo extenso: hay cientos de widgets y objetos de renderizado, y decenas de tipos de animación e interpolación.



La jerarquía de clases es deliberadamente superficial y amplia para maximizar el número posible de combinaciones, centrándose en widgets pequeños y componibles que cada uno hace bien una cosa. Las características principales son abstractas, e incluso las características básicas como el relleno y la alineación se implementan como componentes separados en lugar de integrarse en el núcleo. (Esto también contrasta con las API más tradicionales en las que funciones como el relleno están integradas en el núcleo común de cada componente de diseño). Por ejemplo, para centrar un widget, en lugar de ajustar una propiedad de alineación teórica, se envuelve en un widget de centro. .


Hay widgets para relleno, alineación, filas, columnas y cuadrículas. Estos widgets de diseño no tienen una representación visual propia. En cambio, su único propósito es controlar algún aspecto del diseño de otro widget. Flutter también incluye widgets de utilidad que aprovechan este enfoque de composición.


Por ejemplo, Container, un widget de uso común, se compone de varios widgets responsables del diseño, la pintura, el posicionamiento y el tamaño. Específicamente, Container se compone de los widgets LimitedBox, ConstrainedBox, Align, Padding, DecoratedBox y Transform, como puede ver al leer su código fuente. Una característica definitoria de Flutter es que puede profundizar en la fuente de cualquier widget y examinarlo. Por lo tanto, en lugar de subclasificar Container para producir un efecto personalizado, puede componerlo y otros widgets simples de formas novedosas, o simplemente crear un nuevo widget utilizando Container como inspiración.


Construyendo widgets

Como se mencionó anteriormente, usted determina la representación visual de un widget anulando la función build () para devolver un nuevo árbol de elementos. Este árbol representa la parte del widget de la interfaz de usuario en términos más concretos. Por ejemplo, un widget de barra de herramientas puede tener una función de construcción que devuelve un diseño horizontal de texto y varios botones. Según sea necesario, el marco solicita recursivamente a cada widget que se compile hasta que el árbol esté completamente descrito por objetos renderizables concretos. El marco luego une los objetos renderizables en un árbol de objetos renderizables.


La función de compilación de un widget debe estar libre de efectos secundarios. Siempre que se le pida a la función que se compile, el widget debe devolver un nuevo árbol de widgets1, independientemente de lo que el widget haya devuelto anteriormente. El marco hace el trabajo pesado para determinar qué métodos de compilación deben llamarse en función del árbol de objetos de renderizado (que se describe con más detalle más adelante). 


En cada cuadro renderizado, Flutter puede recrear solo las partes de la IU donde el estado ha cambiado llamando al método build () de ese widget. Por lo tanto, es importante que los métodos de compilación regresen rápidamente, y el trabajo computacional pesado debe realizarse de alguna manera asincrónica y luego almacenarse como parte del estado para ser utilizado por un método de compilación.


Aunque su enfoque es relativamente ingenuo, esta comparación automatizada es bastante eficaz, ya que permite aplicaciones interactivas de alto rendimiento. Y, el diseño de la función de compilación simplifica su código al enfocarse en declarar de qué está hecho un widget, en lugar de las complejidades de actualizar la interfaz de usuario de un estado a otro.


Estado del widget

El marco presenta dos clases principales de widgets: widgets con estado y sin estado.


Muchos widgets no tienen un estado mutable: no tienen propiedades que cambien con el tiempo (por ejemplo, un icono o una etiqueta). Estos widgets son una subclase StatelessWidget.


Sin embargo, si las características únicas de un widget deben cambiar según la interacción del usuario u otros factores, ese widget tiene estado. Por ejemplo, si un widget tiene un contador que aumenta cada vez que el usuario toca un botón, entonces el valor del contador es el estado de ese widget. Cuando ese valor cambia, el widget debe reconstruirse para actualizar su parte de la interfaz de usuario. Estos widgets son una subclase de StatefulWidget y (debido a que el widget en sí es inmutable) almacenan el estado mutable en una clase separada que subclasifica State. StatefulWidgets no tiene un método de compilación; en cambio, su interfaz de usuario se construye a través de su objeto State.


Siempre que mutes un objeto State (por ejemplo, incrementando el contador), debes llamar a setState () para indicar al marco que actualice la interfaz de usuario llamando al método de construcción del State nuevamente.


Tener objetos de estado y widget separados permite que otros widgets traten a los widgets sin estado y con estado exactamente de la misma manera, sin preocuparse por perder el estado. En lugar de tener que aferrarse a un niño para preservar su estado, el padre puede crear una nueva instancia del niño en cualquier momento sin perder el estado persistente del niño. El marco hace todo el trabajo de encontrar y reutilizar objetos de estado existentes cuando sea apropiado.



El propio Flutter usa InheritedWidget ampliamente como parte del marco para el estado compartido, como el tema visual de la aplicación, que incluye propiedades como el color y los estilos de tipografía que son omnipresentes en toda la aplicación. El método build () de MaterialApp inserta un tema en el árbol cuando se construye, y luego, en la jerarquía más profunda, un widget puede usar el método .of () para buscar los datos relevantes del tema,


Administración del Estado

Entonces, si muchos widgets pueden contener estados, ¿cómo se administra el estado y cómo se transmite el sistema?


Al igual que con cualquier otra clase, puede usar un constructor en un widget para inicializar sus datos, por lo que un método build () puede garantizar que cualquier widget secundario se instancia con los datos que necesita:


content_copy

@anular

Compilación del widget (contexto BuildContext) {

   return ContentWidget (importantState);

}

Sin embargo, a medida que los árboles de widgets se hacen más profundos, pasar información de estado hacia arriba y hacia abajo en la jerarquía del árbol se vuelve engorroso. Entonces, un tercer tipo de widget, InheritedWidget, proporciona una manera fácil de obtener datos de un antepasado compartido. Puede usar InheritedWidget para crear un widget de estado que envuelva un ancestro común en el árbol de widgets.


Este enfoque también se utiliza para Navigator, que proporciona enrutamiento de página; y MediaQuery, que brinda acceso a métricas de pantalla como orientación, dimensiones y brillo.


A medida que las aplicaciones crecen, los enfoques de administración de estado más avanzados que reducen la ceremonia de creación y uso de widgets con estado se vuelven más atractivos. Muchas aplicaciones de Flutter usan paquetes de utilidades como proveedor, que proporciona un envoltorio alrededor de InheritedWidget. La arquitectura en capas de Flutter también permite enfoques alternativos para implementar la transformación de estado en UI, como el paquete flutter_hooks.


Renderizado y maquetación

Esta sección describe la canalización de renderizado, que es la serie de pasos que sigue Flutter para convertir una jerarquía de widgets en los píxeles reales pintados en una pantalla.


Modelo de renderizado de Flutter

Quizás se esté preguntando: si Flutter es un marco multiplataforma, ¿cómo puede ofrecer un rendimiento comparable al de los marcos de una sola plataforma?


Es útil comenzar pensando en cómo funcionan las aplicaciones tradicionales de Android. Al dibujar, primero llama al código Java del marco de Android. Las bibliotecas del sistema Android proporcionan los componentes responsables de dibujarse a sí mismos en un objeto Canvas, que luego Android puede representar con Skia, un motor gráfico escrito en C / C ++ que llama a la CPU o GPU para completar el dibujo en el dispositivo.


Los marcos multiplataforma normalmente funcionan creando una capa de abstracción sobre las bibliotecas de IU nativas de Android e iOS subyacentes, intentando suavizar las inconsistencias de cada representación de plataforma. El código de la aplicación a menudo se escribe en un lenguaje interpretado como JavaScript, que a su vez debe interactuar con las bibliotecas del sistema iOS basadas en Java o en Objective-C para mostrar la interfaz de usuario. Todo esto agrega una sobrecarga que puede ser significativa, particularmente cuando hay mucha interacción entre la interfaz de usuario y la lógica de la aplicación.



Por el contrario, Flutter minimiza esas abstracciones, pasando por alto las bibliotecas de widgets de la interfaz de usuario del sistema en favor de su propio conjunto de widgets. El código de Dart que pinta las imágenes de Flutter se compila en código nativo, que usa Skia para renderizar. Flutter también incorpora su propia copia de Skia como parte del motor, lo que permite al desarrollador actualizar su aplicación para mantenerse actualizado con las últimas mejoras de rendimiento, incluso si el teléfono no se ha actualizado con una nueva versión de Android. Lo mismo ocurre con Flutter en otras plataformas nativas, como iOS, Windows o macOS.


De la entrada del usuario a la GPU

El principio primordial que Flutter aplica a su canal de renderizado es que simple es rápido. Flutter tiene una canalización sencilla sobre cómo fluyen los datos al sistema,



Cuando Flutter necesita renderizar este fragmento, llama al método build (), que devuelve un subárbol de widgets que renderiza la interfaz de usuario según el estado actual de la aplicación. Durante este proceso, el método build () puede introducir nuevos widgets, según sea necesario, según su estado. Como ejemplo simple, en el fragmento de código anterior, Container tiene color y propiedades secundarias. Al mirar el código fuente de Container, puede ver que si el color no es nulo, inserta un ColoredBox que representa el color:


content_copy

si (color! = nulo)

   current = ColoredBox (color: color, child: current);

En consecuencia, los widgets de imagen y texto pueden insertar widgets secundarios como RawImage y RichText durante el proceso de compilación. Por lo tanto, la jerarquía de widgets eventual puede ser más profunda de lo que representa el código.



Esto explica por qué, cuando examina el árbol a través de una herramienta de depuración como el inspector Flutter, parte de Dart DevTools, puede ver una estructura que es considerablemente más profunda que la que se encuentra en su código original. Durante la fase de construcción, Flutter traduce los widgets expresados en código en un árbol de elementos correspondiente, con un elemento para cada widget. Cada elemento representa una instancia específica de un widget en una ubicación determinada de la jerarquía del árbol. Hay dos tipos básicos de elementos: ComponentElement, un anfitrión para otros elementos. RenderObjectElement, un elemento que participa en las fases de diseño o pintura.

RenderObjectElements es un intermediario entre su widget analógico y el RenderObject subyacente, al que llegaremos más adelante.


Se puede hacer referencia al elemento de cualquier widget a través de su BuildContext, que es un identificador de la ubicación de un widget en el árbol. Este es el contexto en una llamada de función como Theme.of (context), y se proporciona al método build () como parámetro.


Debido a que los widgets son inmutables, incluida la relación padre / hijo entre los nodos, cualquier cambio en el árbol de widgets (como cambiar Texto ('A') a Texto ('B') en el ejemplo anterior) provoca que un nuevo conjunto de objetos de widget ser devuelto. Pero eso no significa que la representación subyacente deba reconstruirse. El árbol de elementos es persistente de un marco a otro y, por lo tanto, juega un papel crítico en el rendimiento, lo que permite que Flutter actúe como si la jerarquía de widgets fuera completamente desechable mientras almacena en caché su representación subyacente. Con solo recorrer los widgets que cambiaron, Flutter puede reconstruir solo las partes del árbol de elementos que requieren reconfiguración.


Diseño y renderizado

Sería una aplicación poco común que dibujara un solo widget. Por lo tanto, una parte importante de cualquier marco de interfaz de usuario es la capacidad de diseñar de manera eficiente una jerarquía de widgets, determinando el tamaño y la posición de cada elemento antes de que se muestren en la pantalla.


La clase base para cada nodo en el árbol de renderizado es RenderObject, que define un modelo abstracto para diseño y pintura. Esto es extremadamente general: no se compromete con un número fijo de dimensiones o incluso con un sistema de coordenadas cartesianas (demostrado por este ejemplo de un sistema de coordenadas polares). Cada RenderObject conoce a su padre, pero sabe poco sobre sus hijos, aparte de cómo visitarlos y sus limitaciones. Esto proporciona a RenderObject suficiente abstracción para poder manejar una variedad de casos de uso.


Durante la fase de construcción, Flutter crea o actualiza un objeto que hereda de RenderObject para cada RenderObjectElement en el árbol de elementos. Los RenderObjects son primitivos: RenderParagraph renderiza texto, RenderImage renderiza una imagen y RenderTransform aplica una transformación antes de pintar su hijo.



La mayoría de los widgets de Flutter son renderizados por un objeto que hereda de la subclase RenderBox, que representa un RenderObject de tamaño fijo en un espacio cartesiano 2D. RenderBox proporciona la base de un modelo de restricción de cuadro, estableciendo un ancho y alto mínimo y máximo para cada widget a renderizar.


Para realizar el diseño, Flutter recorre el árbol de renderizado en un recorrido transversal en profundidad y pasa las restricciones de tamaño de padre a hijo. Al determinar su tamaño, el niño debe respetar las restricciones que le impone su padre. Los niños responden pasando un tamaño a su objeto padre dentro de las restricciones que estableció el padre.



Al final de este único recorrido por el árbol, cada objeto tiene un tamaño definido dentro de las restricciones de su padre y está listo para ser pintado llamando al método paint ().


El modelo de restricción de caja es muy poderoso como una forma de diseñar objetos en tiempo O (n):


Los padres pueden dictar el tamaño de un objeto secundario estableciendo restricciones máximas y mínimas al mismo valor. Por ejemplo, el objeto de renderizado más alto en una aplicación de teléfono obliga a su hijo a tener el tamaño de la pantalla. (Los niños pueden elegir cómo usar ese espacio. Por ejemplo, pueden centrar lo que quieren representar dentro de las restricciones dictadas).

Un padre puede dictar el ancho del niño pero darle flexibilidad al niño sobre la altura (o dictar la altura pero ofrecer flexibilidad sobre el ancho). Un ejemplo del mundo real es el texto de flujo, que podría tener que ajustarse a una restricción horizontal pero variar verticalmente según la cantidad de texto.

Este modelo funciona incluso cuando un objeto hijo necesita saber cuánto espacio tiene disponible para decidir cómo representará su contenido. Al usar un widget LayoutBuilder, el objeto hijo puede examinar las restricciones transmitidas y usarlas para determinar cómo las usará,



La raíz de todos los RenderObjects es RenderView, que representa la salida total del árbol de renderizado. Cuando la plataforma exige que se renderice un nuevo marco (por ejemplo, debido a un vsync o porque se completó una descompresión / carga de textura), se realiza una llamada al método compositeFrame (), que es parte del objeto RenderView en la raíz del árbol de render. Esto crea un SceneBuilder para activar una actualización de la escena. Cuando se completa la escena, el objeto RenderView pasa la escena compuesta al método Window.render () en dart: ui, que pasa el control a la GPU para renderizarla.


Incrustación de plataforma

Como hemos visto, en lugar de traducirse a los widgets del sistema operativo equivalentes, las interfaces de usuario de Flutter son creadas, diseñadas, compuestas y pintadas por el propio Flutter. El mecanismo para obtener la textura y participar en el ciclo de vida de la aplicación del sistema operativo subyacente varía inevitablemente según las preocupaciones únicas de esa plataforma. El motor es independiente de la plataforma, presentando una ABI (Application Binary Interface) estable que proporciona un embebedor de plataforma con una forma de configurar y usar Flutter.


El incrustador de plataforma es la aplicación nativa del sistema operativo que aloja todo el contenido de Flutter y actúa como el pegamento entre el sistema operativo host y Flutter. Cuando inicias una aplicación de Flutter, el incrustador proporciona el punto de entrada, inicializa el motor de Flutter, obtiene subprocesos para la interfaz de usuario y la trama, y ​​crea una textura en la que Flutter puede escribir. El incrustador también es responsable del ciclo de vida de la aplicación, incluidos los gestos de entrada (como el mouse, el teclado, el tacto), el tamaño de la ventana, la administración de subprocesos y los mensajes de la plataforma. Flutter incluye integradores de plataforma para Android, iOS, Windows, macOS y Linux; también puede crear un embebedor de plataforma personalizado, como en este ejemplo trabajado que admite sesiones remotas de Flutter a través de un framebuffer de estilo VNC o este ejemplo práctico para Raspberry Pi.


Cada plataforma tiene su propio conjunto de API y restricciones. Algunas notas breves específicas de la plataforma:


En iOS y macOS, Flutter se carga en el embedder como UIViewController o NSViewController, respectivamente. El incrustador de plataforma crea un FlutterEngine, que sirve como host para Dart VM y su tiempo de ejecución de Flutter, y un FlutterViewController, que se conecta al FlutterEngine para pasar eventos de entrada UIKit o Cocoa a Flutter y para mostrar los fotogramas renderizados por el FlutterEngine usando Metal o OpenGL.

En Android, Flutter se carga, de forma predeterminada, en el embebedor como una actividad. La vista está controlada por un FlutterView, que muestra el contenido de Flutter como una vista o una textura, dependiendo de la composición y los requisitos de ordenamiento z del contenido de Flutter.

En Windows, Flutter está alojado en una aplicación tradicional de Win32, y el contenido se procesa usando ANGLE, una biblioteca que traduce las llamadas API de OpenGL a los equivalentes de DirectX 11. Actualmente se están realizando esfuerzos para ofrecer también un incrustador de Windows utilizando el modelo de aplicación UWP, así como para reemplazar ANGLE con una ruta más directa a la GPU a través de DirectX 12.

Integrarse con otro código

Flutter proporciona una variedad de mecanismos de interoperabilidad, ya sea que acceda a código o API escritas en un lenguaje como Kotlin o Swift, llame a una API nativa basada en C, incorpore controles nativos en una aplicación de Flutter o incorpore Flutter en una aplicación existente.


Canales de plataforma

Para aplicaciones móviles y de escritorio, Flutter le permite llamar a código personalizado a través de un canal de plataforma, que es un mecanismo simple para comunicarse entre su código de Dart y el código específico de la plataforma de su aplicación de host. Al crear un canal común (encapsulando un nombre y un códec), puede enviar y recibir mensajes entre Dart y un componente de la plataforma escrito en un lenguaje como Kotlin o Swift. Los datos se serializan desde un tipo Dart como Map en un formato estándar y luego se deserializan en una representación equivalente en Kotlin (como HashMap) o Swift (como Dictionary).



Interfaz de función ajena

Para las API basadas en C, incluidas las que se pueden generar para código escrito en lenguajes modernos como Rust o Go, Dart proporciona un mecanismo directo para vincularse al código nativo mediante la biblioteca dart: ffi. El modelo de interfaz de función externa (FFI) puede ser considerablemente más rápido que los canales de plataforma, porque no se requiere serialización para pasar datos. En cambio, el tiempo de ejecución de Dart proporciona la capacidad de asignar memoria en el montón que está respaldado por un objeto de Dart y realizar llamadas a bibliotecas vinculadas estática o dinámicamente. FFI está disponible para todas las plataformas que no sean web, donde el paquete js tiene un propósito equivalente.


Para usar FFI, cree un typedef para cada una de las firmas de Dart y de método no administrado, e indique a la máquina virtual de Dart que se asigne entre ellas.



Representación de controles nativos en una aplicación de Flutter

Debido a que el contenido de Flutter se dibuja en una textura y su árbol de widgets es completamente interno, no hay lugar para que exista algo como una vista de Android dentro del modelo interno de Flutter o renderizado intercalado dentro de los widgets de Flutter. Ese es un problema para los desarrolladores que deseen incluir componentes de plataforma existentes en sus aplicaciones Flutter, como un control de navegador.


Flutter resuelve esto al introducir widgets de vista de plataforma (AndroidView y UiKitView) que le permiten incrustar este tipo de contenido en cada plataforma. Las vistas de la plataforma se pueden integrar con otro contenido de Flutter4. Cada uno de estos widgets actúa como intermediario del sistema operativo subyacente. Por ejemplo, en Android, AndroidView tiene tres funciones principales:


Hacer una copia de la textura gráfica renderizada por la vista nativa y presentarla a Flutter para su composición como parte de una superficie renderizada por Flutter cada vez que se pinta el marco.

Responder a las pruebas de posicionamiento y los gestos de entrada, y traducirlos a la entrada nativa equivalente.

Creando un análogo del árbol de accesibilidad y pasando comandos y respuestas entre las capas nativa y Flutter.

Inevitablemente, hay una cierta cantidad de gastos generales asociados con esta sincronización. Por lo tanto, en general, este enfoque es más adecuado para controles complejos como Google Maps, donde volver a implementar en Flutter no es práctico.


Normalmente, una aplicación Flutter crea una instancia de estos widgets en un método build () basado en una prueba de plataforma.



La comunicación con el código nativo subyacente a AndroidView o UiKitView generalmente ocurre mediante el mecanismo de canales de la plataforma, como se describió anteriormente.


Actualmente, las vistas de plataforma no están disponibles para plataformas de escritorio, pero esto no es una limitación arquitectónica; Es posible que se agregue soporte en el futuro.


Alojamiento de contenido de Flutter en una aplicación principal

Lo contrario del escenario anterior es incrustar un widget de Flutter en una aplicación existente de Android o iOS. Como se describe en una sección anterior, una aplicación Flutter recién creada que se ejecuta en un dispositivo móvil se aloja en una actividad de Android o UIViewController de iOS. El contenido de Flutter se puede incrustar en una aplicación existente de Android o iOS utilizando la misma API de incrustación.


La plantilla del módulo Flutter está diseñada para una fácil inserción; puede incrustarlo como una dependencia de origen en una definición de compilación de Gradle o Xcode existente, o puede compilarlo en un archivo binario de Android Archive o iOS Framework para su uso sin requerir que todos los desarrolladores tengan instalado Flutter.


El motor de Flutter tarda un poco en inicializarse, porque necesita cargar las bibliotecas compartidas de Flutter, inicializar el tiempo de ejecución de Dart, crear y ejecutar un aislamiento de Dart y adjuntar una superficie de renderizado a la interfaz de usuario. Para minimizar los retrasos en la interfaz de usuario al presentar el contenido de Flutter, es mejor inicializar el motor de Flutter durante la secuencia de inicialización general de la aplicación, o al menos antes de la primera pantalla de Flutter, para que los usuarios no experimenten una pausa repentina mientras se ejecuta el primer código de Flutter. cargado. Además, la separación del motor Flutter permite reutilizarlo en múltiples pantallas de Flutter y compartir la sobrecarga de memoria involucrada con la carga de las bibliotecas necesarias.



Soporte web Flutter

Si bien los conceptos arquitectónicos generales se aplican a todas las plataformas que admite Flutter, existen algunas características únicas del soporte web de Flutter que merecen un comentario.


Dart ha estado compilando en JavaScript desde que existe el lenguaje, con una cadena de herramientas optimizada tanto para el desarrollo como para la producción. Muchas aplicaciones importantes se compilan desde Dart a JavaScript y se ejecutan en producción en la actualidad, incluidas las herramientas para anunciantes de Google Ads. Debido a que el framework Flutter está escrito en Dart, compilarlo en JavaScript fue relativamente sencillo.


Sin embargo, el motor Flutter, escrito en C ++, está diseñado para interactuar con el sistema operativo subyacente en lugar de un navegador web. Por tanto, se requiere un enfoque diferente. En la web, Flutter proporciona una reimplementación del motor además de las API de navegador estándar. Actualmente tenemos dos opciones para renderizar contenido de Flutter en la web: HTML y WebGL. En modo HTML, Flutter usa HTML, CSS, Canvas y SVG. Para renderizar en WebGL, Flutter usa una versión de Skia compilada en WebAssembly llamada CanvasKit. Mientras que el modo HTML ofrece las mejores características de tamaño de código, CanvasKit proporciona la ruta más rápida a la pila de gráficos del navegador y ofrece una fidelidad gráfica algo mayor con los objetivos móviles nativos.



Quizás la diferencia más notable en comparación con otras plataformas en las que se ejecuta Flutter es que no es necesario que Flutter proporcione un tiempo de ejecución de Dart. En cambio, el marco Flutter (junto con cualquier código que escriba) se compila en JavaScript. También vale la pena señalar que Dart tiene muy pocas diferencias semánticas de lenguaje en todos sus modos (JIT versus AOT, compilación nativa versus web), y la mayoría de los desarrolladores nunca escribirán una línea de código que se encuentre con tal diferencia.


Durante el tiempo de desarrollo, Flutter web usa dartdevc, un compilador que admite la compilación incremental y, por lo tanto, permite el reinicio en caliente (aunque actualmente no se carga en caliente) para las aplicaciones. Por el contrario, cuando esté listo para crear una aplicación de producción para la web, se utiliza dart2js, el compilador de JavaScript de producción altamente optimizado de Dart, que empaqueta el núcleo y el marco de Flutter junto con su aplicación en un archivo fuente minimizado que se puede implementar en cualquier servidor web. . El código puede ofrecerse en un solo archivo o dividirse en varios archivos mediante importaciones diferidas.


Comentarios

Entradas más populares de este blog

Ejemplo de Entrevista Psicológica

Aportaciones de Kant a la Psicología.

La Ética en Psicoterapia