Flutter Notes | Flutter Core Principles (1) Architecture and Life Cycle

Flutter Architecture

insert image description here
To put it simply, Flutter can be divided into three layers from top to bottom: framework layer , engine layer and embedding layer . We will introduce them separately below:

1. Frame layer

Flutter Framework , the framework layer. This is a pure Dart-implemented SDK, which implements a set of basic libraries, from the bottom up, let's briefly introduce:

  • The bottom two layers (Foundation and Animation, Painting, Gestures) are merged into a dart UI layer in some Google videos, corresponding to the dart:ui package in Flutter, which is the underlying UI library exposed by Flutter Engine, providing animation, Gestures and drawing capabilities.

  • The Rendering layer, that is, the rendering layer, is an abstract layout layer that depends on the Dart UI layer. The rendering layer will build a rendering tree composed of renderable objects. When these objects are dynamically updated, the rendering tree will Figure out what changed, and update the render. The rendering layer can be said to be the core part of the Flutter framework layer. In addition to determining the position and size of each rendering object, it also performs coordinate transformation and drawing (calling the underlying dart:ui).

  • The Widgets layer is a set of basic component libraries provided by Flutter. On top of the basic component library, Flutter also provides two visual style component libraries, Material and Cupertino, which respectively implement the Material and iOS design specifications.

The Flutter framework is relatively small, because some higher-level functions that developers may use have been split into different packages, implemented using Dart and Flutter's core libraries, including platform plugins such as camera and webview , and platform-independent features such as animations .

2. Engine layer

Engine , the engine layer. There is no doubt that it is the core of Flutter. This layer is mainly implemented in C++, including Skia engine, Dart runtime (Dart runtime), text layout engine, etc. When the code calls the dart:ui library, the call will eventually go to the engine layer, and then the real drawing and display will be realized.

3. Embedding layer

Embedder , the embedding layer. The final rendering and interaction of Flutter depends on the operating system API of its platform, and the embedding layer is mainly to "install" the Flutter engine on a specific platform. The embedding layer is written in the language of the current platform, such as Java and C++ for Android, Objective-C and Objective-C++ for iOS and macOS, and C++ for Windows and Linux. Flutter code can be integrated into existing applications in a modular way through the embedding layer, or it can be used as the main body of the application. Flutter itself contains the embedding layer of each common platform. If Flutter wants to support a new platform in the future, you need to write an embedding layer for the new platform.

Generally speaking, developers do not need to be aware of the existence of Engine and Embedder (if they do not need to call platform system services), Framework is what developers need to interact with directly, so it is also at the top of the entire layered architecture model.

Widget interface

In Flutter, the function is to widget" describe the configuration information of a UI element ". That is to say, it does not actually represent the display elements that are finally drawn Widgeton the device screen . The content, alignment, and text style of are all its configuration information. Let's take a look at the class declaration:WidgetTextWidget

 // 不可变的
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;
  }
  ...
}
  • @immutableIt means that the Widget is immutable , which restricts Widgetthe properties defined in (that is, configuration information) to be immutable ( final), why Widgetare the properties defined in not allowed to change? This is because in Flutter, if the attribute changes, the tree will be rebuilt Widget, that is, a new Widgetinstance will be recreated to replace the old Widgetinstance , so Widgetit is meaningless to allow the attribute change, because once Widgetits own attribute changes, it will be replaced . This is why Widgetproperties defined in must be final.
  • WidgetThe class inherits from , DiagnosticableTreethe "diagnostic tree" , whose main role is to provide debugging information.DiagnosticableTree
  • Key: This keyattribute is similar to React/Vuethe one in key, the main function is to decide whether to reuse the old one next timebuildwidget , the decision condition is in canUpdate()the method.
  • createElement(): As mentioned above, "one widgetcan correspond to multiple Element"; when the Flutter framework builds the UI tree, it will first call this method to generate objects corresponding to the nodes Element. This method is implicitly called by the Flutter framework, and it is basically not called during our development.
  • debugFillProperties(...)The method of overriding the parent class is mainly to set some characteristics of the diagnostic tree.
  • canUpdate(...)It is a static method, which is mainly used to reuse the old widget when the widget tree is rebuilt . In fact, it should be: whether to use the new widget object to update the configuration of the corresponding Element object on the old UI tree ; through We can see its source code, as long as the newWidgetsum of and is equal , the new widget will be used to update the configuration of the object, otherwise a new one will be created .oldWidgetruntimeTypekeyElementElement

In Flutter, it can be said that everything is everything Widget. Even a centering ability, in traditional imperative UI development, is usually set as a property, but in Flutter it is abstracted into a named Centercomponent. In addition, Flutter advocates radical combination development, that is, to Widgetconstruct your goals through a series of basic structures as much as possible Widget.

Based on the above two characteristics, Flutter's code will be full of various Widget, and every frame of UI update means partial Widgetreconstruction. You may worry whether this design is too bloated and inefficient. In fact, on the contrary, this is the cornerstone of Flutter's ability to perform high-performance rendering. The reason why Flutter is designed in this way is also intentional based on the following two facts:

  • WidgetThe more nodes on the Widget Tree , the more accurate and smaller the part that needs to be reconstructed through the DiffWidget algorithm, and the main performance bottleneck of UI rendering is the reconstruction of nodes.

  • The object model and GC model of the Dart language optimize the fast read allocation and recovery of small objects, and it isWidget this small object .

Three Trees in Flutter

