Flutter Notes | Principios básicos de Flutter (1) Arquitectura y ciclo de vida

Arquitectura Flutter

inserte la descripción de la imagen aquí
En pocas palabras, Flutter se puede dividir en tres capas de arriba a abajo: capa de marco , capa de motor y capa de incrustación . Las presentaremos por separado a continuación:

1. Capa de marco

Flutter Framework , la capa del marco. Este es un SDK puro implementado por Dart, que implementa un conjunto de bibliotecas básicas, de abajo hacia arriba, presentemos brevemente:

  • Las dos capas inferiores (Fundación y Animación, Pintura, Gestos) se fusionan en una capa de interfaz de usuario de dardo en algunos videos de Google, lo que corresponde al paquete dart:ui en Flutter, que es la biblioteca de interfaz de usuario subyacente expuesta por Flutter Engine, que proporciona animación, Gestos y capacidades de dibujo.

  • La capa de representación, es decir, la capa de representación, es una capa de diseño abstracto que depende de la capa de interfaz de usuario de Dart. La capa de representación creará un árbol de representación compuesto por objetos que se pueden representar. Cuando estos objetos se actualicen dinámicamente, el árbol de representación se dará cuenta lo que cambió, y actualice el render. Se puede decir que la capa de representación es la parte central de la capa del marco Flutter. Además de determinar la posición y el tamaño de cada objeto de representación, también realiza la transformación de coordenadas y el dibujo (llamando al dart:ui subyacente).

  • La capa Widgets es un conjunto de bibliotecas de componentes básicos proporcionados por Flutter Además de la biblioteca de componentes básicos, Flutter también proporciona dos bibliotecas de componentes de estilo visual, Material y Cupertino, que implementan respectivamente las especificaciones de diseño de iOS y Material.

El marco de Flutter es relativamente pequeño, porque algunas funciones de nivel superior que los desarrolladores pueden usar se han dividido en diferentes paquetes, implementados usando las bibliotecas principales de Dart y Flutter, incluidos los complementos de la plataforma, como la cámara y la vista web, y funciones independientes de la plataforma , como las animaciones .

2. Capa motor

Motor , la capa del motor. No hay duda de que es el núcleo de Flutter, esta capa está implementada principalmente en C++, incluido el motor Skia, el tiempo de ejecución de Dart (Dart runtime), el motor de diseño de texto, etc. Cuando el código llama a la biblioteca dart:ui, la llamada eventualmente irá a la capa del motor y luego se realizará el dibujo y la visualización reales.

3. Capa de incrustación

Embedder , la capa de incrustación. La representación final y la interacción de Flutter dependen de la API del sistema operativo de su plataforma, y ​​la capa de incrustación es principalmente para "instalar" el motor de Flutter en una plataforma específica. La capa de incrustación está escrita en el lenguaje de la plataforma actual, como Java y C++ para Android, Objective-C y Objective-C++ para iOS y macOS, y C++ para Windows y Linux. El código de Flutter se puede integrar en las aplicaciones existentes de forma modular a través de la capa de incrustación, o se puede utilizar como el cuerpo principal de la aplicación. Flutter en sí contiene la capa de incrustación de cada plataforma común. Si Flutter quiere admitir una nueva plataforma en el futuro, debe escribir una capa de incrustación para la nueva plataforma.

En términos generales, los desarrolladores no necesitan estar al tanto de la existencia de Engine y Embedder (si no necesitan llamar a los servicios del sistema de la plataforma), Framework es con lo que los desarrolladores deben interactuar directamente, por lo que también está en la parte superior de todo. modelo de arquitectura en capas.

Interfaz de widgets

En Flutter, la función es .en la pantalla del dispositivodibujanelementos". Es decir, endescribir la información de configuración de un elemento de UIwidget" Echemos un vistazo a la declaración de clase:WidgetWidgetTextWidget

 // 不可变的
abstract class Widget extends DiagnosticableTree {
    
    
  const Widget({
    
     this.key });

  final Key? key;

  
  @factory
  Element createElement();

  
  String toStringShort() {
    
    
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    
    
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  
  
  bool operator ==(Object other) => super == other;

  
  
  int get hashCode => super.hashCode;

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    
    
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  ...
}
  • @immutableSignifica que el Widget es inmutable , lo que restringe Widgetlas propiedades definidas en (es decir, la información de configuración) para que sean inmutables ( final), ¿por qué Widgetno se permite cambiar las propiedades definidas en? Esto se debe a que en Flutter, si el atributo cambia, el árbol se reconstruirá Widget, es decir, Widgetse recreará una nueva instancia para reemplazar la WidgetinstanciaWidget anterior, por lo que no tiene sentido permitir que el atributo cambie, porque una vez Widgetque su propio atributo cambia, será reemplazado. Es por eso que Widgetlas propiedades definidas en deben ser final.
  • WidgetLa clase hereda de , DiagnosticableTreeel "árbol de diagnóstico" , cuya función principal es proporcionar información de depuración.DiagnosticableTree
  • Key: Este keyatributo es similar al React/Vuede key, la función principal es decidir si reutilizar el anterior la próxima vezbuildwidget , la condición de decisión está en canUpdate()el método.
  • createElement(): Como se mencionó anteriormente, "uno widgetpuede corresponder a varios Element"; cuando el marco Flutter construye el árbol de la interfaz de usuario, primero llamará a este método para generar objetos correspondientes a los nodos Element. El marco Flutter llama implícitamente a este método, y básicamente no se llama durante nuestro desarrollo.
  • debugFillProperties(...)El método para anular la clase padre es principalmente establecer algunas características del árbol de diagnóstico.
  • canUpdate(...)Es un método estático, que se usa principalmente para reutilizar el widget anterior cuando se reconstruye el árbol de widgets . De hecho, debería ser: si usar el nuevo objeto de widget para actualizar la configuración del objeto Element correspondiente en el árbol de interfaz de usuario anterior. ; a través de Podemos ver su código fuente, siempre que la newWidgetsuma de y sea igual , el nuevo widget se usará para actualizar la configuración del objeto, de lo contrario se creará uno nuevo .oldWidgetruntimeTypekeyElementElement

En Flutter se puede decir que todo es todo Widget. Incluso una capacidad de centrado, en el desarrollo de interfaz de usuario imperativo tradicional, generalmente se establece como una propiedad, pero en Flutter se abstrae en un Centercomponente con nombre. Además, Flutter aboga por un desarrollo de combinación radical, es decir, construir Widgettus objetivos a través de una serie de estructuras básicas tanto como sea posible Widget.

Según las dos características anteriores, el código de Flutter estará lleno de varios Widget, y cada cuadro de actualización de la interfaz de usuario significa una Widgetreconstrucción parcial. Es posible que te preocupe si este diseño es demasiado inflado e ineficiente. De hecho, por el contrario, esta es la piedra angular de la capacidad de Flutter para realizar un renderizado de alto rendimiento. La razón por la que Flutter está diseñado de esta manera también es intencional y se basa en los siguientes dos hechos:

  • WidgetCuantos más nodos haya en el árbol de widgets , más precisa y pequeña será la parte que debe reconstruirse a través del algoritmo DiffWidget , y el principal cuello de botella en el rendimiento de la representación de la interfaz de usuario es la reconstrucción de los nodos.

  • El modelo de objetos y el modelo GC del lenguaje Dart optimizan la asignación de lectura rápida y la recuperación de objetos pequeños, y es este Widgetobjeto pequeño .

Tres árboles en aleteo

Dado que Widget solo describe la información de configuración de un elemento de la interfaz de usuario, ¿quién hace el diseño y el dibujo reales?

El flujo de procesamiento del framework Flutter es el siguiente:

  1. WidgetGenere un árbol basado en el árbol Elementy Elementlos nodos en el árbol heredan de la Elementclase.
  2. El árbol (árbol de representación) se genera de acuerdo con Elementel árbol Rendery los nodos en el árbol de representación heredan de RenderObjectla clase.
  3. Genere un árbol basado en el árbol de renderizado Layery luego muéstrelo en la pantalla.Todos Layerlos nodos en el árbol heredan de Layerla clase.

El diseño real y la lógica de representación están en Renderel árbol, Elementque es el pegamento de Widgety , que puede entenderse como un proxy intermedio.RenderObject

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Las funciones respectivas de los tres árboles son:

