This is the 15th day of my participation in Gwen Challenge

Today, we will do an in-depth analysis of the Flutter framework.

Flutter turneth the edge of

The cross-platform high-performance rendering engine has gradually become a hot topic in the field of mobile and large front-end. As the star framework of Flutter, it has developed rapidly in recent years and has great potential to become the next generation of cross-terminal solutions. Since May 2017, Google has released the Alpha version of Flutter; The 1.0 release of Flutter Live at the end of 2018; Version 1.5 was released in July 2019, and as of today (February 2020) the v1.14.6 Beta has been released. Prior to Flutter, there were many cross-platform UI frameworks such as Cordova, ReactNative, Weex, Uni-App, Hippy, etc. Common terminal platforms that need to be handled include Android, ios, Web, Iot, etc. However, due to the wave of big front-end, Development efficiency and user experience are very important for enterprises and developers. The traditional approach is to develop different terminal projects by different teams. If we continue to expand to other platforms, the cost and time we need to pay will increase exponentially. This is why the rise of cross-end frameworks, including Flutter, essentially helps developers reuse business code and reduce the need to adapt to multiple platforms, thereby reducing development costs and improving development efficiency.

The existing cross-end schemes can be divided into three categories: Web container, pan-Web container and self-drawing engine framework.

The cross-platform based on Web container, that is, browser-based, is getting better and better, and the natural pipeline is getting shorter and shorter, which is complementary with some technical means of Native to achieve performance. Egret, Cocos, and Laya are cross-platform game engines written in Typescript that easily run HTML5 games in a variety of browsers on iOS and Android platforms and provide a near-uniform user experience across platforms. Egret also provides an efficient JS-C Binding compilation mechanism for games compiled in native format, but most HTML game engines fall into the web container category as well. Web container frameworks also have an obvious and fatal (in the case of high experience & performance requirements) disadvantage, which is the poor rendering efficiency and JavaScript execution performance of WebViews. Add in the customization of Android across different versions and device manufacturers, and it’s hard to guarantee a consistent experience across all devices.

Pan-web container frameworks such as ReactNative and Weex, that is, the top layer adopts front-end friendly UI and the bottom layer adopts native rendering form. Although the SAME HTML+ JS-like UI construction logic is used, the corresponding custom native controls are eventually generated. It is also a good solution to make full use of the higher drawing efficiency of native controls compared with WebView, and H5 and Native complement each other to achieve better user experience. The drawbacks are also obvious. Developers may have to deal with platform differences as the system version changes and apis change, and some features can only be implemented on some platforms, thus reducing the cross-platform features of the framework.

Self-drawing engine framework here specifically refers to the Flutter framework, which undertakes cross-terminal tasks and rendering methods from the bottom. At present, Flutter is likely to become the next mainstream cross-platform framework in terms of technology implementation, solution maturity and product performance.

One of the differences between Flutter and other cross-end frameworks is its own rendering engine. The Flutter rendering engine relies on the cross-platform Skia graphics library. The Skia engine processes abstract view structure data built using Dart into GPU data, which is then fed to the GPU for rendering by OpenGL. The loop of rendering is now complete, so that the experience of an app can be as consistent as possible across different platforms and devices.

Dart language, which supports JIT and AOT at the same time, is used as the development language. Dart itself provides three modes of operation. Dart is compiled into JavaScript code using Dart 2JS and runs in a regular browser. Use DartVM to run Dart code directly from the command line; AOT compiled to machine code, such as the Flutter App framework. In addition, Dart avoids preemptive scheduling and shared memory, allows for object allocation and garbage collection without locking, and delivers superior performance in terms of development efficiency, code performance and user experience. Therefore, Flutter stands out among various cross-platform mobile development solutions. Meanwhile, at Last year’s Google IO 2019, the much-anticipated Fuchsia system was not announced, but it was announced that Flutter now supports Web applications as well as Android and iOS applications. At I/O, Google has released the first technical preview of Flutter for the Web, announcing that Flutter will support Google Smart Display platforms, including Google Home Hub. And take the first steps to support desktop-level applications with Chrome OS.

Many JS developers will wonder why the Google Flutter team chose Dart over JS. The reason given by Google is simple and straightforward: The Dart language development team is next door, and some of the new language features needed for Flutter can be quickly implemented syntactic; If you choose JavaScript, you have to go through lengthy decisions by various committees (TC39, etc.) and browser providers.

Principle of Flutter drawing

In computer system, the display of image needs the cooperation of CPU, GPU and display: CPU is responsible for image data calculation, GPU is responsible for image data rendering, and the display is responsible for the final image display.

The CPU gives the calculated content to be displayed to the GPU, and the GPU completes the rendering and puts it into the frame buffer. Then the video controller reads the frame data from the frame buffer and submits it to the display to complete the image display at the speed of 60 times per second according to the VSync signal.

The operating system follows this mechanism for rendering images, and Flutter, as a cross-platform development framework, adopts this underlying scheme. Here is a more detailed diagram explaining how Flutter is drawn. As you can see, Flutter focuses on how to compute and synthesize view data between VSync signals of two hardware clocks as quickly as possible, and then deliver it to the GPU for rendering via Skia: Dart is used by the UI thread to construct view structure data, which is then layered in the GPU thread and then delivered to the Skia engine for processing into GPU data, which is ultimately provided to the GPU rendering through OpenGL.