Since Widget only describes the configuration information of a UI element, who does the actual layout and drawing?

The processing flow of the Flutter framework is as follows:

  1. WidgetGenerate a tree based on the tree Element, and Elementthe nodes in the tree inherit from the Elementclass.
  2. The tree (render tree) is generated according to Elementthe tree Render, and the nodes in the render tree inherit from RenderObjectthe class.
  3. Generate a tree based on the rendering tree Layer, and then display it on the screen. LayerThe nodes in the tree all inherit from Layerthe class.

The real layout and rendering logic is in Renderthe tree, Elementwhich is the glue of Widgetand , which can be understood as an intermediate agent.RenderObject

insert image description here

insert image description here
insert image description here
insert image description here

The respective functions of the three trees are:

  • Widget: Responsible for configuration. In order to Elementdescribe the configuration information of the UI, it has a public API. This part is also a part that developers can directly perceive and use.
  • Element: The manager of Flutter Virtual DOM, widgetthe life cycle it manages, represents the UI data that actually exists in memory, it is responsible for the widgetinstantiation of specific locations in the tree, widgetthe references it holds, and the management of parent-child relationships in the tree, in fact, Widget Treewith RenderObject Treeare Element Treegenerated by the driver.
  • RenderObject: Responsible for handling size, layout and drawing, it will draw itself, place child nodes, etc.

Note here:

  • Among the three trees, Elementand Widgetare in one-to-one correspondence, but Elementand RenderObjectare not in one-to-one correspondence. For example StatelessWidgetand StatefulWidgethave no corresponding RenderObject.
  • The rendering tree will generate a tree before it is displayed on the screen Layer, so there are actually four trees in Flutter, but we only need to understand the above three trees.

StatelessWidget

StatelessWidgetRelatively simple, it inherits from a widgetclass and overrides createElement()the method:


StatelessElement createElement() => StatelessElement(this);

StatelessElementIndirectly inherited from the Elementclass, StatelessWidgetcorresponding to (as its configuration data).

StatelessWidgetUsed in scenarios that do not need to maintain state , it usually builds the UI buildby nesting other in the method widget, and recursively builds its nested ones during the construction process widget.

Here is a simple example:

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

We can then use it as follows:

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

By convention, widgetthe constructor parameters should use named parameters , and the parameters that must be passed in the named parameters should be added with requiredkeywords, which is beneficial for static code analyzers to check; when inheriting widget, the first parameter should usually be Key. Also, if widgetreceivers are required widget, the childor childrenparameter should usually be placed last in the parameter list. Also by convention, widgetproperties should be declared as much as possible tofinal prevent accidental changes.

Context

buildThe method has a contextparameter, which is BuildContextan instance of the class, representing the current contextwidget in widgetthe tree , and each corresponds to a object.widgetcontext

In fact, it is a handle ( ) to perform "related operations" in the contextcurrent position widgetin the tree , for example, it provides methods for traversing the tree upwards from the current and finding the parent by type .widgethandlewidgetwidgetwidgetwidget

widgetHere's an example of getting the parent in a subtree :

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

insert image description here

StatefulWidget

If you have one StatelessWidget, why have another StatefulWidget? WidgetAll are immutable in Flutter , but actually need to be refreshed according to the corresponding state Widget, so it is generated StatefulWdiget, which is composed StatefulWidgetof two objects Widgetand .State

StatefulWidgetIt also inherits from widgetthe class and rewrites createElement()the method. The difference is that the returned Elementobjects are not the same, but one is returned StatefulElement; in addition, StatefulWidgeta new interface is added to the class createState().

abstract class StatefulWidget extends Widget {
    
    
  const StatefulWidget({
    
     Key key }) : super(key: key);
    
  
  StatefulElement createElement() => StatefulElement(this);
    
  
  State createState();
}
  • StatefulElementIndirectly inherited from the Elementclass, StatefulWidgetcorresponding to (as its configuration data). StatefulElementmay be called multiple times createState()to create the state( State) object. Developers don't need to care about this method.

  • createState()For creating and StatefulWidgetrelated states, developers must implement this method in subclasses. It StatefulWidgetmay be called multiple times during the lifetime of the . For example, when a tree StatefulWidgetis inserted into widgetmultiple positions at the same time, the Flutter framework will call this method to generate an independent Stateinstance for each position. In fact, it is essentially one StatefulElementcorresponding to one Stateinstance.

In StatefulWidget, Stateobjects StatefulElementhave a one-to-one correspondence with each other, so in Flutter's SDK documentation, you can often see descriptions such as "remove a State object from the tree" or "insert a State object into the tree". The tree refers to the Element tree generated by the widget tree. The "tree" is often mentioned in Flutter's SDK documentation, and we can judge which tree it refers to based on the context. In fact, for users, we don’t need to care about which tree it is. No matter what kind of tree its ultimate goal is to describe the structure and drawing information of the UI, we only need to understand it as “a node tree that constitutes the user interface” , don't get too entangled in these concepts.

State

A StatefulWidgetclass corresponds to a Stateclass, indicating the state to be maintained Statecorresponding to it , and the saved state information in it can be:StatefulWidgetState

  1. StateCan be read synchronously at widgetbuild time .
  2. StateIt can be changed during widgetthe life cycle , and the method can be manually called to change, which will notify the Flutter framework of the state change. After receiving the message, the Flutter framework will call its method again to rebuild the tree, so as to achieve the purpose of updating the UI.setState()Statebuildwidget