  • Widget: Responsable de la configuración. Para Elementdescribir la información de configuración de la interfaz de usuario, tiene una API pública. Esta parte también es una parte que los desarrolladores pueden percibir y usar directamente.
  • Element: el administrador de Flutter Virtual DOM, widgetel ciclo de vida que administra, representa los datos de la interfaz de usuario que realmente existen en la memoria, es responsable de la widgetcreación de instancias de ubicaciones específicas en el árbol, widgetlas referencias que contiene y la administración de las relaciones padre-hijo en el árbol, de hecho, Widget Treecon RenderObject Treeson Element Treegenerados por el controlador.
  • RenderObject: Responsable de manejar el tamaño, el diseño y el dibujo, se dibujará a sí mismo, colocará los nodos secundarios, etc.

Nota aquí:

  • Entre los tres árboles, Elementy Widgetestán en correspondencia uno a uno, pero Elementy RenderObjectno están en correspondencia uno a uno. Por ejemplo StatelessWidgety StatefulWidgetno tienen correspondiente RenderObject.
  • El árbol de representación generará un árbol antes de que se muestre en la pantalla Layer, por lo que en realidad hay cuatro árboles en Flutter, pero solo necesitamos entender los tres árboles anteriores.

Widget sin estado

StatelessWidgetRelativamente simple, hereda de una widgetclase y anula createElement()el método:


StatelessElement createElement() => StatelessElement(this);

StatelessElementIndirectamente heredado de la Elementclase, StatelessWidgetcorrespondiente a (como sus datos de configuración).

StatelessWidgetUsado en escenarios que no necesitan mantener el estado , por lo general construye la interfaz de usuario buildanidando otra en el método widget, y construye recursivamente sus anidados durante el proceso de construcción widget.

Aquí hay un ejemplo simple:

class Echo extends StatelessWidget  {
    
    
  const Echo({
    
    
    Key? key,  
    required this.text,
    this.backgroundColor = Colors.grey, //默认为灰色
  }):super(key:key);
    
  final String text;
  final Color backgroundColor;

  
  Widget build(BuildContext context) {
    
    
    return Center(
      child: Container(
        color: backgroundColor,
        child: Text(text),
      ),
    );
  }
}

Entonces podemos usarlo de la siguiente manera:

 Widget build(BuildContext context) {
    
    
  return Echo(text: "hello world");
}

Por convención, widgetlos parámetros del constructor deben usar parámetros con nombre , y los parámetros que se deben pasar en los parámetros con nombre deben agregarse con requiredpalabras clave, lo cual es beneficioso para que los analizadores de código estático lo verifiquen; al heredar widget, el primer parámetro generalmente debe ser Key. Además, si widgetse requieren receptores widget, el parámetro childo childrengeneralmente debe colocarse en último lugar en la lista de parámetros. También por convención, widgetlas propiedades deben declararse tanto como sea posible parafinal evitar cambios accidentales.

Contexto

buildEl método tiene un contextparámetro, que es BuildContextuna instancia de la clase, que representa el contextowidget actual en widgetel árbol , y cada uno corresponde a un objeto.widgetcontext

De hecho, es un manejador ( ) para realizar "operaciones relacionadas" en la posición contextactual widgeten el árbol , por ejemplo, proporciona métodos para atravesar el árbol hacia arriba desde el actual y encontrar el padre por tipo .widgethandlewidgetwidgetwidgetwidget

widgetAquí hay un ejemplo de obtener el padre en un subárbol :

class ContextRoute extends StatelessWidget  {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      appBar: AppBar(
        title: Text("Context测试"),
      ),
      body: Container(
        child: Builder(builder: (context) {
    
    
          // 在 widget 树中向上查找最近的父级`Scaffold`  widget 
          Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
          // 直接返回 AppBar的title, 此处实际上是Text("Context测试")
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
  }
}

inserte la descripción de la imagen aquí

Widget con estado

Si tienes uno StatelessWidget, ¿por qué tener otro StatefulWidget? WidgetTodos son inmutables en Flutter , pero en realidad necesitan actualizarse de acuerdo con el estado correspondiente Widget, por lo que se genera StatefulWdiget, que se compone StatefulWidgetde dos objetos Widgety .State

StatefulWidgetTambién hereda de widgetla clase y reescribe createElement()el método, la diferencia es que los Elementobjetos devueltos no son los mismos, pero se devuelve uno StatefulElement, además StatefulWidgetse agrega una nueva interfaz a la clase createState().

abstract class StatefulWidget extends Widget {
    
    
  const StatefulWidget({
    
     Key key }) : super(key: key);
    
  
  StatefulElement createElement() => StatefulElement(this);
    
  
  State createState();
}
  • StatefulElementIndirectamente heredado de la Elementclase, StatefulWidgetcorrespondiente a (como sus datos de configuración). StatefulElementse puede llamar varias veces createState()para crear el Stateobjeto state(). Los desarrolladores no necesitan preocuparse por este método.

  • createState()Para crear y StatefulWidgetestados relacionados, los desarrolladores deben implementar este método en las subclases. Se StatefulWidgetpuede llamar varias veces durante la vigencia del . Por ejemplo, cuando un árbol StatefulWidgetse inserta en widgetvarias posiciones al mismo tiempo, el marco de trabajo de Flutter llamará a este método para generar una Stateinstancia independiente para cada posición. De hecho, es esencialmente una StatefulElementcorrespondiente a una Stateinstancia.

En StatefulWidget, Statelos objetos StatefulElementtienen una correspondencia uno a uno entre sí, por lo que en la documentación del SDK de Flutter, a menudo puedes ver descripciones como "quitar un objeto de estado del árbol" o "insertar un objeto de estado en el árbol". se refiere al árbol de elementos generado por el árbol de widgets. El "árbol" se menciona a menudo en la documentación del SDK de Flutter, y podemos juzgar a qué árbol se refiere según el contexto. De hecho, para los usuarios, no es necesario que nos preocupemos por qué árbol es. No importa qué tipo de árbol su objetivo final sea describir la estructura y la información de dibujo de la interfaz de usuario, solo debemos entenderlo como "un nodo". árbol que constituye la interfaz de usuario”, no se enrede demasiado en estos conceptos.

Estado

Una StatefulWidgetclase corresponde a una Stateclase, indicando el estado a mantener Statecorrespondiente a la misma , y ​​la información de estado guardada en ella puede ser:StatefulWidgetState

  1. StateSe puede leer sincrónicamente en widgettiempo de compilación .
  2. StateSe puede cambiar durante widgetel ciclo de vida , y se puede llamar manualmente al método para que cambie, lo que notificará al marco de trabajo de Flutter sobre el cambio de estado. Después de recibir el mensaje, el marco de trabajo de Flutter volverá a llamar a su método para reconstruir el árbol, de modo que para lograr el propósito de actualizar la interfaz de usuario.setState()Statebuildwidget

StateHay dos propiedades comunes en:

  1. State.widgetState, a través del cual podemos obtener la instancia asociada con esta instancia widget, que se establece dinámicamente por el marco Flutter. Tenga en cuenta que esta asociación no es permanente, porque en el ciclo de vida de la aplicación, la instancia de un nodo en el árbol de la interfaz de usuario widgetpuede cambiar cuando se reconstruye, pero Statela instancia solo se creará cuando se inserte en el árbol por primera vez. Al reconstruir, si widgetse modifica, el marco Flutter se configurará dinámicamente para State.widgetapuntar a la nueva widgetinstancia .

  2. State.context, es StatefulWidgetcorrespondiente BuildContexty tiene el mismo StatelessWidgetefecto BuildContext. Cuando el marco necesita crear uno StatefulWidget, llama StatefulWidget.createState()al método y luego asocia Stateel objeto con el objeto BuildContextEsta asociación es permanente, Stateel objeto nunca lo cambia BuildContext, pero BuildContextél mismo se puede mover alrededor del árbol.

ciclo de vida del estado

Después de que Stateel método crea el objeto createState(), llamará initState()al método y comenzará su propio ciclo de vida:

inserte la descripción de la imagen aquí

El significado de cada función de devolución de llamada:

  • initState(): Se llamará cuando widgetse inserte en el árbol por primera vez widget. Para cada Stateobjeto, el marco Flutter solo llamará a esta devolución de llamada una vez . Por lo tanto, generalmente realiza algunas operaciones únicas en esta devolución de llamada, como inicialización de estado y notificación de suscripción de evento de subárbol, etc.

    Nota: No se puede initStatellamar BuildContext.dependOnInheritedWidgetOfExactType(este método se usa para widgetobtener widgetel padre más cercano en el árbol InheritedWidget), porque después de completar la inicialización, widgetel árbol InheritFrom widgettambién puede cambiar, por lo que la forma correcta debe ser en build()el método o didChangeDependencies()en llamarlo.

  • didChangeDependencies(): StateSe llamará cuando cambie la dependencia del objeto; por ejemplo, si build()se incluye uno en el anterior InheritedWidgety luego se cambia build()en el posterior, las devoluciones de llamada del sub se llamarán en este momento. Un escenario típico es que cuando cambia el idioma del sistema o el tema de la aplicación , el marco de Flutter notificará para llamar a esta devolución de llamada. Cabe señalar que cuando el componente se crea y se monta por primera vez (incluso cuando se vuelve a crear), también se llamará al componente correspondiente.Inherited widgetInheritedWidgetwidgetdidChangeDependencies()LocalewidgetdidChangeDependencies

  • build(): Utilizado principalmente para construir widgetsubárboles, se llamará en los siguientes escenarios:

    1. initState()después de la llamada
    2. didUpdateWidget()después de la llamada
    3. setState()después de la llamada
    4. didChangeDependencies()después de la llamada
    5. Llamado después de que un Stateobjeto ha sido eliminadodeactivate de una ubicación en el árbol y reinsertado en otra parte del árbol .
  • reassemble(): Una devolución de llamada proporcionada especialmente para el desarrollo y la depuración. Se llamará durante la recarga en caliente . Esta devolución de llamada nunca se llamará en el modo de lanzamiento .

  • didUpdateWidget(): Al widgetreconstruir, el marco Flutter llamará widget.canUpdatepara detectar widgetlos nodos antiguos y nuevos en la misma posición en el árbol, y luego decidirá si se requiere una actualización, y si widget.canUpdateregresa true, se llamará a esta devolución de llamada.

    Como se mencionó anteriormente, regresará cuando widget.canUpdateel antiguo y el nuevo sean iguales al mismo tiempo , lo que significa que se llamará en este caso . (De hecho, el marco flutter aquí creará un nuevo widget, vinculará este estado y pasará el antiguo widget en esta función) Cuando el nodo de nivel superior reconstruye el widget, es decir, cuando cambia el estado del componente de nivel superior , también activará la ejecución del sub-widget . Cabe señalar que si el cambio del controlador está involucrado, el oyente del controlador anterior debe eliminarse en esta función y debe crearse el oyente del nuevo controlador.widgetkeyruntimeType truedidUpdateWidget()didUpdateWidget

  • deactivate(): StateEsta devolución de llamada se llama cuando el objeto se elimina del árbol. En algunos escenarios, el marco Flutter reinsertará Stateel objeto en el árbol, como Statecuando el subárbol que contiene el objeto se mueve de una posición del árbol a otra (esto se puede GlobalKeylograr con ). El método se llamará a continuación si se eliminó y no se reinsertó en el árbol dispose().

  • dispose(): StateSe llama cuando el objeto se elimina permanentemente del árbol; los recursos generalmente se liberan, las suscripciones se dan de baja, las animaciones se cancelan, etc. en esta devolución de llamada.

Ilustre con el siguiente contraejemplo:

class CounterWidget extends StatefulWidget {
    
    
  const CounterWidget({
    
    Key? key, this.initValue = 0});

  final int initValue;

  
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
    
    
  int _counter = 0;

  
  void initState() {
    
    
    super.initState();
    //初始化状态
    _counter = widget.initValue;
    print("initState");
  }

  
  Widget build(BuildContext context) {
    
    
    print("build");
    return Scaffold(
      body: Center(
        child: TextButton(
          child: Text('$_counter'),
          //点击后计数器自增
          onPressed: () => setState(
            () => ++_counter,
          ),
        ),
      ),
    );
  }

  
  void didUpdateWidget(CounterWidget oldWidget) {
    
    
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget ");
  }

  
  void deactivate() {
    
    
    super.deactivate();
    print("deactivate");
  }

  
  void dispose() {
    
    
    super.dispose();
    print("dispose");
  }

  
  void reassemble() {
    
    
    super.reassemble();
    print("reassemble");
  }

  
  void didChangeDependencies() {
    
    
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}

Nota: Al heredar StatefulWidgety reescribir su método, para @mustCallSuperel método de la clase principal que contiene anotaciones, se debe llamar al método de la clase principal en el método de la subclase.

A continuación, cree una nueva página de ruta donde solo mostramos una CounterWidget:

class StateLifecycleTest extends StatelessWidget {
    
    
  const StateLifecycleTest({
    
    Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    
    
    return CounterWidget();
  }
}

Ejecutamos la aplicación y abrimos la página de enrutamiento.Después de abrir la nueva página de enrutamiento, aparecerá un número 0 en el centro de la pantalla, y luego la salida de registro de la consola:

I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build

Como puede ver, el primer método se llama cuando StatefulWidgetse inserta en el árbol.widgetinitState

Luego hacemos clic en el botón ⚡️ para recargar en caliente, y el registro de salida de la consola es el siguiente:

I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget 
I/flutter ( 5436): build

Puede ver que buildantes del método, solo se llama reassemblea y didUpdateWidget.

A continuación, widgeteliminamos en el árbol CounterWidget, cambiando el método StateLifecycleTestde a build:

 Widget build(BuildContext context) {
    
    
  //移除计数器 
  //return CounterWidget ();
  //随便返回一个Text()
  return Text("xxx");
}

Luego recarga en caliente, el registro es el siguiente:

I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose

Como puede ver, cuando se quitan CounterWidgetdel widgetárbol, deactivey disposese llaman a su vez.

establecerEstado()

setState()El método es la única forma legal de modificar el estado en Flutter. Este método notificará al marco que el estado interno de este objeto ha cambiado. El método de llamada es: proporcionará un método de devolución de llamada en el que el desarrollador debe modificar el valor del estado setState(() {_myState = newValue;});. , y la devolución de llamada se llamará inmediatamente de forma síncrona . Tenga en cuenta que no debe realizar cálculos que consumen mucho tiempo en esta devolución de llamada, y mucho menos devolver uno Future(la devolución de llamada no se puede asyncmarcar con " "), esta devolución de llamada generalmente solo se usa para ajustar el cambio real al estado.

Después de llamar , el objeto correspondiente actual setState()se marcará como , y luego se llamará al método para actualizar la interfaz de usuario. El siguiente es el ciclo de vida del Estado después de unirse:widgetelementdirtybuild()setState()
inserte la descripción de la imagen aquí

Parece que el ciclo de vida del Estado en realidad se puede dividir en tres etapas:

  • Inicialización: constructor, initState(),didChangeDependencies()
  • Cambio de estado: ejecución desencadenada por cambio de configuración didUpdateWidget(), build()ejecución provocada setState()porbuild()
  • Eliminación de componentes: deactivate(),dispose()

¿Por qué StatefulWidget separa Estado y Widget?

StatefulWidget¿Por qué poner buildel método Stateen la clase y no en el StatefulWidget?

  • Esto se debe principalmente a consideraciones de rendimiento y flexibilidad de desarrollo .

Si build()pones el método StatefulWidget, hay dos problemas en términos de flexibilidad:

1. Acceso estatal inconveniente.

Imagínese, si tenemos StatefulWidgetmuchos estados, y buildel método se llama cada vez que cambia el estado, ya que el estado se almacena en State, si buildel método está StatefulWidgeten, entonces buildlos métodos y 状态están en dos clases respectivamente, luego leyendo el estado durante la construcción será ¡Será muy inconveniente! Imagínese, si buildel método realmente se coloca StatefulWidgeten , dado que el proceso de creación de la interfaz de usuario requiere dependencias State, buildel método tendrá que agregar un Stateparámetro, que probablemente sea el siguiente:

  Widget build(BuildContext context, State state){
    
    
      //state.counter
      ...
  }

En este caso, Statetodos los estados de la clase solo se pueden declarar como estados públicos, ¡de modo que se Statepueda acceder a los estados fuera de la clase! Sin embargo, después de que el estado se establezca en público, el estado ya no será privado, lo que hará que la modificación del estado se vuelva incontrolable. Pero si build()coloca el método Stateen , el proceso de compilación no solo puede acceder directamente al estado, sino que tampoco necesita exponer el estado privado, lo cual es muy conveniente.

2. No es conveniente heredar StatefulWidget.

Por ejemplo, en Flutter hay una clase base para widgets animados AnimatedWidgetque hereda de StatefulWidgetlas clases. AnimatedWidgetSe introduce un método abstracto en build(BuildContext context), y AnimatedWidgetlas animaciones heredadas de widgetdeben implementar este buildmétodo. Ahora imagine que si StatefulWidgetya hay un método en la clase build, como se mencionó anteriormente, buildel método necesita recibir un Stateobjeto en este momento, lo que significa que AnimatedWidgetsu propio Stateobjeto (denotado como _animatedWidgetState) debe proporcionarse a sus subclases, porque las subclases deben ser En su buildmétodo, el método para llamar a la clase padre buildpuede ser el siguiente:

class MyAnimationWidget extends AnimatedWidget {
    
    
    
    Widget build(BuildContext context, State state){
    
    
      // 由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
      // 所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState 暴露给其子类   
      super.build(context, _animatedWidgetState)
    }
}

Obviamente, esto no es razonable, porque AnimatedWidgetel objeto de estado es AnimatedWidgetun detalle de implementación interna y no debe exponerse al exterior.

Si desea exponer el estado de la clase principal a la subclase, debe tener un mecanismo de transferencia, y no tiene sentido hacer este conjunto de mecanismos de transferencia, porque la transferencia de estado entre las clases principal y secundaria no tiene nada que ver con la lógica de la propia subclase.

En resumen, se puede encontrar que, para StatefulWidget, buildponer el método Statepuede aportar mucha flexibilidad al desarrollo.

Por otro lado, se trata principalmente de consideraciones de rendimiento. State administra el estado (que puede entenderse como controlador), y Widget es UI (es decir, Vista). Generar Widget (es decir, Vista) de acuerdo con el cambio de estado puede ahorrar memoria, en lugar de creando el objeto de estado Estado cada vez.

Obtener el objeto State en el árbol de widgets

Dado que la lógica específica de StatefulWidget está en su estado, muchas veces necesitamos obtener el objeto State correspondiente a StatefulWidget para llamar a algunos métodos.Por ejemplo, la clase de estado ScaffoldState correspondiente al componente Scaffold define la apertura de SnackBar (barra de solicitud en la parte inferior de la página de enrutamiento) Métodos. Tenemos dos formas de obtener el objeto State del StatefulWidget principal en el árbol de widgets secundarios.

1. Obtenga el estado a través del contexto

contextLos objetos tienen un findAncestorStateOfType()método que widgetsube por el árbol desde el nodo actual para encontrar un objeto StatefulWidgetcorrespondiente Statedel tipo especificado. Aquí hay SnackBarun ejemplo que implementa un open :

class GetStateObjectRoute extends StatefulWidget {
    
    
  const GetStateObjectRoute({
    
    Key? key}) : super(key: key);

  
  State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}

class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      appBar: AppBar(
        title: Text("子树中获取State对象"),
      ),
      body: Center(
        child: Column(
          children: [
            Builder(builder: (context) {
    
    
              return ElevatedButton(
                onPressed: () {
    
    
                  // 查找父级最近的Scaffold对应的ScaffoldState对象
                  ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;
                  // 打开抽屉菜单
                  _state.openDrawer();
                },
                child: Text('打开抽屉菜单1'),
              );
            }),
          ],
        ),
      ),
      drawer: Drawer(),
    );
  }
}

En términos generales, si el estado de StatefulWidget es privado (no debe estar expuesto al exterior), entonces no deberíamos obtener directamente su objeto State en el código; si se espera que el estado de StatefulWidget esté expuesto (generalmente hay algunas operaciones de componentes método), podemos obtener directamente su objeto State. Sin embargo, el método para obtener el estado de StatefulWidget a través de context.findAncestorStateOfType es universal. No podemos especificar si el estado de StatefulWidget es privado a nivel gramatical, por lo que existe un acuerdo predeterminado en el desarrollo de Flutter: si el estado de StatefulWidget debe ser exposed , debe proporcionar un método estático of en StatefulWidget para obtener su objeto State, y los desarrolladores pueden obtenerlo directamente a través de este método; si State no quiere estar expuesto, entonces no proporcione el método of. Esta convención se puede ver en todas partes en Flutter SDK. Por lo tanto, Scaffold en el ejemplo anterior también proporciona un método of, al que podemos llamar directamente:

Builder(builder: (context) {
    
    
  return ElevatedButton(
    onPressed: () {
    
    
      // 直接通过of静态方法来获取ScaffoldState
      ScaffoldState _state=Scaffold.of(context);
      // 打开抽屉菜单
      _state.openDrawer();
    },
    child: Text('打开抽屉菜单2'),
  );
}),

Para otro ejemplo, si queremos mostrar snackbar, podemos llamarlo a través del siguiente código:

Builder(builder: (context) {
    
    
  return ElevatedButton(
    onPressed: () {
    
    
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("我是SnackBar")),
      );
    },
    child: Text('显示SnackBar'),
  );
}),