Skia principle

Skia is a 2D graphics rendering engine developed in C++. Acquired by Google in 2005 and widely used in Android and other core products, Skia is now the official image rendering engine for Android, Therefore, the Flutter Android SDK does not need to have built-in Skia engine to gain natural Skia support; For iOS, Skia is embedded into the iOS SDK of Flutter as the Flutter iOS rendering engine, since it is cross-platform. Instead of iOS closed source Core Graphics/Core Animation/Core Text, this is why Flutter iOS SDK packages are larger than Android.

Once the underlying rendering capabilities are unified, the upper level development interface and functional experience are unified, and developers no longer need to worry about platform-specific rendering features. That is, Skia ensures that the same set of code calls render exactly the same on Android and iOS.

Flutter architecture

The underlying Framework is the Flutter engine, which is responsible for drawing graphics (Skia), typesetting (libtxt) and providing the Dart runtime. The engine is all implemented in C++. The Framework layer allows us to invoke the power of the engine in Dart language. The Flutter architecture adopts a layered design, which consists of three layers from bottom to top: Embedder, Engine, and Framework.

Embedder is an operating system Embedder layer that provides the ability to embed platform-specific features such as render Surface Settings, thread Settings, and platform plug-ins. From this we can see that the Flutter platform does not have many characteristics, which makes the cost of maintaining cross-end consistency from the framework level relatively low. The Engine layer contains Skia, Dart and Text, which implements the rendering Engine, Text typesetting, event processing and Dart runtime of Flutter. Skia and Text provide the ability to call the underlying rendering and typesetting for the upper interface, while Dart provides the ability to call Dart and the rendering engine at runtime for Flutter. The Engine layer combines them to render views from the data they generate.

The Framework layer is a UI SDK that uses Dart and includes animation, graphics, and gesture recognition. To provide a more intuitive and convenient interface for drawing fixed graphics such as controls, Flutter also builds on these basic capabilities by encapsulating a library of UI components in the Material and Cupertino visual design styles that developers can use directly.

Flutter operation flow

Widgets on a page are organized in a tree, a control tree. Flutter makes up the tree of rendered objects by creating different types of render objects from each control in the control tree. The rendering process of the Flutter interface is divided into three stages: layout, drawing, and composition. Layout and rendering are done in the Flutter framework, and composition is done by the engine.

Flutter uses a depth-first mechanism to traverse the tree of rendered objects and determine their position and size on the screen. During the layout process, each render object in the render object tree will receive the layout constraint parameters of the parent object, determine its own size, and then the parent object will determine the location of each child object according to the control logic, and finally complete the layout process. The only thing to note here is that both layout and drawing are traversal relationships between parents and children: the layout of the parent Widget depends on the layout result of the child Widget; The reverse is true for drawing (the child Widget needs to be overlaid on top of the parent Widget). Layout is subsequent traversal, and drawing is pre-traversal, both of which are depth-first traversal.

Flutter life cycle

As you can see, the life cycle of a Flutter State can be divided into three stages: create (insert the view tree), update (exist in the view tree), and destroy (remove from the view tree). Next, let’s take a look at the specific process of each stage.

Step 1 Create

  • State is initialized in sequence: constructor -> initState -> didChangeDependencies -> Build, followed by page rendering. Constructor is the starting point of the State life cycle, will Flutter by calling StatefulWidget. CreateState () to create a State. We can use a constructor to receive the initialization UI configuration data passed by the parent Widget. This configuration data determines the initial rendering of the Widget.

  • InitState, called when the State object is inserted into the view tree. This function will only be called once in the lifetime of State, so we can do some initialization work here, such as setting default values for State variables.

  • DidChangeDependencies handle State object dependencies that are called by Flutter after initState() is called.

  • Build, which builds the view. After the above steps, the Framework decides that State is ready and calls Build. In this function, we need to create a Widget and return it based on the initial configuration data passed in by the parent Widget and the current State of State.

Step 2 Update

Status updates of widgets are triggered by several methods: setState, didchangeDependencies, and didUpdateWidget.

  • SetState: One of the most familiar methods. When the state data changes, we always call this method to tell Flutter: “My data has changed, please rebuild the UI with the updated data!”

  • DidChangeDependencies: State Calls back to this method when the dependencies of the object change, subsequently triggering a component build. In what cases does the dependency of a State object change? Typically, when the system language Locale or application topic changes, the system tells State to execute the didChangeDependencies callback method.

  • DidUpdateWidget: This function is called when the configuration of a Widget changes, for example, when the parent Widget triggers a rebuild (that is, when the state of the parent Widget changes), or when a hot overload occurs. Once these three methods are called, Flutter then destroys the old Widget and calls the Build method to rebuild the Widget.

Step 3 Destruction

When a component is removed or a page is destroyed, the system calls two methods, Deactivate and Dispose, to remove or dispose the component.

The Flutter ecosphere and its commonly used framework

The popularity of a technology and its framework is best reflected by its active ecosystem. Here are some library tools commonly used in Flutter development.

reference

  • 1, Flutter theory and practice (tech.meituan.com/2018/08/09/…). Less jie

  • 2, Overview of Flutter framework technology (Flutter. Dev /docs/resour…

  • 3, Flutter Chinese website (pub.dartlang.org/flutter/)

  • Pub. Dev/Flutter /pac…