StateThere are two common properties in :

  1. State.widgetState, through which we can get the instance associated with this instance widget, which is dynamically set by the Flutter framework. Note that this association is not permanent, because in the application life cycle, the instance of a node on the UI tree widgetmay change when it is rebuilt, but Statethe instance will only be created when it is inserted into the tree for the first time. When rebuilding, if widgetis modified, the Flutter framework will dynamically set to State.widgetpoint to the new widgetinstance .

  2. State.context, it is StatefulWidgetcorresponding BuildContextand has the same StatelessWidgeteffect BuildContext. When the framework needs to create one StatefulWidget, it calls StatefulWidget.createState()the method and then associates Statethe object with the object BuildContext. This association is permanent, Statethe object never changes it BuildContext, but BuildContextitself can be moved around the tree.

State life cycle

After Statethe object is createState()created by the method, it will call initState()the method and start its own life cycle:

insert image description here

The meaning of each callback function:

  • initState(): It will be called when widgetit is inserted into the tree for the first time widget. For each Stateobject, the Flutter framework will only call this callback once . Therefore, you usually do some one-time operations in this callback, such as state initialization and subtree event subscription notification etc.

    Note: It cannot initStatebe called in BuildContext.dependOnInheritedWidgetOfExactType(this method is used to widgetget widgetthe closest parent on the tree InheritedWidget), because after the initialization is completed, widgetthe tree InheritFrom widgetmay also change, so the correct way should be in build()the method or didChangeDependencies()in call it.

  • didChangeDependencies(): StateIt will be called when the dependency of the object changes; for example, if build()one is included in the previous InheritedWidgetand then changed build()in the later, then the callbacks of the sub will be called at this time. A typical scenario is that when the system language or application theme changes, the Flutter framework will notify to call this callback. It should be noted that when the component is first created and mounted (including re-created), the corresponding one will also be called.Inherited widgetInheritedWidgetwidgetdidChangeDependencies()LocalewidgetdidChangeDependencies

  • build(): Mainly used to build widgetsubtrees, it will be called in the following scenarios:

    1. after the call initState().
    2. after the call didUpdateWidget().
    3. after the call setState().
    4. after the call didChangeDependencies().
    5. Called after an object has been removedState from one location in the tree and reinserted elsewhere in the tree .deactivate
  • reassemble(): A callback specially provided for development and debugging. It will be called during hot reload . This callback will never be called in Release mode.

  • didUpdateWidget(): When widgetrebuilding, the Flutter framework will call widget.canUpdateto detect widgetthe old and new nodes at the same position in the tree, and then decide whether an update is required, and if widget.canUpdateit returns true, this callback will be called.

    As mentioned before, it will return when widget.canUpdatethe old and new are equal at the same time , which means that it will be called in this case . (In fact, the flutter framework here will create a new Widget, bind this State, and pass the old Widget in this function) When the upper-level node rebuilds the widget, that is, when the state of the upper-level component changes, it will also trigger the execution of the sub-widget . It should be noted that if the change of the controller is involved, the listener of the old controller needs to be removed in this function, and the listener of the new controller needs to be created.widgetkeyruntimeType truedidUpdateWidget()didUpdateWidget

  • deactivate(): StateThis callback is called when the object is removed from the tree. In some scenarios, the Flutter framework will Statereinsert the object into the tree, such as Statewhen the subtree containing the object moves from one position of the tree to another (this can be GlobalKeyachieved by ). The method will be called next if it was removed and not reinserted into the tree dispose().

  • dispose(): StateCalled when the object is permanently removed from the tree; resources are usually released, subscriptions are unsubscribed, animations are canceled, etc. in this callback.

Illustrate with the following counter example:

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

Note: When inheriting StatefulWidgetand rewriting its method, for @mustCallSuperthe parent class method that contains annotations, the parent class method must be called in the subclass method.

Next create a new route page where we only display one CounterWidget:

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

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

We run the application and open the routing page. After the new routing page is opened, a number 0 will appear in the center of the screen, and then the console log output:

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

As you can see, the first method is called when StatefulWidgetinserting into the tree.widgetinitState

Then we click the ⚡️ button to hot reload, and the console output log is as follows:

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

You can see that buildbefore the method, only reassembleand didUpdateWidgetare called.

Next, we widgetremove in the tree CounterWidget, changing the method StateLifecycleTestof to build:

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

Then hot reload, the log is as follows:

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

As you can see, when removing CounterWidgetfrom widgetthe tree, deactiveand disposeare called in turn.

setState()

setState()The method is the only legal way to modify the state in Flutter. This method will notify the framework that the internal state of this object has changed. The calling method is: it will provide setState(() {_myState = newValue;});a callback method in which the developer must modify the state value, and the callback will immediately is called synchronously . Note that you must not perform time-consuming calculations in this callback, let alone return one Future(the callback cannot be asyncmarked with " "), this callback is usually only used to wrap the actual change to the state.

After calling setState(), the current widgetcorresponding elementobject will be marked as dirty, and then build()the method will be called to refresh the UI. The following is setState()the life cycle of State after joining:
insert image description here

It seems that the State life cycle can actually be divided into three stages:

  • Initialization: constructor, initState(),didChangeDependencies()
  • State change: Execution triggered by configuration change didUpdateWidget(), build()Execution caused setState()bybuild()
  • Component removal: deactivate(),dispose()

Why does StatefulWidget separate State and Widget?

StatefulWidgetWhy put buildthe method Statein the class and not in the StatefulWidget?

  • This is mainly for development flexibility and performance considerations.