2. Obtener Estado a través de GlobalKey

Flutter también tiene una forma general de obtener el objeto State: ¡ GlobalKeyhazlo! El procedimiento se divide en dos pasos:

  1. StatefulWidgetAgregar al objetivo GlobalKey.
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(
    key: _globalKey , //设置key
    ...  
)
  1. GlobalKeyObtener Stateel objeto por
_globalKey.currentState.openDrawer()

GlobalKeyEs un mecanismo provisto por Flutter para referenciar en toda la App element. Si widgetse establece a GlobalKey, entonces podemos obtener el objeto correspondiente globalKey.currentWidgetobteniendo el widgetobjeto , y si la corriente es , podemos obtener el objeto correspondiente obteniendo el objeto .globalKey.currentElementwidgetelementwidgetStatefulWidgetglobalKey.currentStatewidgetstate

Nota: El uso GlobalKeyes costoso y debe evitarse si hay otras opciones disponibles. Además, el mismo debe ser únicoGlobalKey en todo el árbol y no puede repetirse.widget

Estado del ciclo de vida desde la perspectiva del enrutamiento de navegación

inserte la descripción de la imagen aquí

Análisis del ciclo de vida de StatefulWidget desde la perspectiva de StatefulElement

inserte la descripción de la imagen aquí

Se puede ver que la activación de cada devolución de llamada del ciclo de vida se realiza BuildOwnerindirectamente . A continuación se analizará la ruta de activación y el significado de cada devolución de llamada desde la perspectiva del código fuente.StatefulElementState

// 代码清单8-1 flutter/packages/flutter/lib/src/widgets/framework.dart
StatefulElement(StatefulWidget widget)
    : state = widget.createState(), // 触发State创建
      super(widget) {
    
    
  state._element = this; // State持有Element和Widget的引用
  state._widget = widget;
  assert(state._debugLifecycleState == _StateLifecycle.created);
}

StatefulElementLa creación se completa en su propio constructorState y se completa la asignación de dos campos clave. Después de eso, se llevará a cabo el proceso de este Elementnodo Build, y la lógica específica se muestra en la lista de códigos 8-2.

// 代码清单8-2 flutter/packages/flutter/lib/src/widgets/framework.dart

void _firstBuild() {
    
    
  try {
    
     // 触发initState回调
    final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
  } finally {
    
     ...... }
  state.didChangeDependencies(); // 触发didChangeDependencies回调
  super._firstBuild();
}

Debido a que la lógica anterior activa initStateel método de suma en el ciclo de vida, generalmente didChangeDependenciesse usa como el punto de tiempo cuando las variables de miembros internos comienzan a inicializarse.initStateState

didChangeDependenciesAunque se activará incondicionalmente durante la primera compilación, aún se activará en el proceso de compilación subsiguiente, como se muestra en el Listado 8-3.

// 代码清单8-3 flutter/packages/flutter/lib/src/widgets/framework.dart
 // StatefulElement
void performRebuild() {
    
    
  if (_didChangeDependencies) {
    
     // 通常在代码清单8-13中设置为true,详见8.2节
    state.didChangeDependencies(); // 当该字段为true时再次触发didChangeDependencies
    _didChangeDependencies = false;
  }
  super.performRebuild();
}

