This is my third blog about Flutter. Please check out my first two Flutter implementation posts

  • Implementation of the Flutter PIP effect
  • Implementation of the Flutter sidebar and city selection UI

preface

Flutter is Google’s new mobile UI framework to help developers develop high quality native apps on both Ios and Android. When I first came to know Flutter, I experienced three important versions of Flutter history.

  • On February 27, 2018, Google released the first Beta version of Flutter at Mobile World Congress 2018.
  • On June 21, 2018, the first Release Perview 1 was released at the Global Big Front-end Technology Conference.
  • The first Release 1.0 was released on December 5, 2018.

After that, more and more people began to pay attention to Flutter.

Prior to Flutter, there were many cross-platform UI frameworks, such as WebView-based Cordova and AppCan, React Native and Weex, which used HTML+JavaScript rendering as Native controls. Flutter opens up a whole new way of thinking, rewriting a cross-platform UI framework from the ground up, including UI controls, rendering logic and even a development language.

Flutter framework

It can be seen from the figure that the Flutter is mainly divided into two Framework layers and the Flutter Engine.

The Framework layer is written using Dart, with complete API of UI Framework, and pre-written UI of Android (MaterialDesign) and IOS (Cupertino) style, which greatly facilitates the development of mobile terminal.

The underlying Framework is the Flutter engine, which is responsible for drawing graphics (Skia), typesetting (libtxt) and providing Dart runtime. The engine is all implemented in C++.

Principle of Flutter performance

Compare with other cross-platform frameworks

Before looking at the Flutter framework, let’s take a look at other cross-platform framework designs

Dart’s high-performance support for the UI framework

We know that the Framework layer of Flutter is written in Dart language. What are the advantages of Dart language? The following is divided into several points to illustrate

Dart memory allocation mechanism

DartVM’s memory allocation strategy was very simple. Objects were created by simply moving Pointers over the existing heap, and memory growth was always linear, eliminating the need to find available memory segments

Dart’s thread-like concept is called Isolate. Each Isolate cannot share memory, so this allocation strategy enables Dart to quickly allocate without locking.

Dart garbage collection mechanism

Dart also adopts the multi-generation algorithm for garbage collection. The new generation adopts the “half-space” algorithm for memory collection. When garbage collection is triggered, Dart copies the “active” objects in the current half-space to the spare space, and then releases all the memory in the current space as shown in the figure.

Dart only operates on a small number of “live” objects and ignores a large number of “dead” objects that are not referenced. This multi-generation, lock-free garbage collector is optimized for the creation and destruction of a large number of Widgets that are common in the UI framework, ideal for scenarios where a large number of Widgets are rebuilt in the Flutter framework.

Dart programming volume optimization, and JIT and AOT compilation support

Tree Shaking code volume, only code that needs to be called at runtime is preserved at compile time (no implicit references such as reflection are allowed), so a large library of Widgets does not make publishing too big.

Dart supports two compilation modes:

  • JIT compilation Just In Time Compiler – Just-in-time compilation
  • AOT compilation Ahead Of Time

In debug mode, JIT compilation is used to generate SRCIpt/Bytecode for interpretation and execution. HotReload (hot overload) can be supported to modify the code and keep the effect visible on the device. AOT compilation under Release generates Machine Code, which runs efficiently.

Dart single-threaded asynchronous messaging mechanism

Description of client interaction

For mobile terminals, most of the interactions are in the wait state, waiting for network requests, waiting for user input, etc. So imagine that making a network request can only be done in one thread? Of course network requests must be asynchronous (note that asynchronous and multithreading are not a concept). This is possible because Flutter uses Dart, a single-threaded mechanism, to avoid the performance loss caused by multithreaded context switching. (For high-time activities, multithreading is also supported and enabled using Isolate, but note that the memory cannot be shared because of multithreading.)

Dart Asynchronous message mechanism

When a Dart method starts executing, it continues executing until the method exit point is reached. In other words, the Dart method is not interrupted by other Dart code. A sign when a Dart application starts is that its Main ISOLATE executes the Main method. When the main method exits, the threads of the Main ISOLATE process the messages in the message queue one by one.

With message queues, and then with loops to read messages in message queues, you have the ability for a single thread to execute asynchronous messages. Generic messages use the Future in DART: Async to support asynchronous messages.

Flutter Engine High performance

When discussing the Flutter Engin layer, we will talk about how screen drawing works.

Principles of Screen drawing

We all know that monitors refresh at a fixed rate, like 60Hz on the iPhone or 120Hz on the iPad Pro. When one frame of an image is drawn and ready to draw the next, the display emits a vertical sync signal, or VSync, so a 60Hz screen emits 60 such signals a second.

Generally speaking, in a computer system, CPU, GPU and display cooperate in a specific way: CPU submits the calculated display content to GPU, and GPU puts it into the frame buffer after rendering, and then the video controller takes the frame data from the frame buffer according to VSync signal and transmits it to the display.

As a professional Android developer, I have seen the drawing mechanism of Android and found that it is similar to Flutter by the mechanism between SurfaceFlinger and HAL layers. How about IOS? I guess the drawing mechanism of the screen is the same, but different platforms have different implementations.

The rendering mechanism of Flutter Engine

Flutter only cares about providing view data to the GPU. The GPU’s VSync signal is synchronized to the UI thread. The UI thread uses Dart to construct an abstract view structure. This data is fed to the GPU via OpenGL or Vulkan.

So Flutter doesn’t care about the display, the video controller, or the GPU. It only cares about the VSync signal emitted by the GPU, calculates and synthesizes view data between the two VSync signals as quickly as possible, and feeds the data to the GPU.

The drawing mechanism of the Flutter Framework layer

The UI tree principle

The rendering process of Flutter interface is divided into three stages: layout, drawing and composition.

In the layout phase, there are three important objects. RenderObject, Element, Widget.

  • Widgets are frequently used by developers and are read-only by default.

  • Element is the middle layer that a Flutter uses to separate the control tree from the actual rendered object. The control describes the corresponding Element property. The control may reuse the same Element after reconstruction.

  • The RenderObject is responsible for providing configuration information and creating concrete elements.

Element holds the RenderObject that is really responsible for layout, drawing, and hit tests.

So, if the control’s properties change (because the control’s properties are read only, the change means that a new control tree is created), but the type of each node in the tree does not change, The Element and Render trees can fully reuse the original object (since both Element and Render Object properties are mutable).

The layout principle

Traditional layouts such as Android may require multiple measures to calculate the width and height. The Flutter uses constraints for a single measurement layout. The entire layout process only needs to go through the depth once, greatly improving the efficiency.

Each object in the render object tree accepts the Constraints parameter of the parent object during the layout process, determining its size. The parent object then completes the layout process by determining the position of the child objects according to its own logic.

A child object does not store its position in the container, so it does not need to be rearranged or redrawn when its position changes. The bitwise information of a child object is stored in its own parentData field, but that field is maintained by its parent object and does not care about its contents.

Because of its simple layout logic, Flutter can set a Relayout boundary at certain nodes. This means that when any object inside the boundary is rearranged, it does not affect objects outside the boundary and vice versa.

The resources

The references are as follows

  • Flutter website
  • Meituan technical team
  • Idle Fish technology public number Flutter
  • The theory of Flutter is deeply analyzed
  • Introduction to Flutter Principle