If you build()put the method StatefulWidgetin, there are two problems in terms of flexibility:

1. Inconvenient state access.

Just imagine, if we StatefulWidgethave a lot of states, and buildthe method is called every time the state changes, since the state is stored in State, if buildthe method is StatefulWidgetin, then buildthe methods and 状态are in two classes respectively, then reading the state during construction will be It will be very inconvenient! Just imagine, if buildthe method is really placed StatefulWidgetin , since the process of building the user interface requires dependencies State, buildthe method will have to add a Stateparameter, which is probably as follows:

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

In this case, Stateall the states of the class can only be declared as public states, so that Statethe states can be accessed outside the class! However, after the state is set to public, the state will no longer be private, which will cause the modification of the state to become uncontrollable. But if you build()put the method Statein , not only can the build process directly access the state, but it also doesn't need to expose private state, which is very convenient.

2. It is inconvenient to inherit StatefulWidget.

For example, in Flutter there is a base class for animated widgets AnimatedWidgetthat inherits from StatefulWidgetclasses. AnimatedWidgetAn abstract method is introduced in build(BuildContext context), and AnimatedWidgetanimations inherited from widgetmust implement this buildmethod. Now imagine that if StatefulWidgetthere is already a method in the class build, as mentioned above, buildthe method needs to receive an Stateobject at this time, which means that AnimatedWidgetits own Stateobject (denoted as _animatedWidgetState) must be provided to its subclasses, because subclasses need to be in In its buildmethod, the method of calling the parent class buildmay be as follows:

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

This is obviously unreasonable, because AnimatedWidgetthe state object is AnimatedWidgetan internal implementation detail and should not be exposed to the outside.

If you want to expose the state of the parent class to the subclass, you must have a transfer mechanism, and it is meaningless to do this set of transfer mechanism, because the transfer of state between the parent and child classes has nothing to do with the logic of the subclass itself.

To sum up, it can be found that, for StatefulWidget, buildputting the method Statein can bring a lot of flexibility to the development.

On the other hand, it is mainly performance considerations. State manages the state (which can be understood as Controller), and Widget is UI (ie View). Generating Widget (ie View) according to the state change can save memory without having to create the state object State every time. .

Get the State object in the widget tree

Since the specific logic of StatefulWidget is in its State, many times, we need to obtain the State object corresponding to StatefulWidget to call some methods. For example, the state class ScaffoldState corresponding to the Scaffold component defines opening SnackBar (prompt bar at the bottom of the routing page) Methods. We have two ways to get the State object of the parent StatefulWidget in the child widget tree.

1. Get State through Context

contextObjects have a findAncestorStateOfType()method that walks widgetup the tree from the current node to find a StatefulWidgetcorresponding Stateobject of the specified type. Here's SnackBaran example that implements an 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(),
    );
  }
}

Generally speaking, if the state of StatefulWidget is private (should not be exposed to the outside), then we should not directly obtain its State object in the code; if the state of StatefulWidget is expected to be exposed (usually there are some component operations method), we can directly obtain its State object. However, the method of obtaining the state of StatefulWidget through context.findAncestorStateOfType is universal. We cannot specify whether the state of StatefulWidget is private at the grammatical level, so there is a default agreement in Flutter development: if the state of StatefulWidget is to be exposed , you should provide an of static method in StatefulWidget to obtain its State object, and developers can obtain it directly through this method; if the State does not want to be exposed, then do not provide the of method. This convention can be seen everywhere in the Flutter SDK. Therefore, the Scaffold in the above example also provides an of method, which we can actually call directly:

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

For another example, if we want to display snackbar, we can call it through the following code:

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

2. Get State through GlobalKey

Flutter also has a general way to get the State object - GlobalKeyget it by! The procedure is divided into two steps:

  1. StatefulWidgetAdd to target GlobalKey.
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(
    key: _globalKey , //设置key
    ...  
)
  1. GlobalKeyGet Statethe object by
_globalKey.currentState.openDrawer()

GlobalKeyIt is a mechanism provided by Flutter to reference in the entire App element. If a widgetis set GlobalKey, then we can obtain the corresponding object by globalKey.currentWidgetobtaining the widgetobject , and if the current is , we can obtain the corresponding object by obtaining the object .globalKey.currentElementwidgetelementwidgetStatefulWidgetglobalKey.currentStatewidgetstate

Note: Using GlobalKeyis expensive, and should be avoided if there are other options available. In addition, the same must be uniqueGlobalKey in the entire tree and cannot be repeated.widget

State life cycle from the perspective of navigation routing

insert image description here

Analysis of StatefulWidget life cycle from the perspective of StatefulElement

insert image description here

It can be seen that the triggering of each life cycle callback is BuildOwnerindirectly StatefulElementdriven . StateThe following will analyze the trigger path and meaning of each callback from the perspective of source code.

// 代码清单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);
}

StatefulElementThe creation is completed in its own constructorState , and the assignment of two key fields is completed. After that, the process of this Elementnode will be carried out Build, and the specific logic is shown in the code list 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();
}

Because the above logic triggers initStatethe sum didChangeDependenciesmethod in the life cycle, it is initStategenerally used as Statethe time point when internal member variables start to initialize.

didChangeDependenciesAlthough it will be triggered unconditionally during the first build, it will still be triggered in the subsequent Build process, as shown in Listing 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();
}