didChangeDependenciesMarca que el Elementnodo dependiente ha cambiado y que didChangeDependenciesel método se volverá a llamar en este momento, por lo que esta devolución de llamada es más adecuada para responder a algunas actualizaciones dependientes. performnRebuildEl método que eventualmente se activará StatefulElementse buildmuestra en el Listado 8-4.

// 代码清单8-4 flutter/packages/flutter/lib/src/widgets/framework.dart

Widget build() => state.build(this);

La lógica anterior está en línea con StatefulWidgetla intuición de uso, es decir, buildel método se coloca Stateen la devolución de llamada y la devolución de llamada se usa principalmente para la actualización de la interfaz de usuario. Cabe señalar que si el Elementnodo actual está marcado como dirty, builddefinitivamente se llamará al método, por lo que no es recomendable realizar operaciones que consumen mucho tiempo para no afectar la fluidez de la interfaz de usuario.

Para el caso de que no sea la primera compilación, el método que generalmente se activa Element, la lógica se muestra en el Listado 8-5.updateStatefulElement

// 代码清单8-5 flutter/packages/flutter/lib/src/widgets/framework.dart
void update(StatefulWidget newWidget) {
    
      // StatefulElement,见代码清单5-47
  super.update(newWidget);
  assert(widget == newWidget);
  final StatefulWidget oldWidget = state._widget!;
  _dirty = true;
  state._widget = widget as StatefulWidget;
  try {
    
    
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
    final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) 
       as dynamic;
  } finally {
    
    
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
  }
  rebuild(); // 触发代码清单8-4中的逻辑
}

La lógica anterior activará su método antes de pasar el método rebuildactivado . Es un momento apropiado para eliminar algunas referencias y dependencias a y actualizar algunos recursos que dependen de las propiedades.StatebuilddidUpdateWidgetoldWidgetWidget

ElementdetachEl nodo llamará _deactivateRecursivelyal método en la etapa y su lógica específica se muestra en la lista de códigos 8-6.

// 代码清单8-6 flutter/packages/flutter/lib/src/widgets/framework.dart
static void _deactivateRecursively(Element element) {
    
     // 见代码清单5-50
  element.deactivate();
  assert(element._lifecycleState == _ElementLifecycle.inactive);
  element.visitChildren(_deactivateRecursively);
}

void deactivate() {
    
     // StatefulElement
  state.deactivate(); // 触发deactivate回调
  super.deactivate(); // 见代码清单8-7
}

La lógica anterior activará el Statemétodo deactivatey hará que el nodo actual entre en inactivela etapa. El disparador de devolución de llamada indica que el Elementnodo actual se eliminó Element Tree, pero aún está disponible para volver a unirse. Esta vez es adecuada para liberar algunos recursos que están fuertemente relacionados con el estado actual. Para aquellos recursos que no están relacionados con el estado, teniendo en cuenta que el nodo aún puede Elementingresar Element Tree, no es necesario que se libere en este momento (se puede comparar con la devolución de llamada en Android Activity) onPause.

Se debe llamar al método superde la lógica anterior y su lógica se muestra en el Listado 8-7.deactivate

// 代码清单8-7 flutter/packages/flutter/lib/src/widgets/framework.dart

void deactivate() {
    
     // Element
  if (_dependencies != null && _dependencies!.isNotEmpty) {
    
     // 依赖清理
    for (final InheritedElement dependency in _dependencies!)
      dependency._dependents.remove(this);
  }
  _inheritedWidgets = null;
  _lifecycleState = _ElementLifecycle.inactive; // 更新状态
}

La lógica anterior se utiliza principalmente para eliminar las dependencias correspondientes.

Después de que termine el proceso de construcción, se llamará Elemental método y su lógica se muestra en el Listado 8-8.unmount

// 代码清单8-8 flutter/packages/flutter/lib/src/widgets/framework.dart
 // StatefulElement,见代码清单5-52
void unmount() {
    
    
  super.unmount();
  state.dispose(); // 触发dispose回调
  state._element = null;
}
 
void unmount() {
    
     // Element
  final Key? key = _widget.key;
  if (key is GlobalKey) {
    
    
    key._unregister(this); // 取消注册
  }
  _lifecycleState = _ElementLifecycle.defunct;
}

En la lógica anterior, StatefulElementel método principal se Statellamará dispose. ElementLa lógica general en se encarga de destruir GlobalKeyel registro asociado.

Ciclo de vida de la aplicación desde la perspectiva de Flutter

Cabe señalar que si desea conocer el ciclo de vida de la App, debe obtenerla WidgetsBindingObservera través de didChangeAppLifecycleState. El estado del ciclo de vida que se puede obtener a través de esta interfaz está en AppLifecycleStatela clase. Los estados comunes incluyen los siguientes:

  • reanudado : la interfaz es visible y puede responder a la entrada del usuario, al igual que la de AndroidonResume
  • inactivo : cuando la interfaz se retira al fondo o aparece un cuadro de diálogo, está en un estado inactivo, es decir, pierde el foco y no recibe la entrada del usuario, pero aún se puede ejecutar la devolución de llamada; al igual que drawFrameAndroidonPause
  • en pausa : la aplicación está suspendida y actualmente es invisible para el usuario, como retirarse al fondo, perder el foco, no puede responder a la entrada del usuario y no recibirá drawFramedevoluciones de llamada (el motor no devolverá la llamada PlatformDispatcher) ; igual onBeginFrameque onDrawFrameAndroidonStop
  • tachado : la aplicación todavía está alojada en el motor flutter, pero se ha separado de las vistas del host y ya no se ejecutarán las devoluciones de llamada drawFrame; cuando la aplicación está en este estado, el motor se ejecuta sin una vista. Esto podría estar en el proceso de adjuntar la vista cuando el motor se inicializa por primera vez, o después de que Navigatorla vista se destruya debido a una ventana emergente.

inserte la descripción de la imagen aquí

Ejemplo de uso:

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
    
    

  
  void initState() {
    
    
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  
  
  void dispose() {
    
    
    super.dispose();
    WidgetsBinding.instance.removeObserver(this);
  }
  
  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    
    
    print(state);
  } 
} 

Cómo se compara Flutter con otras soluciones multiplataforma

Hoy en día, ya existen muchos frameworks multiplataforma en la industria (refiriéndose específicamente a las plataformas Android e iOS), y de acuerdo a sus principios, se dividen principalmente en tres categorías:

  • H5 + nativo (Cordova, Ionic, subprograma WeChat)
  • Desarrollo JavaScript + renderizado nativo (React Native, Weex)
  • Interfaz de usuario autodibujada + nativa (Qt para dispositivos móviles, Flutter)

inserte la descripción de la imagen aquí

El lenguaje de desarrollo en la tabla anterior se refiere principalmente al lenguaje de desarrollo de la capa de aplicación, y la eficiencia de desarrollo se refiere a la eficiencia de todo el ciclo de desarrollo, incluido el tiempo de codificación, el tiempo de depuración y el tiempo para depurar y manejar problemas de compatibilidad. Dinámico se refiere principalmente a si admite la entrega de código dinámico y si admite actualizaciones en caliente. Vale la pena señalar que el paquete de lanzamiento de Flutter se compila utilizando el modo Dart AOT de forma predeterminada, por lo que no admite la dinamización, pero Dart también tiene modos de operación JIT o instantánea, y estos modos admiten la dinamización.

Solución 1: H5 + nativo

El principio principal de este tipo de marco es implementar el contenido que necesita cambiar dinámicamente en la aplicación a través de HTML5 (H5 para abreviar) y cargarlo a través del control de carga de la página web nativa WebView (Android) o WKWebView (iOS). En esta solución, la parte H5 se puede cambiar en cualquier momento sin publicar una versión y se pueden cumplir los requisitos dinámicos; al mismo tiempo, dado que el código H5 solo necesita desarrollarse una vez, se puede ejecutar tanto en Android como en iOS plataformas al mismo tiempo, lo que también puede reducir el tiempo de desarrollo Costo, es decir, cuantas más funciones de la parte H5, menor será el costo de desarrollo. A esto lo llamamos H5 + modo de desarrollo nativo desarrollo híbrido . Las aplicaciones desarrolladas en modo híbrido se denominan aplicaciones híbridas o HTMLybrid Apps . Si la mayoría de las funciones de una aplicación están implementadas por H5, lo llamamos Web App .