didChangeDependenciesIt marks that the Elementdependent node has changed, and didChangeDependenciesthe method will be called again at this time, so this callback is more suitable for responding to some dependent updates. performnRebuildThe method that will eventually be triggered StatefulElementis buildshown in Listing 8-4.

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

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

The above logic is in line with StatefulWidgetthe intuition of use, that is, buildthe method is placed Statein the callback, and the callback is mainly used for UI update. It should be noted that if the current Elementnode is marked as dirty, buildthe method will definitely be called, so it is not advisable to perform time-consuming operations, so as not to affect the fluency of the UI.

For the case of non-first Build, the method that is usually triggered Element, the logic is shown in Listing 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中的逻辑
}

The above logic will trigger its method before passing the rebuildtriggered method. It is an appropriate time to remove some references and dependencies to , and update some resources that depend on properties.StatebuilddidUpdateWidgetoldWidgetWidget

ElementdetachThe node will call _deactivateRecursivelythe method in the stage, and its specific logic is shown in the code list 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
}

The above logic will trigger the Statemethod deactivateand cause the current node to enter inactivethe stage. The callback trigger indicates that the current Elementnode is removed Element Tree, but it is still available to be re-joined. This time is suitable for releasing some resources that are strongly related to the current state. For those resources that are not related to the state, considering that the node may still Elemententer Element Tree, it is not necessary It is suitable to be released at this time (it can be compared to the callback in Android Activity) onPause.

superThe method in the above logic deactivatemust be called, and its logic is shown in Listing 8-7.

// 代码清单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; // 更新状态
}

The above logic is mainly used to remove the corresponding dependencies.

After the Build process ends, Elementthe unmountmethod will be called, and its logic is shown in Listing 8-8.

// 代码清单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;
}

In the above logic, StatefulElementthe main method will be Statecalled dispose. ElementThe general logic in is responsible for destroying GlobalKeythe associated registration.

App life cycle from the perspective of Flutter

It should be pointed out that if you want to know the life cycle of the App, you need to obtain it WidgetsBindingObserverthrough didChangeAppLifecycleState. The life cycle state that can be obtained through this interface is in AppLifecycleStatethe class. Common states include the following:

  • resumed : The interface is visible and can respond to user input, the same as Android'sonResume
  • inactive : When the interface retreats to the background or pops up a dialog box, it is in an inactive state, that is, it loses focus and does not receive user input, but the drawFramecallback can still be executed; the same as AndroidonPause
  • paused : The application is suspended and currently invisible to the user, such as retreating to the background, losing focus, unable to respond to user input, and will not receive drawFramecallbacks (the engine will not call back PlatformDispatcher) ; same onBeginFrameas onDrawFrameAndroidonStop
  • detached : The app is still hosted on the flutter engine, but has been detached from any host views and callbacks will no longer be executed drawFrame; when the app is in this state, the engine runs without a view. This could be in the process of attaching the view when the engine is first initialized, or after Navigatorthe view is destroyed due to popping.

insert image description here

Example usage:

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

How Flutter compares to other cross-platform solutions

Today, there are already many cross-platform frameworks in the industry (specifically referring to the Android and iOS platforms). According to their principles, they are mainly divided into three categories:

  • H5 + native (Cordova, Ionic, WeChat applet)
  • JavaScript development + native rendering (React Native, Weex)
  • Self-drawn UI + native (Qt for mobile, Flutter)

insert image description here

The development language in the above table mainly refers to the development language of the application layer, and the development efficiency refers to the efficiency of the entire development cycle, including coding time, debugging time, and time for debugging and handling compatibility issues. Dynamic mainly refers to whether it supports dynamic code delivery and whether it supports hot update. It is worth noting that the Release package of Flutter is compiled using the Dart AOT mode by default, so it does not support dynamization, but Dart also has JIT or snapshot operation modes, and these modes support dynamization.

Solution 1: H5 + native

The main principle of this type of framework is to implement the content that needs to change dynamically in the App through HTML5 (H5 for short), and load it through the native web page loading control WebView (Android) or WKWebView (iOS). In this solution, the H5 part can be changed at any time without publishing a version, and the dynamic requirements can be met; at the same time, since the H5 code only needs to be developed once, it can run on both Android and iOS platforms at the same time, which can also reduce development time. Cost, that is, the more H5 part functions, the smaller the development cost. We call this H5 + native development mode hybrid development . Apps developed in hybrid mode are called hybrid applications or HTMLybrid Apps . If most of the functions of an application are implemented by H5, we call it Web App .

Typical representatives of the current hybrid development framework are: Cordova, Ionic. Most apps will have some functions developed by H5, at least so far, HTMLybrid App is still the most versatile and mature cross-end solution.

Here, we need to mention the small program. At present, the development technology stack of the small program application layer of various domestic companies is the Web technology stack, and the underlying rendering method is basically a combination of WebView and native.

Hybrid development technology point

1) Example: JavaScript calls the native API to get the phone model

Next, we take Android as an example to implement a native API for obtaining the phone model for calling JavaScript. In this example, JavaScriptthe process of calling native API will be shown. We choose the dsBridge of the open source library on Github as JsBridgethe communication.

  • First implement the API for obtaining the phone model in the native
class JSAPI {
    
    
 
 public Object getPhoneModel(Object msg) {
    
    
   return Build.MODEL;
 }
}    
  • WebViewRegister the native API through JsBridgethe
import wendu.dsbridge.DWebView
...
//DWebView继承自WebView,由dsBridge提供  
DWebView dwebView = (DWebView) findViewById(R.id.dwebview);
//注册原生API到JsBridge
dwebView.addJavascriptObject(new JsAPI(), null);
  • JavaScriptCall native API in
var dsBridge = require("dsbridge")
//直接调用原生API `getPhoneModel`
var model = dsBridge.call("getPhoneModel");
//打印机型
console.log(model);

The above example demonstrates JavaScriptthe process of calling native APIs. Similarly, generally speaking, the excellent ones JsBridgealso support native calls JavaScript, dsBridgeand they are also supported. If you are interested, you can go to the Github dsBridge project home page to view.

Now, let's look back, a hybrid application is nothing more than pre-implementing a series of APIs for JavaScriptcalling in the first step, so that JavaScriptyou have the ability to access system functions. Seeing this, I believe you can also implement a hybrid development framework yourself.

summary

The advantages of hybrid applications are: dynamic content can be developed with H5, and H5 is a Web technology stack. The Web technology stack has an open ecology and rich community resources, and the overall development efficiency is high. The disadvantage is that the performance experience is not good, and for complex user interfaces or animations, WebView can sometimes be overwhelmed.

Solution 2: JavaScript development + native rendering

React Reactive Programming

React is a responsive web framework. Let's first understand two important concepts: DOM tree and responsive programming.

DOM tree

The Document Object Model (DOM for short) is a standard programming interface recommended by the W3C organization for processing Extensible Markup Language, a platform- and language-independent way to access and modify the content and structure of a document. In other words, this is a standard interface for representing and manipulating an HTML or XML document. In simple terms, DOM is the document tree, which corresponds to the user interface control tree. In front-end development, it usually refers to the rendering tree corresponding to HTML, but in a broad sense, DOM can also refer to the control tree corresponding to the XML layout file in Android. The term DOM operation It refers to directly operating the rendering tree (or control tree). Therefore, it can be seen that the DOM tree and the control tree are equivalent concepts, but the former is often used in Web development, while the latter is often used in native development.

reactive programming

An important idea is put forward in React: the UI changes automatically when the state changes. The React framework itself performs the work of rebuilding the user interface in response to user state change events. This is a typical responsive programming paradigm. Let's summarize the responsive principles in React below:

  • Developers only need to pay attention to the state transfer (data). When the state changes, the React framework will automatically rebuild the UI according to the new state.
  • After receiving the user state change notification, the React framework will calculate the changed part of the tree through the Diff algorithm based on the current rendering tree and the latest state change, and then only update the changed part (DOM operation), thus avoiding the entire tree Tree refactoring to improve performance.

It is worth noting that in the second step, after the state changes, the React framework will not immediately calculate and render the changed part of the DOM tree. On the contrary, React will build an abstract layer on the basis of the DOM tree, that is, the virtual DOM tree . Any changes made to data and state will be automatically and efficiently synchronized to the virtual DOM, and finally synchronized to the real DOM in batches, instead of manipulating the DOM every time there is a change.

Why can't we directly manipulate the DOM tree every time it changes? This is because every DOM operation in the browser may cause the browser to redraw or reflow (re-typesetting the layout, determining the size and position of the DOM node):

  1. If the DOM only changes in appearance and style, such as a color change, it will cause the browser to redraw the interface.
  2. If the structure of the DOM tree changes, such as size, layout, hidden nodes, etc., the browser needs to reflow.

The redrawing and reflow of the browser are relatively expensive operations. If each change directly operates on the DOM, this will cause performance problems. However, batch operations will only trigger a DOM update, which will have higher performance.

React Native

React Native (RN for short) is a cross-platform mobile application development framework open sourced by Facebook in April 2015. It is a derivative of Facebook's earlier open source Web framework React on the native mobile application platform. It currently supports both iOS and Android platforms. RN uses the JSX language (extended JavaScript, mainly to write HTML tags in JavaScript) and CSS to develop mobile applications. Therefore, technicians who are familiar with web front-end development can enter the field of mobile application development with very little learning.

Since the principles of RN and React are similar, and Flutter is also inspired by React at the application layer, many ideas are also similar.

As mentioned above, React Native is a derivative of React on the native mobile application platform. What is the main difference between the two? Actually, the main difference is what objects are mapped by the virtual DOM. The virtual DOM in React will eventually be mapped to the browser DOM tree, while the virtual DOM in RN will be mapped to native controls through JavaScriptCore.

JavaScriptCore is a JavaScript interpreter, it has two main functions in React Native:

  1. Provides a runtime environment for JavaScript.

  2. It is a communication bridge between JavaScript and native applications, and its function is the same as that of JsBridge. In fact, in iOS, many implementations of JsBridge are based on JavaScriptCore.
    The process of mapping virtual DOM to native controls in RN is mainly divided into two steps:

  3. Layout message passing; pass virtual DOM layout information to native;

  4. Native rendering through the corresponding native controls according to the layout information;

So far, React Native has achieved cross-platform. Compared with hybrid applications, since React Native renders native controls, the performance will be better than that of H5 in hybrid applications. At the same time, React Native provides many web components corresponding to native components. In most cases, developers only need to use the Web technology stack. Can develop App. We can find that in this way, one code can be maintained, and it can be cross-platform.

Weex

Weex is a cross-platform mobile development framework released by Alibaba in 2016. Its ideas and principles are similar to React Native. The bottom layer is rendered natively. The difference is the application layer development syntax (ie DSL, Domain Specific Language): Weex supports Vue Grammar and Rax syntax, Rax's DSL (Domain Specific Language) syntax is created based on React JSX syntax, while RN's DSL is based on React and does not support Vue.