Los representantes típicos del marco de desarrollo híbrido actual son: Cordova, Ionic. La mayoría de las aplicaciones tendrán algunas funciones desarrolladas por H5, al menos hasta ahora, la aplicación HTMLybrid sigue siendo la solución cruzada más versátil y madura.

Aquí, debemos mencionar el programa pequeño. En la actualidad, la pila de tecnología de desarrollo de la capa de aplicación de programa pequeño de varias empresas nacionales es la pila de tecnología web, y el método de representación subyacente es básicamente una combinación de WebView y nativo.

Punto de tecnología de desarrollo híbrido

1) Ejemplo: JavaScript llama a la API nativa para obtener el modelo de teléfono

A continuación, tomamos Android como ejemplo para implementar una API nativa para obtener el modelo de teléfono para llamar JavaScript. En este ejemplo, JavaScriptse mostrará el proceso de llamar a la API nativa. Elegimos el dsBridge de la biblioteca de código abierto en Github como JsBridgecomunicación.

  • Primero implemente la API para obtener el modelo de teléfono en el nativo
class JSAPI {
    
    
 
 public Object getPhoneModel(Object msg) {
    
    
   return Build.MODEL;
 }
}    
  • WebViewRegistre la API nativa a través JsBridgedel
import wendu.dsbridge.DWebView
...
//DWebView继承自WebView,由dsBridge提供  
DWebView dwebView = (DWebView) findViewById(R.id.dwebview);
//注册原生API到JsBridge
dwebView.addJavascriptObject(new JsAPI(), null);
  • JavaScriptLlame a la API nativa en
var dsBridge = require("dsbridge")
//直接调用原生API `getPhoneModel`
var model = dsBridge.call("getPhoneModel");
//打印机型
console.log(model);

El ejemplo anterior demuestra JavaScriptel proceso de llamar a las API nativas. Del mismo modo, en términos generales, las excelentes JsBridgetambién admiten llamadas nativas JavaScripty dsBridgetambién son compatibles. Si está interesado, puede ir a la página de inicio del proyecto Github dsBridge para ver.

Ahora, echemos la vista atrás, una aplicación híbrida no es más que pre-implementar una serie de APIs para JavaScriptllamar en el primer paso, para que JavaScripttengas la capacidad de acceder a las funciones del sistema, viendo esto, creo que también puedes implementar un desarrollo híbrido. marco usted mismo.

resumen

Las ventajas de las aplicaciones híbridas son: el contenido dinámico se puede desarrollar con H5, y H5 es una pila de tecnología web. La pila de tecnología web tiene una ecología abierta y recursos comunitarios ricos, y la eficiencia de desarrollo general es alta. La desventaja es que la experiencia de rendimiento no es buena, y para interfaces de usuario o animaciones complejas, WebView a veces puede verse abrumado.

Solución 2: desarrollo de JavaScript + renderizado nativo

Programación reactiva reactiva

React es un marco web receptivo. Primero comprendamos dos conceptos importantes: el árbol DOM y la programación receptiva.

árbol DOM

El modelo de objeto de documento (DOM para abreviar) es una interfaz de programación estándar recomendada por la organización W3C para procesar el lenguaje de marcado extensible, una forma independiente de plataforma e idioma para acceder y modificar el contenido y la estructura de un documento. En otras palabras, esta es una interfaz estándar para representar y manipular un documento HTML o XML. En términos simples, DOM es el árbol de documentos, que corresponde al árbol de control de la interfaz de usuario. En el desarrollo front-end, generalmente se refiere al árbol de representación correspondiente a HTML, pero en un sentido amplio, DOM también puede referirse al árbol de control. correspondiente al archivo de diseño XML en Android. El término operación DOM se refiere a operar directamente el árbol de representación (o árbol de control). Por lo tanto, se puede ver que el árbol DOM y el árbol de control son conceptos equivalentes, pero el primero es a menudo se usa en el desarrollo web, mientras que el último se usa a menudo en el desarrollo nativo.

programación reactiva

Se presenta una idea importante en React: la interfaz de usuario cambia automáticamente cuando cambia el estado. El marco React en sí mismo realiza el trabajo de reconstruir la interfaz de usuario en respuesta a los eventos de cambio de estado del usuario. Este es un paradigma de programación receptivo típico . Resumamos los principios receptivos en React:

  • Los desarrolladores solo deben prestar atención a la transferencia de estado (datos).Cuando el estado cambia, el marco React reconstruirá automáticamente la interfaz de usuario de acuerdo con el nuevo estado.
  • Después de recibir la notificación de cambio de estado del usuario, el marco React calculará la parte modificada del árbol a través del algoritmo Diff basado en el árbol de representación actual y el último cambio de estado, y luego solo actualizará la parte modificada (operación DOM), evitando así la árbol completo Refactorización del árbol para mejorar el rendimiento.

Vale la pena señalar que en el segundo paso, después de los cambios de estado, el marco de React no calculará ni renderizará inmediatamente la parte modificada del árbol DOM. Por el contrario, React construirá una capa abstracta sobre la base del árbol DOM, es decir, el árbol DOM virtual Cualquier cambio realizado en los datos y el estado se sincronizará de manera automática y eficiente con el DOM virtual y, finalmente, se sincronizará con el DOM real en lotes, en lugar de manipular el DOM cada vez que haya un cambio.

¿Por qué no podemos manipular directamente el árbol DOM cada vez que cambia? Esto se debe a que cada operación de DOM en el navegador puede hacer que el navegador se vuelva a dibujar o a fluir (volver a escribir el diseño, determinar el tamaño y la posición del nodo DOM):

  1. Si el DOM solo cambia en apariencia y estilo, como un cambio de color, hará que el navegador vuelva a dibujar la interfaz.
  2. Si la estructura del árbol DOM cambia, como el tamaño, el diseño, los nodos ocultos, etc., el navegador debe volver a fluir.

El redibujado y el reflujo del navegador son operaciones relativamente costosas. Si cada cambio opera directamente en el DOM, esto causará problemas de rendimiento. Sin embargo, las operaciones por lotes solo desencadenarán una actualización del DOM, que tendrá un mayor rendimiento.

reaccionar nativo

React Native (RN para abreviar) es un marco de desarrollo de aplicaciones móviles multiplataforma de código abierto de Facebook en abril de 2015. Es un derivado del marco web de código abierto anterior de Facebook React en la plataforma de aplicaciones móviles nativa. Actualmente es compatible con iOS y Android plataformas RN utiliza el lenguaje JSX (JavaScript extendido, principalmente para escribir etiquetas HTML en JavaScript) y CSS para desarrollar aplicaciones móviles. Por lo tanto, los técnicos que están familiarizados con el desarrollo web front-end pueden ingresar al campo del desarrollo de aplicaciones móviles con muy poco aprendizaje.

Dado que los principios de RN y React son similares, y Flutter también se inspira en React en la capa de aplicación, muchas ideas también son similares.

Como se mencionó anteriormente, React Native es un derivado de React en la plataforma de aplicaciones móviles nativas ¿Cuál es la principal diferencia entre los dos? En realidad, la principal diferencia es qué objetos son asignados por el DOM virtual. El DOM virtual en React finalmente se asignará al árbol DOM del navegador, mientras que el DOM virtual en RN se asignará a los controles nativos a través de JavaScriptCore.

JavaScriptCore es un intérprete de JavaScript, tiene dos funciones principales en React Native:

  1. Proporciona un entorno de tiempo de ejecución para JavaScript.

  2. Es un puente de comunicación entre JavaScript y las aplicaciones nativas, y su función es la misma que la de JsBridge, de hecho, en iOS, muchas implementaciones de JsBridge están basadas en JavaScriptCore.
    El proceso de asignación de DOM virtual a controles nativos en RN se divide principalmente en dos pasos:

  3. Paso de mensajes de diseño; pasar información de diseño de DOM virtual a nativo;

  4. Representación nativa a través de los controles nativos correspondientes según la información de diseño;

Hasta ahora, React Native ha logrado ser multiplataforma. En comparación con las aplicaciones híbridas, dado que React Native genera controles nativos, el rendimiento será mejor que el de H5 en aplicaciones híbridas. Al mismo tiempo, React Native proporciona muchos componentes web correspondientes a los componentes nativos. En la mayoría de los casos, los desarrolladores solo necesitan usar la pila de tecnología Web Puede desarrollar App. Podemos encontrar que de esta manera, se puede mantener un código y puede ser multiplataforma.