summary

The main advantages of JavaScript development + native rendering are as follows:

  1. Using the web development technology stack, the community is huge, quick to learn, and the development cost is relatively low.
  2. Native rendering, the performance is much improved compared to H5.
  3. It is dynamic and supports hot updates.

insufficient:

  1. Communication between JavaScript and native is required during rendering. In some scenarios such as dragging, frequent communication may cause lag.
  2. JavaScript is a scripting language, which needs to be interpreted and executed during execution (this execution method is usually called JIT, or Just In Time, which refers to generating machine code in real time during execution), execution efficiency and compiled language (the execution method of compiled language is AOT , that is, Ahead Of Time, which means that the source code has been preprocessed before the code is executed. This preprocessing usually compiles the source code into machine code or some kind of intermediate code) and there is still a gap.
  3. Because rendering relies on native controls, controls on different platforms need to be maintained separately, and when the system is updated, community controls may lag behind; in addition, its control system will also be limited by the native UI system, for example, in Android, gesture conflicts The disambiguation rules are fixed, which makes the gesture conflict problem very difficult when using nested controls written by different people. This will lead to high development and maintenance costs if custom native rendering components are required.

Solution 3: Self-drawn UI + native

The idea of ​​this technology is to draw the UI by implementing a rendering engine with a unified interface on different platforms, without relying on the native controls of the system, so the UI consistency of different platforms can be achieved.

Note that the self-drawing engine solves the cross-platform problem of UI. If other system capability calls are involved, native development is still involved. The advantages of this platform technology are as follows:

  1. High performance; since the self-drawing engine directly calls the system API to draw the UI, the performance is close to that of native controls.

  2. Flexible, easy to maintain component library, high fidelity and consistency of UI appearance; since UI rendering does not depend on native controls, there is no need to maintain a separate component library according to controls on different platforms, so the code is easy to maintain. Since the component library is the same set of code and the same rendering engine, the component display appearance can achieve high fidelity and high consistency on different platforms; in addition, because it does not rely on native controls, it will not be limited by the native layout system. This layout system will be very flexible.

insufficient:

  1. Insufficient dynamics; in order to ensure UI drawing performance, self-drawing UI systems generally use AOT mode to compile their release packages, so after the application is released, it cannot dynamically deliver code like Hybrid and RN frameworks that use JavaScript (JIT) as the development language .
  2. Low application development efficiency: Qt uses C++ as its development language, and programming efficiency will directly affect the efficiency of App development. As a static language, C++ is not as flexible as a dynamic language such as JavaScript in terms of UI development. In addition, C++ needs to be developed Or manually manage memory allocation, there is no garbage collection (GC) mechanism in JavaScript and Java.

You may have guessed that Flutter belongs to this type of cross-platform technology. Yes, Flutter implements a set of self-drawing engines and has its own UI layout system. At the same time, it has made great breakthroughs in development efficiency. However, the idea of ​​a self-drawing engine is not a new concept. Flutter is not the first to try to do this. Before it, there was a typical representative, that is, the famous Qt.

Qt Mobile

Qt is a cross-platform C++ graphical user interface application development framework developed by the Qt Company in 1991. In 2008, Qt Company technology was acquired by Nokia, and Qt became a programming language tool under Nokia. In 2012, Qt was acquired by Digia. In April 2014, the cross-platform integrated development environment Qt Creator 3.1.0 was officially released, fully supporting iOS, adding WinRT, Beautifier and other plug-ins, abandoning the GDB debugging support without Python interface, and integrating Clang-based C /C++ code module, and made adjustments to Android support, so far fully supports iOS, Android, WP, it provides application developers with all the functions required to build a graphical user interface.

However, although Qt has achieved great success on the PC side and is highly sought after by the community, it has not performed well on the mobile side. In recent years, the voice of Qt has rarely been heard, even though Qt is a cross-platform self-drawing platform for mobile development. The pioneer of the engine, but became a martyr. The reasons may be as follows:

  • The Qt mobile development community is too small, the learning materials are insufficient, and the ecology is not good.
  • The official promotion is not good and the support is not enough.
  • The mobile terminal started late, and the market has been occupied by other dynamic frameworks (Hybrid and RN).
  • In mobile development, C++ development has an inherent disadvantage compared with the Web development stack, and the direct result is that the efficiency of Qt development is too low.

Flutter

Flutter is a framework released by Google for creating cross-platform, high-performance mobile applications. Flutter, like Qt mobile, does not use native controls. On the contrary, they both implement a self-drawing engine and use their own layout and drawing system. Then, we will worry, whether the problems faced by Qt mobile are the same as Flutter, will Flutter step into the footsteps of Qt mobile and become another martyr? Since Flutter was born in 2017, after years of experience, the Flutter ecosystem has grown rapidly. There are many successful cases based on Flutter at home and abroad, and domestic Internet companies basically have dedicated Flutter teams. In short, Flutter has developed rapidly, has received widespread attention and recognition in the industry, and has been warmly welcomed by developers, becoming one of the most popular frameworks in mobile cross-end development.

Now, let's make a comparison with Qt mobile:

  1. Ecology: The Flutter ecosystem is developing rapidly, and the community is very active. Both the number of developers and third-party components are already very impressive.
  2. Technical support: Now Google is vigorously promoting Flutter. Many of the authors of Flutter are from the Chromium team, and they are very active on Github. From another perspective, from the birth of Flutter to the present, frequent version releases also show that Google has invested a lot of resources in Flutter, so there is no need to worry about official technical support.
  3. Development efficiency: One set of code, multi-terminal operation; and during the development process, Flutter's hot reload can help developers quickly test, build UI, add functions and fix errors faster. Millisecond-level hot reloads can be achieved on iOS and Android simulators or real devices without losing state. This is really great. Believe me, if you are a native developer, after experiencing the Flutter development flow, you probably don’t want to go back to doing native again. After all, few people don’t complain about the compilation speed of native development.

GC in Flutter

Flutter uses Dart as the development language and runtime mechanism. Dart has always retained the runtime mechanism, whether in debug mode (debug) or release mode (release), but there are big differences between the two construction methods.

  • In debug mode, Dart loads all pipelines (all accessories that need to be used) on the device: Dart runtime , JIT (the just-in-time) compiler/interpreter (JIT for Android and interpreter for iOS) , debugging and performance analysis services.
  • In release mode, AOT compilation will be used, JIT will be removed , and the Dart runtime will still be retained, because the Dart runtime is the main contributor to the Flutter App.

insert image description here

Dart's runtime includes a very important component: the garbage collector, whose main role is to allocate and release memory when an object is instantiated or becomes unreachable.

During the running of Flutter, there will be many Objects. They are created before (actually still) rendering StatelessWidget. StatefulWidgetWhen the state changes, they will be destroyed again. In fact, they have a very short lifespan. When we build a complex UI interface, there will be thousands of these Widget.

So, as Flutter developers, do we need to worry about whether the garbage collector can help us manage these well? (Will it bring a lot of performance problems?) As Flutter frequently creates and destroys these Widget(Objects) objects, should developers take steps to limit this behavior?

  • For new Flutter developers, if they know a Widget won't change over time, they create a Widgetreference and put them Statein so they don't get destroyed and rebuilt, which doesn't Not uncommon.

no need to do that

  • It is completely unnecessary to worry about Dart's GC, because its generational architecture is specially optimized to allow us to create and destroy objects frequently. In most cases, we just need to let Flutter's engine create and destroy all it likes Widget.

Dart GC

Dart's GC is generational (generational) and consists of two stages: Young Space Scavenger (scavenger recycles for the young bag) and Parallel Marking and Concurrent Sweeping (sweep collectors recycles for the old generation)

Scheduling

To minimize the impact of GC on application and UI performance, the garbage collector provides hooks to the Flutter engine that will alert it when the engine detects that the application is idle with no user interaction. This gives the garbage collector window a chance to run its collection phase without affecting performance.

The garbage collector can also run sliding compaction during these idle intervals, which minimizes memory overhead by reducing memory fragmentation.

insert image description here

Phase 1: Young Space Scavenger

This stage is mainly to clean up some short-lived objects, such as StatelessWidget. When it is blocked, its cleaning speed is much faster than the second-generation mark/sweep method. And combined with scheduling, completion can eliminate the pause phenomenon when the program is running.

Essentially, objects are allocated to a contiguous space in memory, and when objects are created, they are allocated to the next available space until the allocated memory is filled. Dart uses bump pointer allocation to quickly allocate new space, making the process very fast. (If you maintain free_list and redistribute like malloc, the efficiency is very low)

The new space in which new objects are allocated consists of two halves, called half-spaces. Only half of the space is used at any one time: one half is active and the other half is inactive. New objects are allocated to the active half of the area, and once the active half is filled, the active objects are copied from the active area to the inactive area, and dead objects are cleared. The inactive half then becomes active and the process repeats. (This is very similar to the survivor space of the new generation in the JVM's generational recycling strategy)

To determine which objects are alive or dead, the GC starts with root objects (such as stack variables) and examines what they refer to. Then move the referenced Object (survival) to the inactive state, and directly all surviving Objects are moved. Dead Objects have no references and are thus left around; live objects will be copied over them in future garbage collection events.

For more information on this, check out Cheney's Algorithm.

insert image description here

Phase 2: Parallel Marking and Concurrent Sweeping

When objects reach a certain age (not reclaimed by GC in the first phase), they will be promoted to a new memory space managed by the second-generation collector: mark-sweep.

This garbage collection technique has two phases: first traversing the object graph, and then marking objects that are still in use. In the second phase, the entire memory is scanned and any unmarked objects are reclaimed. Then clear all flags.

This GC technique blocks the marking phase; memory mutations do not occur, and the UI thread is also blocked. But since ephemeral objects are already processed in the Young Space Scavenger phase, this situation is very rare. But sometimes the Dart runtime needs to be paused to run this form of GC. Considering the capabilities of Flutter's planned collection, its impact should be minimized.

It is important to note that this form of GC will occur more frequently if an application does not follow the generational assumption (i.e. the assumption that most objects die young). Given Widgethow things work in Flutter, this is unlikely to happen, but something to know.

Isolate

It is worth noting that the mechanisms in Dart Isolatehave the concept of a private heap , independent of each other . Each Isolatehas its own separate thread to run, and Isolatethe GC of each does not affect Isolatethe performance of the other. Using Isolateis a great way to avoid blocking the UI and offload process-intensive activities. (Time-consuming operations can use Isolate)

You should understand here: Dart uses a powerful generational garbage collector to minimize the performance impact of GC in Flutter. So, you don't need to worry about Dart's garbage collector, and you can focus on your business with confidence.


reference:

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/130716845