Weex

Weex es un marco de desarrollo móvil multiplataforma lanzado por Alibaba en 2016. Sus ideas y principios son similares a React Native. La capa inferior se representa de forma nativa. La diferencia es la sintaxis de desarrollo de la capa de aplicación (es decir, DSL, lenguaje específico del dominio): Weex admite la sintaxis de Vue Grammar y Rax, la sintaxis de DSL (lenguaje específico del dominio) de Rax se crea en función de la sintaxis de React JSX, mientras que el DSL de RN se basa en React y no es compatible con Vue.

resumen

Las principales ventajas del desarrollo JavaScript + renderizado nativo son las siguientes:

  1. Usando la pila de tecnología de desarrollo web, la comunidad es enorme, aprende rápido y el costo de desarrollo es relativamente bajo.
  2. Representación nativa, el rendimiento ha mejorado mucho en comparación con H5.
  3. Es dinámico y admite actualizaciones en caliente.

insuficiente:

  1. Se requiere comunicación entre JavaScript y nativo durante el renderizado. En algunos escenarios, como arrastrar, la comunicación frecuente puede causar retrasos.
  2. JavaScript es un lenguaje de secuencias de comandos que debe interpretarse y ejecutarse durante la ejecución (este método de ejecución generalmente se denomina JIT, o Just In Time, que se refiere a generar código de máquina en tiempo real durante la ejecución), eficiencia de ejecución y lenguaje compilado (la ejecución método de lenguaje compilado es AOT, es decir, Ahead Of Time, lo que significa que el código fuente ha sido preprocesado antes de que se ejecute el código. Este preprocesamiento generalmente compila el código fuente en código de máquina o algún tipo de código intermedio) y todavía hay un hueco.
  3. Debido a que el renderizado se basa en controles nativos, los controles en diferentes plataformas deben mantenerse por separado, y cuando se actualiza el sistema, los controles de la comunidad pueden retrasarse; además, su sistema de control también estará limitado por el sistema de interfaz de usuario nativo, por ejemplo, en Android, conflictos de gestos Las reglas de desambiguación son fijas, lo que hace que el problema del conflicto de gestos sea muy difícil cuando se usan controles anidados escritos por diferentes personas. Esto conducirá a altos costos de desarrollo y mantenimiento si se requieren componentes de representación nativos personalizados.

Solución 3: interfaz de usuario autodibujada + nativa

La idea de esta tecnología es dibujar la interfaz de usuario mediante la implementación de un motor de renderizado con una interfaz unificada en diferentes plataformas, sin depender de los controles nativos del sistema, por lo que se puede lograr la consistencia de la interfaz de usuario de las diferentes plataformas.

Tenga en cuenta que el motor de dibujo automático resuelve el problema multiplataforma de la interfaz de usuario. Si hay otras llamadas de capacidad del sistema involucradas, el desarrollo nativo aún está involucrado. Las ventajas de esta tecnología de plataforma son las siguientes:

  1. Alto rendimiento; dado que el motor de dibujo automático llama directamente a la API del sistema para dibujar la interfaz de usuario, el rendimiento es similar al de los controles nativos.

  2. Biblioteca de componentes flexible y fácil de mantener, alta fidelidad y consistencia de la apariencia de la interfaz de usuario; dado que la representación de la interfaz de usuario no depende de los controles nativos, no es necesario mantener una biblioteca de componentes separada de acuerdo con los controles en diferentes plataformas, por lo que el código es fácil de mantener . Dado que la biblioteca de componentes es el mismo conjunto de código y el mismo motor de renderizado, la apariencia de visualización del componente puede lograr una alta fidelidad y una gran consistencia en diferentes plataformas; además, debido a que no se basa en controles nativos, no estará limitado por el sistema de diseño nativo Este sistema de diseño será muy flexible.

insuficiente:

  1. Dinámica insuficiente; para garantizar el rendimiento del dibujo de la interfaz de usuario, los sistemas de interfaz de usuario de dibujo automático generalmente usan el modo AOT para compilar sus paquetes de lanzamiento, por lo que una vez que se lanza la aplicación, no puede entregar de forma dinámica código como marcos híbridos y RN que usan JavaScript (JIT) como el lenguaje de desarrollo.
  2. Baja eficiencia de desarrollo de aplicaciones: Qt utiliza C++ como su lenguaje de desarrollo, y la eficiencia de la programación afectará directamente la eficiencia del desarrollo de App. Como lenguaje estático, C++ no es tan flexible como un lenguaje dinámico como JavaScript en términos de desarrollo de UI. , C ++ debe desarrollarse O administrar manualmente la asignación de memoria, no hay un mecanismo de recolección de basura (GC) en JavaScript y Java.

Es posible que hayas adivinado que Flutter pertenece a este tipo de tecnología multiplataforma. Sí, Flutter implementa un conjunto de motores de dibujo automático y tiene su propio sistema de diseño de interfaz de usuario. Al mismo tiempo, ha logrado grandes avances en la eficiencia del desarrollo. Sin embargo, la idea de un motor autodibujado no es un concepto nuevo, Flutter no es el primero en intentar hacer esto, antes había un representante típico, es decir, el famoso Qt.

qt móvil

Qt es un marco de desarrollo de aplicaciones de interfaz gráfica de usuario C ++ multiplataforma desarrollado por Qt Company en 1991. En 2008, Nokia adquirió la tecnología Qt Company y Qt se convirtió en una herramienta de lenguaje de programación bajo Nokia. En 2012, Digia adquirió Qt. En abril de 2014, se lanzó oficialmente el entorno de desarrollo integrado multiplataforma Qt Creator 3.1.0, totalmente compatible con iOS, agregando WinRT, Beautifier y otros complementos, abandonando el soporte de depuración de GDB sin la interfaz de Python e integrando C / basado en Clang. Módulo de código C ++, y realizó ajustes en la compatibilidad con Android, hasta ahora es totalmente compatible con iOS, Android, WP, proporciona a los desarrolladores de aplicaciones todas las funciones necesarias para crear una interfaz gráfica de usuario.

Sin embargo, aunque Qt ha logrado un gran éxito en el lado de la PC y es muy buscado por la comunidad, no ha tenido un buen desempeño en el lado móvil. En los últimos años, la voz de Qt rara vez se ha escuchado, a pesar de que Qt es una cruz. -plataforma plataforma de dibujo automático para el desarrollo móvil. El pionero del motor, pero se convirtió en un mártir. Las razones pueden ser las siguientes:

  • La comunidad de desarrollo móvil de Qt es demasiado pequeña, los materiales de aprendizaje son insuficientes y la ecología no es buena.
  • La promoción oficial no es buena y el apoyo no es suficiente.
  • El terminal móvil arrancó tarde, y el mercado ha sido ocupado por otros marcos dinámicos (Híbrido y RN).
  • En el desarrollo móvil, el desarrollo C++ tiene una desventaja inherente en comparación con la pila de desarrollo web y el resultado directo es que la eficiencia del desarrollo Qt es demasiado baja.

Aleteo

Flutter es un marco lanzado por Google para crear aplicaciones móviles multiplataforma de alto rendimiento. Flutter, al igual que Qt mobile, no utiliza controles nativos, al contrario, ambos implementan un motor de autodibujado y utilizan su propio sistema de maquetación y dibujo. Entonces, nos preocuparemos, si los problemas que enfrenta Qt mobile son los mismos que Flutter, ¿Flutter seguirá los pasos de Qt mobile y se convertirá en otro mártir? Desde que nació Flutter en 2017, después de años de experiencia, el ecosistema de Flutter ha crecido rápidamente. Hay muchos casos exitosos basados ​​en Flutter en el país y en el extranjero, y las empresas nacionales de Internet básicamente tienen equipos dedicados a Flutter. En resumen, Flutter se ha desarrollado rápidamente, ha recibido una gran atención y reconocimiento en la industria y ha sido muy bien recibido por los desarrolladores, convirtiéndose en uno de los marcos más populares en el desarrollo móvil cruzado.

Ahora, hagamos una comparación con Qt mobile:

  1. Ecología: el ecosistema de Flutter se está desarrollando rápidamente y la comunidad es muy activa. Tanto la cantidad de desarrolladores como los componentes de terceros ya son muy impresionantes.
  2. Soporte técnico: ahora Google está promocionando vigorosamente Flutter. Muchos de los autores de Flutter son del equipo de Chromium y son muy activos en Github. Desde otra perspectiva, desde el nacimiento de Flutter hasta el presente, los frecuentes lanzamientos de versiones también muestran que Google ha invertido muchos recursos en Flutter, por lo que no hay necesidad de preocuparse por el soporte técnico oficial.
  3. Eficiencia de desarrollo: un conjunto de código, operación multiterminal y, durante el proceso de desarrollo, la recarga en caliente de Flutter puede ayudar a los desarrolladores a probar, crear UI, agregar funciones y corregir errores rápidamente. Se pueden lograr recargas en caliente de nivel de milisegundos en simuladores de iOS y Android o dispositivos reales sin perder el estado. Esto es realmente genial. Créanme, si usted es un desarrollador nativo, después de experimentar el flujo de desarrollo de Flutter, probablemente no quiera volver a hacer nativo de nuevo. Después de todo, pocas personas no se quejan de la velocidad de compilación de desarrollo nativo.

GC en aleteo

Flutter utiliza Dart como lenguaje de desarrollo y mecanismo de tiempo de ejecución.Dart siempre ha conservado el mecanismo de tiempo de ejecución, ya sea en modo de depuración (debug) o en modo de liberación (release), pero existen grandes diferencias entre los dos métodos de construcción.

  • En el modo de depuración , Dart carga todas las canalizaciones (todos los accesorios que deben usarse) en el dispositivo: tiempo de ejecución de Dart , compilador/intérprete JIT (el justo a tiempo) (JIT para Android e intérprete para iOS), depuración y análisis de rendimiento servicios.
  • En el modo de lanzamiento , se utilizará la compilación AOT , se eliminará JIT y se conservará el tiempo de ejecución de Dart, ya que el tiempo de ejecución de Dart es el principal contribuyente de la aplicación Flutter.

inserte la descripción de la imagen aquí

El tiempo de ejecución de Dart incluye un componente muy importante: el recolector de basura, cuya función principal es asignar y liberar memoria cuando se crea una instancia de un objeto o se vuelve inalcanzable.

Durante la ejecución de Flutter, habrá muchos Objetos. Se crean antes (en realidad aún) de renderizado StatelessWidget. StatefulWidgetCuando el estado cambie, serán destruidos nuevamente. De hecho, tienen una vida útil muy corta. Cuando construimos una interfaz de usuario compleja, habrá miles de estos Widget.

Entonces, como desarrolladores de Flutter, ¿debemos preocuparnos de si el recolector de basura puede ayudarnos a administrarlos bien? (¿Traerá muchos problemas de rendimiento?) Dado que Flutter crea y destruye con frecuencia estos Widgetobjetos (Objetos), ¿deberían los desarrolladores tomar medidas para limitar este comportamiento?

  • Para los nuevos desarrolladores de Flutter, si saben que un Widget no cambiará con el tiempo, crean una Widgetreferencia y los colocan Statepara que no se destruyan y reconstruyan, lo cual no es poco común.

no hay necesidad de hacer eso

  • Es completamente innecesario preocuparse por el GC de Dart, porque su arquitectura generacional está especialmente optimizada para permitirnos crear y destruir objetos con frecuencia. En la mayoría de los casos, solo necesitamos dejar que el motor de Flutter cree y destruya todo lo que quiera Widget.

Dardo GC

El GC de Dart es generacional (generacional) y consta de dos etapas: Young Space Scavenger (recicla el carroñero para la bolsa joven) y Parallel Marking and Concurrent Sweeping (recicla los recolectores de barrido para la vieja generación)

Planificación

Para minimizar el impacto de GC en la aplicación y el rendimiento de la interfaz de usuario, el recolector de basura proporciona enlaces al motor de Flutter que lo alertarán cuando el motor detecte que la aplicación está inactiva sin interacción del usuario. Esto le da a la ventana del recolector de basura la oportunidad de ejecutar su fase de recolección sin afectar el rendimiento.

El recolector de elementos no utilizados también puede ejecutar la compactación deslizante durante estos intervalos de inactividad, lo que minimiza la sobrecarga de memoria al reducir la fragmentación de la memoria.

inserte la descripción de la imagen aquí

Fase 1: joven carroñero espacial

Esta etapa es principalmente para limpiar algunos objetos de corta duración, como StatelessWidget. Cuando está bloqueado, su velocidad de limpieza es mucho más rápida que el método de marcado/barrido de segunda generación. Y combinado con la programación, la finalización puede eliminar el fenómeno de pausa cuando el programa se está ejecutando.

Básicamente, los objetos se asignan a un espacio contiguo en la memoria y, cuando se crean, se asignan al siguiente espacio disponible hasta que se llena la memoria asignada. Dart utiliza la asignación de punteros de relieve para asignar rápidamente un nuevo espacio, lo que hace que el proceso sea muy rápido. (Si mantiene free_list y redistribuye como malloc, la eficiencia es muy baja)

El nuevo espacio en el que se asignan los nuevos objetos consta de dos mitades, denominadas semiespacios. Solo la mitad del espacio se usa a la vez: la mitad está activa y la otra mitad está inactiva. Los objetos nuevos se asignan a la mitad activa del área y, una vez que se llena la mitad activa, los objetos activos se copian del área activa al área inactiva y los objetos inactivos se borran. La mitad inactiva luego se vuelve activa y el proceso se repite. (Esto es muy similar al espacio de supervivencia de la nueva generación en la estrategia de reciclaje generacional de JVM)

Para determinar qué objetos están vivos o muertos, el GC comienza con los objetos raíz (como las variables de pila) y examina a qué se refieren. Luego mueva el Objeto referenciado (supervivencia) al estado inactivo, y directamente se moverán todos los Objetos sobrevivientes. Los objetos muertos no tienen referencias y, por lo tanto, se dejan; los objetos vivos se copiarán sobre ellos en futuros eventos de recolección de basura.

Para obtener más información sobre esto, consulte el Algoritmo de Cheney.

inserte la descripción de la imagen aquí

Fase 2: Marcado paralelo y barrido simultáneo

Cuando los objetos alcanzan cierta antigüedad (no recuperada por GC en la primera fase), serán promovidos a un nuevo espacio de memoria administrado por el recopilador de segunda generación: barrido de marca.

Esta técnica de recolección de basura tiene dos fases: primero atravesar el gráfico de objetos y luego marcar los objetos que todavía están en uso. En la segunda fase, se escanea toda la memoria y se recuperan los objetos no marcados. Luego borre todas las banderas.

Esta técnica de GC bloquea la fase de marcado; no se producen mutaciones de memoria y el subproceso de la interfaz de usuario también está bloqueado. Pero dado que los objetos efímeros ya se procesan en la fase Young Space Scavenger, esta situación es muy rara. Pero a veces es necesario pausar el tiempo de ejecución de Dart para ejecutar esta forma de GC. Teniendo en cuenta las capacidades de la colección planificada de Flutter, su impacto debe minimizarse.

Es importante tener en cuenta que esta forma de GC ocurrirá con más frecuencia si una aplicación no sigue la suposición generacional (es decir, la suposición de que la mayoría de los objetos mueren jóvenes). Dado Widgetcómo funcionan las cosas en Flutter, es poco probable que esto suceda, pero hay algo que saber.

Aislar

Vale la pena señalar que los mecanismos en Dart Isolatetienen el concepto de un montón privado , independientes entre sí . Cada uno Isolatetiene su propio hilo separado para ejecutar, y Isolateel GC de cada uno no afecta Isolateel rendimiento del otro. El uso Isolatees una excelente manera de evitar el bloqueo de la interfaz de usuario y descargar actividades que requieren un uso intensivo del proceso. (Las operaciones que consumen mucho tiempo pueden usar Aislar)

Debes entender esto: Dart usa un poderoso recolector de basura generacional para minimizar el impacto en el rendimiento de GC en Flutter. Por lo tanto, no necesita preocuparse por el recolector de basura de Dart y puede concentrarse en su negocio con confianza.


referencia:

Supongo que te gusta

Origin blog.csdn.net/lyabc123456/article/details/130716845
Recomendado
Clasificación