The Flutter is a new cross-platform, open source UI framework developed by Google for iOS, Android, and the default development kit for Fuchsia. Since the first release in May 2017, Flutter has now been released in nearly 60 versions, and the first “Ready for Production Apps” Beta 3 was released in May 2018, The first Release Preview version was released on June 20.

First Flutter

The goal of Flutter is to have the same code running on Both Android and iOS with the same performance as a native App. Flutter even provides two sets of controls for Both Android and iOS (scrolling effects, fonts, control ICONS, etc.) in order to make the App look more like a native App in detail.

Before Flutter, there were many cross-platform UI frameworks such as WebView-based Cordova and AppCan, and React Native and Weex, which were rendered as Native controls using HTML+JavaScript.

Based on the framework of the WebView advantage obviously, they can almost completely inherits all the achievements of modern Web development (much more control library, framework, fully meet the various needs of page dynamic, automated testing tools, etc.), of course, including Web developers, don’t need too much study and migration costs can develop an App. The WebView framework also has a fatal (in the case of high performance and experience requirements) disadvantage, which is the WebView rendering efficiency and JavaScript execution performance is poor. Add to that the customization of various Android versions and device vendors, and it’s hard to guarantee a consistent experience on all the devices you’re on.

In order to solve the problem of poor WebView performance, a class of frameworks represented by React Native return the final rendering work to the system. Although the SAME HTML+ JS-like UI construction logic is used, the corresponding custom Native controls will be generated eventually. To take advantage of the high rendering efficiency of native controls over WebViews. At the same time, this strategy also binds the framework itself and App developers to the control system of the system. Not only does the framework itself need to deal with a lot of platformer related logic, but as the system version changes and API changes, developers may also need to deal with the differences between different platforms, and even some features can only be implemented on some platforms. This compromises the cross-platform nature of the framework.

Flutter opens up a whole new way of thinking about rewriting a cross-platform UI framework from start to finish, including UI controls, rendering logic, and even development language. The rendering engine relies on the cross-platform Skia graphics library. The only interface that relies on the system is the graphics rendering interface, which maximizes the consistency of the experience across different platforms and devices. The logic processing uses the AOT-enabled Dart language, which is much more efficient than JavaScript.

The Flutter supports Windows, Linux, and macOS operating systems as a development environment, and provides full support on both Android Studio and VS Code ides. The Dart language that Flutter uses supports both AOT and JIT operations. There is also a popular development tool called “Hot Reload” in JIT mode. After editing the Dart code in Android Studio, Just hit the Save or ‘Hot Reload’ button and you can instantly update to a running device. You don’t need to recompile the App, or even restart the App, and you’ll see the updated look immediately.

In Flutter, all functions can be achieved by combining multiple widgets, including alignment, row arrangement, column arrangement, grid arrangement, and even event handling. There are two main types of Flutter controls, StatelessWidgets and StatefulWidgets. Statelesswidgets are used to display static text or images, if the control needs to be changed based on external data or user actions. You need to use the StatefulWidget. The concept of State is also derived from Facebook’s popular Web framework React. React style framework uses control tree and their State to build the interface. When the State of a certain control changes, the frame is responsible for comparing the difference between the State before and after and updating the rendering results with the minimum cost.

Hot Reload

Modify the string “Hello, World” in the Dart code file, add an exclamation point, and click the Save or hot refresh button to instantly update the interface in just a few hundred milliseconds:

The Flutter achieves this magical hot-reload effect by injecting new code into the running DartVM. After DartVM has updated the class structure in the program, Flutter immediately reconstructs the entire control tree to update the interface. There are some limitations to hot refresh, however, and not all code changes can be updated with hot refresh:

  1. Compilation error. If the modified Dart code does not compile, the Flutter will report an error on the console and the corresponding code needs to be modified.
  2. Control type fromStatelessWidgettoStatefulWidgetBecause Flutter preserves the program’s original state when performing a hot refresh, MyWidget is not a subtype of StatelessWidget, but the widgets that were stateful changed from stageless to stateful From stateful to stateless complains “type ‘myWidget’ is not a subtype of type ‘StatefulWidget’ of ‘newWidget'”.
  3. Global variables and static member variables, which are not updated during hot flushes.
  4. Modified the root control node created in the main function. The Flutter will only recreate the control tree from the original root node after a hot refresh. The root node will not be modified.
  5. Hot refresh fails when a class is converted from an ordinary type to an enumerated type, or when the type’s generic parameter list changes.

When a Hot refresh is not possible, a Hot Restart is performed to fully update all code, again without restarting the App. The difference is that the Restart packages all the Dart code and synchronizes it to the device, and all states are reset.

Flutter plug-in

The Dart language that Flutter uses does not directly call the Java interface provided by the Android system, so a plug-in is required to do this. Flutter officially offers a rich native interface package:

  • android_alarm_managerTo access the Android systemAlertManager.
  • Android_intent constructs Android Intent objects.
  • Battery: Obtains and monitors system power changes.
  • Connectivity, access and monitor system network connection status.
  • Device Info: obtains the device model information.
  • Image_picker, selects or takes a photo from the device.
  • Package_info: obtain the version of the App installation package.
  • Path_provider: obtains common file paths.
  • Quick_actions, Add Shortcuts to App ICONS, eponymous Concept for iOS and App Shortcuts for Android
  • Access device accelerometer and gyroscope sensors.
  • Shared_preferences, App KV storage.
  • Url_launcher, the URL to launch a phone call, send a text message, and browse the Web.
  • Video_player, a control that plays video files or network streams.

In Flutter, dependencies are managed by the Pub repository. Project dependencies are declared in a pubspec.yaml file (similar to the VERSION of NPM declaring the Pub Versioning Philosophy). For plugins not published in the Pub repository, use the Git repository address or file path:

dependencies: 
  url_launcher: "> = 0.1.2 < 0.2.0"
  collection: "^ 0.1.2." "
  plugin1: 
    git: 
      url: "git://github.com/flutter/plugin1.git"plugin2: path: .. /plugin2/Copy the code

Using shared_Preferences as an example, add code to pubSpec:

dependencies:
  flutter:
    sdk: flutter

  shared_preferences: "^ 0.4.1"
Copy the code

^1.2.3 is equivalent to >=1.2.3 <2.0.0 and ^0.1.2 is equivalent to >=0.1.2 <0.2.0. After adding dependencies, click the “Packages Get” button to download the plug-in to the local. To use the interface provided by the plug-in, add an import statement to your code:

import 'package:shared_preferences/shared_preferences.Dart';

class _MyAppState extends State<MyAppCounter> {
  int _count = 0;
  static const String COUNTER_KEY = 'counter';

  _MyAppState() {
    init();
  }

  init() async {
    var pref = await SharedPreferences.getInstance();
    _count = pref.getInt(COUNTER_KEY) ?? 0;
    setState(() {});
  }

  increaseCounter() async {
    SharedPreferences pref = awaitSharedPreferences.getInstance(); pref.setInt(COUNTER_KEY, ++_count); setState(() {}); }...Copy the code

Dart

Dart is a strongly typed, cross-platform client development language. It is optimized for the client, highly productive, fast and efficient, portable (ARM/x86 compatible), easy to learn OO programming style, and native support for responsive programming (Stream & Future). Dart is primarily developed and maintained by Google, which launched the project in October 2011 and released the first version of 2.0-DEV in September 2017.

Dart itself provides three modes of operation:

  1. It is compiled into JavaScript code using Dart2js and run in a regular browser (Dart Web).
  2. Run the Dart code (DartVM) directly on the command line using DartVM.
  3. AOT is compiled into machine code, such as the Flutter App framework (Flutter).

Flutter chose Dart as its development language after a selection of more than 20 languages:

  1. A robust type system that supports both static type checking and runtime type checking.
  2. Code volume optimization (Tree Shaking) : only code that needs to be called at run time is retained at compile time (no implicit references like reflection are allowed), so a large Widgets library does not lead to large publishing volumes.
  3. Rich underlying libraries. Dart provides a large number of libraries itself.
  4. Multigeneration unlocked garbage collector optimized for the creation and destruction of the large number of Widgets objects that are common in UI frameworks.
  5. Across platforms, iOS and Android share the same code.
  6. JIT & AOT operation mode supports rapid iteration during development and maximizes hardware performance after release.

There are some important basic concepts to understand in Dart:

  • The values of all variables are objects, that is, instances of classes. Even numbers, functions andnullThey’re all objects, they’re all inherited fromObjectClass.
  • Although Dart is a strongly typed language, explicit variable type declarations are optional, and Dart supports type inference. If you don’t want to use type inference, use the dynamic type.
  • Dart supports generics.List<int>Represents a list containing int,List<dynamic>Contains any type of list.
  • Dart supports top-level and class member functions, as well as nested and local functions.
  • Dart supports top-level variables and class member variables.
  • Dart does not have public, protected, or private keywords. Instead, it uses variables or functions beginning with an underscore “_” to indicate that they are visible only within the library. Reference libraries and visibility.

DartVM’s memory allocation strategy is very simple. Objects are created by simply moving a pointer across the existing heap, and memory growth is always linear, eliminating the need to find available memory segments:

The similarthread concept in Dart is called an Isolate. Each Isolate does not share memory, so this strategy allows the Dart to allocate memory quickly without locks.

Dart also uses the multi-generation algorithm. The new generation uses the “half-space” algorithm to reclaim memory. When garbage collection is triggered, Dart will copy the “active” objects in the current half-space to the spare space, and then release all the memory in the current space:

Dart operates on a small number of “active” objects and ignores a large number of unreferenced “dead” objects. This algorithm is also well suited to scenarios where widgets are recreated in the Flutter framework.

Flutter Framework

The framework of Flutter is implemented entirely in the Dart language and has a clear hierarchical architecture. The layered architecture allows us to directly call and even modify each layer of implementation (since the entire framework is “user space” code) in addition to the easy development capabilities that Flutter provides (a predefined set of high quality Material controls), which gives us the maximum customization capability. The underlying Framework is the Flutter engine, which is responsible for drawing graphics (Skia), typography (libtxt), and providing the Dart runtime. The engine is implemented in C++. The Framework layer allows us to invoke the power of the engine using the Dart language.

Layered architecture

The lowest level of the Framework, called Foundation, defines mostly the very basic utility classes and methods that are used by all the other layers. The Painting library encapsulates the Painting interface provided by the Flutter Engine to provide a more intuitive and convenient interface for drawing fixed style graphics such as controls, such as drawing zooming bitmaps, drawing text, interpolating shadows, and drawing borders around boxes. Animation is an animation-related class that provides functionality similar to Android’s ValueAnimator, with a rich set of built-in interpolators. Gesture provides Gesture recognition functionality, including a touch event class definition and a variety of built-in Gesture recognizers. The GestureBinding class is the abstract service class that handles gestures in Flutter, inherited from the BindingBase class. The Binding classes in Flutter act like the SystemService family (ActivityManager, PackageManager) in Android. Each Binding class provides a service singleton. The Binding at the top of the App will contain all the related Bingding abstract classes. A WidgetsFlutterBinding is required if you are developing with any of the controls provided with Flutter, and a RenderingFlutterBinding is required if you call the Render layer directly without using any of the controls provided with Flutter.

The Flutter itself is supported on both Android and iOS. In addition to being “native” in terms of performance and development languages, it also provides a Material & Cupertino control implementation in two languages designed to help the App better provide a native user experience on different platforms.

Rendering Library (Rendering)

The control tree of the Flutter is converted into the corresponding RenderObject tree for layout and drawing operations. In general, we only need to consider the details of the rendered object tree when debugging the layout or when we need to use custom controls to achieve special effects. The main function classes provided by the rendering library are:

abstract class RendererBinding extends BindingBase with ServicesBinding.SchedulerBinding.HitTestable {... }abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
abstract class RenderBox extends RenderObject {... }class RenderParagraph extends RenderBox {... }class RenderImage extends RenderBox {... }class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox.FlexParentData>,
                                        RenderBoxContainerDefaultsMixin<RenderBox.FlexParentData>,
                                        DebugOverflowIndicatorMixin {... }Copy the code

RendererBinding is the glue layer of the render tree and the Flutter engine that manages the monitoring of changes in frame redraw, window size, and render related parameters. RenderObject Is the base class for all nodes in the render tree, defining interfaces related to layout, drawing, and composition. RenderBox and its three commonly used subclasses, RenderParagraph, RenderImage, and RenderFlex, are implementation classes for specific layout and rendering logic.

The rendering process on the Flutter interface is divided into three stages: layout, drawing, and composition. Layout and painting are done in the Flutter framework, and composition is left to the engine.

Each control in the control tree creates a different type of RenderObject object by implementing RenderObjectWidget#createRenderObject(BuildContext context) → RenderObject. Make up the render object tree. Because Flutter greatly simplifies the layout logic, you only need to do one deep traversal during the layout:

Each object in the render object tree takes the parent object’s Constraints parameter and determines its own size during the layout process. The parent object can then logically determine the position of its children and complete the layout process. A child object does not store its position in the container, so it does not need to be relaid or drawn when its position changes. The location information of a child object is stored in its own parentData field, but this field is maintained by its parent object, which does not care about its contents. Also because of this simple layout logic, the Flutter can set Relayout boundaries on certain nodes, i.e. when any object within the boundary is rearranged, it does not affect objects outside the boundary, and vice versa:

Once the layout is complete and each node in the rendered object tree has a specific size and position, Flutter will draw all objects on different layers:

Because of the depth of rendering node is also traverse, you can see the second node in drawing its background and foreground had to draw on different layers, because the fourth switch node layer (because “4” node is a need to monopolize a layer of content, such as video), and sixth node is also drawn to the red layer. This causes the second node’s foreground (” 5 “) part to be redrawn, but the sixth node, which is logically unrelated to it but on the same layer, must also be redrawn. To avoid this, Flutter provides another concept of “redrawing boundaries” :

The Flutter forces a new layer switch when entering and exiting the redrawn boundary so that interactions between inside and outside the boundary can be avoided. A typical application scenario is ScrollView. When scrolling content is redrawn, other content is usually not redrawn. Although redraw boundaries can be set manually at any node, it is generally not necessary for us to do so. The controls provided with the Flutter are set automatically where they need to be set by default.

Control library (Widgets)

The control library of Flutter provides a very rich set of controls, including basic text, pictures, containers, input boxes, animations, and more. In Flutter, “Everything is a control”. By combining and nesting different types of controls, you can build interfaces of any functionality and complexity. The main classes it contains are:

class WidgetsFlutterBinding extends BindingBase with GestureBinding.ServicesBinding.SchedulerBinding.PaintingBinding.RendererBinding.WidgetsBinding {... }abstract class Widget extends DiagnosticableTree {... }abstract class StatelessWidget extends Widget {... }abstract class StatefulWidget extends Widget {... }abstract class RenderObjectWidget extends Widget {... }abstract class Element extends DiagnosticableTree implements BuildContext {... }class StatelessElement extends ComponentElement {... }class StatefulElement extends ComponentElement {... }abstract class RenderObjectElement extends Element {... }...Copy the code

Applications developed based on the Flutter control system use the WidgetsFlutterBinding, which is the control framework of the Flutter and the glue layer of the Flutter engine. Widgets are the base class for all controls, and all of their properties are read-only. All RenderObjectWidget implementation classes are responsible for providing configuration information and creating the concrete RenderObjectElement. The Element is the middle layer that Flutter uses to separate the control tree from the actual rendered object. The control describes the corresponding Element attributes, and the control may reuse the same Element after reconstruction. RenderObjectElement holds RenderObject objects that are really responsible for layout, drawing, and hit test.

StatelessWidget and StatefulWidget do not directly affect the creation of RenderObject, they are only responsible for creating the corresponding RenderObjectWidget. StatelessElement and StatefulElement have similar functionality.

The relationship between them is as follows:

If the properties of the control have changed (since the properties of the control are read-only, a change means that a new control tree has been created) but the type of each node in the tree has not changed, Element and Render trees can fully reuse the original object (because the attributes of both element and Render objects are mutable) :

However, if the type of a node in the control tree changes, the corresponding nodes in the Element and Render trees need to be recreated as well:

Delivery of the whole category page practice

After researching the various features and implementation principles of the Flutter, the takeaway plan is available on the full category page of the Flutter edition in grayscale. There is no official support for integrating the Flutter page as part of the App, so we first need to understand how the Flutter is compiled, packaged, and run.

Building the Flutter App

The simplest Flutter project consists of at least two files:

Running the Flutter program requires a host project for the corresponding platform. On Android, the Flutter is generated by automatically creating a Gradle project. Executing the Flutter create in the project directory will create the ios and Android directories. Build host projects for each platform. The Android directory is as follows:

There is only one App Module in this Gradle project. The build product is the host APK. When running the Flutter locally, it runs in Debug mode. Running the Flutter run in the project directory installs the Flutter onto the device and runs automatically. In Debug mode, the Flutter executes the Dart code in JIT mode, and all Dart code is bundled into the ASSETS directory of the APK file. Read and executed by DartVM provided in libfluence.so:

Kernel_blob. bin is the underlying interface of the Flutter engine and the basic functionality of the Dart language.

third_party/dart/runtime/bin/*.dart third_party/dart/runtime/lib/*.dart third_party/dart/sdk/lib/_http/*.dart third_party/dart/sdk/lib/async/*.dart third_party/dart/sdk/lib/collection/*.dart third_party/dart/sdk/lib/convert/*.dart  third_party/dart/sdk/lib/core/*.dart third_party/dart/sdk/lib/developer/*.dart third_party/dart/sdk/lib/html/*.dart third_party/dart/sdk/lib/internal/*.dart third_party/dart/sdk/lib/io/*.dart third_party/dart/sdk/lib/isolate/*.dart third_party/dart/sdk/lib/math/*.dart third_party/dart/sdk/lib/mirrors/*.dart third_party/dart/sdk/lib/profiler/*.dart third_party/dart/sdk/lib/typed_data/*.dart third_party/dart/sdk/lib/vmservice/*.dart flutter/lib/ui/*.dartCopy the code

Platform. dill is the code that implements the page logic, including the Flutter Framework and other library code that pub depends on:

flutter_tutorial_2/lib/main.dart flutter/packages/flutter/lib/src/widgets/*.dart flutter/packages/flutter/lib/src/services/*.dart flutter/packages/flutter/lib/src/semantics/*.dart flutter/packages/flutter/lib/src/scheduler/*.dart flutter/packages/flutter/lib/src/rendering/*.dart flutter/packages/flutter/lib/src/physics/*.dart flutter/packages/flutter/lib/src/painting/*.dart flutter/packages/flutter/lib/src/gestures/*.dart flutter/packages/flutter/lib/src/foundation/*.dart Flutter/packages/flutter/lib/SRC/animation / *. Dart. The pub - cache/hosted/pub. The flutter - IO. Cn/collection - 1.14.6 / lib / *. The dart . The pub - cache/hosted/pub. The flutter - IO. Cn/meta - 1.1.5 / lib / *. The dart . The pub - cache/hosted/pub. The flutter - IO. Cn/shared_preferences - 0.4.2 / *. The dartCopy the code

Both kernel_blob.bin and platform.dill are generated by calling KernelCompiler in bundle.dart in Flutter_Tools.

In Release mode (Flutter run — Release), the Flutter uses Dart’s AOT mode to translate the Dart code into ARM instructions at compile time:

Bin and platform.dill are not included in the APK. They are replaced by four files (ISOLATE/VM) _Snapshot_ (data/instr). The snapshot files by Flutter Flutter in the SDK/bin/cache/artifacts/engine/android – arm – release/Darwin x64 / gen_snapshot command produces, Vm_snapshot_ * is the data and code required for the Dart VM to run, and isolate_Snapshot_ * is the data and code required for each ISOLATE to run.

Operation mechanism of Flutter App

The APK that Flutter builds will decompress all the asset files in the Assets directory to the Flutter directory in the App private file directory, mainly including icudtl.dat which handles character encoding. There are also four snapshot files in Debug mode kernel_blob.bin, platform.dill, and Release mode. By default Flutter calls FlutterMain#startInitialization when Application#onCreate to start the decompression task, Then in FlutterActivityDelegate# onCreate call FlutterMain# ensureInitializationComplete to wait for decompression end task.

The JITters are executed in Debug mode primarily to support the popular hot refresh feature:

When a hot refresh is triggered, the Flutter detects the Dart file that has changed and synchronizes it to the App private cache directory. DartVM loads and modifies the corresponding class or method, and immediately rebuilds the control tree to see the effect on the device.

In Release mode the Flutter will map the snapshot file directly into memory and execute the instructions in it:

In Release mode, FlutterActivityDelegate# onCreate call FlutterMain# ensureInitializationComplete method will set in the AndroidManifest snapshot (not set with the above mentioned (default value) file name and other running parameters are set to the corresponding C++ class object with the same name. When constructing FlutterNativeView instance, nativeAttach is called to initialize DartVM and run the compiled Dart code.

Packaging Android Library

With an understanding of how the Flutter project is built and runs, we can package it as an AAR and integrate it into the existing native App. First, in andorid/app/build.gradle:

APK AAR
Example Change the Android plug-in type apply plugin: ‘com.android.application’ apply plugin: ‘com.android.library’
Delete the applicationId field applicationId “com.example.fluttertutorial” applicationId “com.example.fluttertutorial”
You are advised to add and publish all configuration functions to facilitate debugging defaultPublishConfig ‘release’

publishNonDefault true

With simple modifications we can package the Flutter code into the AAR using Android Studio or Gradle command line tools. The resources required by the Flutter runtime are contained in the AAR and can be referenced in the native App project once published to the Maven server or local Maven repository.

But this is only the first step in the integration, there is much more that needs to be done to make the Flutter page fit seamlessly into the takeaway App.

Image reuse

By default, Flutter packages all image resources into assets directory. However, we do not use Flutter to develop new pages. Image resources are originally placed in various drawable directories according to Android specifications. So it’s not appropriate to add images in assets.

The official Flutter does not provide a direct way to call the image resources in the Drawable directory. After all, the processing of drawable files involves a lot of Android logic (screen density, system version, language, etc.). The assets directory file is also read from inside the engine using C++. It is difficult to read drawable files at the Dart level. The assets directory also supports multiple image resources, which can be automatically selected when used, but Flutter requires 1x images for each image before it recognizes the corresponding images in the other directories:

Flutter: assets: -images/cat.png-images /2x/ cat.png-images /3.5x/cat.pngCopy the code
new Image.asset('images/cat.png');
Copy the code

After this configuration, images of the corresponding density can be correctly used on devices with different resolutions. However, to reduce the size of the APK package, our bitmap resources generally only provide the usual 2x resolution, and devices with other resolutions will automatically scale to the corresponding size at run time. For this particular case, we offer the same capabilities as native apps without increasing the package size:

  1. Before calling the Flutter page, the specified image resource is scaled according to the device screen density and stored in the App private directory.
  2. The Flutter is used by customWMImageControl is actually loaded by converting it to a FileImage and automatically setting the scale to devicePixelRatio.

This can solve the problem of APK package size and image resource missing 1x graph at the same time.

Communication between Flutter and native code

We only use Flutter implements a page, the existing large number of logic is to use a Java implementation, there are many scenes in run time must use native application logic and function, such as network requests, we unified network libraries will add a lot of general parameters in each network request, will also be responsible for the success rate and other indicators monitoring, and abnormal report. We need to report the stack and environment information to the server when a critical exception is caught. It’s not likely that these functions will be implemented in one set immediately using Dart, so we’ll need to use the Platform Channel functionality provided by Dart to implement Dart→Java calls to each other.

In the case of network requests, we define a MethodChannel object in Dart:

import 'dart:async';
import 'package:flutter/services.dart';
const MethodChannel _channel = const MethodChannel('com.sankuai.waimai/network');
Future<Map<String, dynamic>> post(String path, [Map<String, dynamic> form]) async {
  return _channel.invokeMethod("post", {'path': path, 'body': form}).then((result) {
    return new Map<String, dynamic>.from(result);
  }).catchError((_) => null);
}
Copy the code

Then implement a MethodChannel with the same name on the Java side:

public class FlutterNetworkPlugin implements MethodChannel.MethodCallHandler {

    private static final String CHANNEL_NAME = "com.sankuai.waimai/network";

    @Override
    public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
        switch (methodCall.method) {
            case "post":
                RetrofitManager.performRequest(post((String) methodCall.argument("path"), (Map) methodCall.argument("body")),
                        new DefaultSubscriber<Map>() {
                            @Override
                            public void onError(Throwable e) {
                                result.error(e.getClass().getCanonicalName(), e.getMessage(), null);
                            }

                            @Override
                            public void onNext(Map stringBaseResponse) {
                                result.success(stringBaseResponse);
                            }
                        }, tag);
                break;

            default:
                result.notImplemented();
                break; }}}Copy the code

After registering on the Flutter page, the corresponding Java implementation can be invoked by calling the POST method:

loadData: (callback) async {
    Map<String, dynamic> data = await post("home/groups");
    if (data == null) {
      callback(false);
      return;
    }
    _data = AllCategoryResponse.fromJson(data);
    if(_data == null || _data.code ! = 0) { callback(false);
      return;
    }
    callback(true);
  }),
Copy the code

SO library compatibility

The OFFICIAL Flutter SO libraries are available for only four CPU architectures: Armeabi-V7A, ARM64-V8A, x86, and x86-64. The x86 family only supports Debug mode, but most of the SDKS used in Takeaway only provide armeabi architectures. Gn in third_party/skia and build/config/arm. Gni in third_party/dart and build. gn in third_party/skia. Armeabi-v7a is already supported by most devices on the market. The hardware-accelerated floating-point operation instructions provide the Flutter with a significant increase in speed. In the grayscale phase, we can actively block devices that do not support Armeabi-V7A and use the Armeabi-V7A version of the engine. Do this we first need to modify the Flutter of the engine, install directory in Flutter bin/cache/artifacts/engine under a Flutter download all platforms engines:

We just need to change the flutter. Jar under Android-ARM, Android-arm-profile and Android-arm-release, Will the lib/armeabi – v7a/libflutter. So move to the lib/armeabi/libflutter. So you can:

cd $FLUTTER_ROOT/bin/cache/artifacts/engine
for arch in android-arm android-arm-profile android-arm-release; do
  pushd $arch
  cp flutter.jar flutter-armeabi-v7a.jar # backup
  unzip flutter.jar lib/armeabi-v7a/libflutter.so
  mv lib/armeabi-v7a lib/armeabi
  zip -d flutter.jar lib/armeabi-v7a/libflutter.so
  zip flutter.jar lib/armeabi/libflutter.so
  popd
done
Copy the code

After packaging, the SO library of Flutter will be sent to the lib/ Armeabi directory of APK. Armeabi-v7a can crash at run time if the device does not support Armeabi-V7A, so we need to proactively identify and block such devices. On Android, it’s easy to tell if a device supports Armeabi-V7A:

public static boolean isARMv7Compatible(a) {
    try {
        if (SDK_INT >= LOLLIPOP) {
            for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
                if (abi.equals("armeabi-v7a")) {
                    return true; }}}else {
            if (CPU_ABI.equals("armeabi-v7a") || CPU_ABI.equals("arm64-v8a")) {
                return true; }}}catch (Throwable e) {
        L.wtf(e);
    }
    return false;
}
Copy the code

Grayscale and automatic degradation strategy

Horn is a cross-platform configuration delivery SDK within Meituan. Using Horn, you can easily specify the gray switch:

Define a set of conditions on the Condition configuration page, then add the new field flutter on the Parameter Configuration page:

The ABI rules defined here are not enabled because the ABI bottom policy is done on the client.

The Flutter is still in the Beta phase, and crashes are inevitable during the grayscale process. After the crash is observed, a downgrade based on the model or device ID can minimize the impact, but we can do it more quickly. Our Crash collection SDK also supports JNI Crash collection. We have registered a Crash listener for the Flutter to stop the device from functioning once a JNI Crash is detected. The FLUTTER_NATIVE_CRASH_FLAG file is determined to be present before Flutter is activated. If it is present, it indicates that the device has had a Flutter related crash, most likely due to an incompatibility issue, and the functionality of Flutter is no longer in use on the device during the current release cycle.

In addition to crashes, the Dart code on the Flutter page may also experience exceptions, such as a failure to parse data sent by the server in a wrong format. Dart also provides global exception capture:

import 'package:wm_app/plugins/wm_metrics.dart';

void main() {
  runZoned(() => runApp(WaimaiApp()), onError: (Object obj, StackTrace stack) {
    uploadException("$obj\n$stack");
  });
}
Copy the code

In this way, we can achieve a full range of abnormal monitoring and perfect degradation strategy, to minimize the impact of gray scale may bring to the user.

Analyze the crash stack and exception data

The engine part of Flutter is implemented entirely in C/C++, and to reduce package size, all SO libraries are published with symbol table information removed. As with any other JNI crash stack, we can only see the memory address offset in the stack information reported:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Rock/Odin/Odin: 7.1.1 / NMF26F / 1527007828: user/dev - keys'
Revision: '0'
Author: collect by 'libunwind'
ABI: 'arm64-v8a'
pid: 28937, tid: 29314, name: 1.ui  >>> com.sankuai.meituan.takeoutnew <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
 
backtrace:
    r0 00000000  r1 ffffffff  r2 c0e7cb2c  r3 c15affcc
    r4 c15aff88  r5 c0e7cb2c  r6 c15aff90  r7 bf567800
    r8 c0e7cc58  r9 00000000  sl c15aff0c  fp 00000001
    ip 80000000  sp c0e7cb28  lr c11a03f9  pc c1254088  cpsr 200c0030
    #00 pc 002d7088 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #01 pc 002d5a23 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #02 pc 002d95b5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #03 pc 002d9f33 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #04 pc 00068e6d /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #05 pc 00067da5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #06 pc 00067d5f /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #07 pc 003b1877 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #08 pc 003b1db5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #09 pc 0000241c /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr
Copy the code

This information alone is difficult to locate the problem, so we need to use the NDK-Stack provided by the NDK to parse out the specific code location:

ndk-stack -sym PATH [-dump PATH]
Symbolizes the stack trace from an Android native crash.
  -sym PATH   sets the root directory for symbols
  -dump PATH  sets the file containing the crash dump (default stdin)
Copy the code

If you are using a custom engine, you must use the libflubed. so file compiled under engine/ SRC /out/android-release. Generally, we use the official version of the engine. You can download the SO file with the symbol table directly from the Flutter_infra page. You can download the corresponding file according to the version of the Flutter tool used when packaging. For example, version 0.4.4 beta:

$ flutter --version Engine 06afdfe54eFlutter 0.4.4, channel beta, https://github.com/flutter/flutter.git Framework, revision f9bb4289e9 five weekes (a line) Engine • Revision 06afdFe54e Tools • Dart 2.0.0-dev.54.0.flutter-46ab040e58 $CAT flutter/bin/internal/engine.version# flutter under the installation directory of engine. The version file can see complete 06 afdfe54ebef9168a90ca00a6721c2d36e6aafa version information
06afdfe54ebef9168a90ca00a6721c2d36e6aafa
Copy the code

After obtaining the engine version number in console.cloud.google.com/storage/bro… Download android-ARM-release, Android-arm64-release, and Android-x86, and store them in the corresponding directories:

You can run the NDK-stack to see the actual code that crashed and the exact number of lines:

ndk-stack -sym flutter-production-syms/06afdfe54ebef9168a90ca00a6721c2d36e6aafa/armeabi-v7a -dump flutter_jni_crash.txt 
********** Crash dump: **********
Build fingerprint: 'Rock/Odin/Odin: 7.1.1 / NMF26F / 1527007828: user/dev - keys'
pid: 28937, tid: 29314, name: 1.ui  >>> com.sankuai.meituan.takeoutnew <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Stack frame #00 pc 002d7088 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::WordBreaker::setText(unsigned short const*, unsigned int) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /flutter/third_party/txt/src/minikin/WordBreaker.cpp:55
Stack frame #01 pc 002d5a23 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::LineBreaker::setText() at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /flutter/third_party/txt/src/minikin/LineBreaker.cpp:74
Stack frame #02 pc 002d95b5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::ComputeLineBreaks() at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /flutter/third_party/txt/src/txt/paragraph.cc:273
Stack frame #03 pc 002d9f33 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::Layout(double, bool) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /flutter/third_party/txt/src/txt/paragraph.cc:428
Stack frame #04 pc 00068e6d /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine blink::ParagraphImplTxt::layout(double) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /flutter/lib/ui/text/paragraph_impl_txt.cc:54
Stack frame #05 pc 00067da5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine tonic::DartDispatcher
      <:indicesholder>
       , void (blink::Paragraph::*)(double)>::Dispatch(void (blink::Paragraph::*)(double)) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /topaz/lib/tonic/dart_args.h:150
      
Stack frame #06 pc 00067d5f /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine void tonic::DartCall
      
       (void (blink::Paragraph::*)(double), _Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /topaz/lib/tonic/dart_args.h:198
      
Stack frame #07 pc 003b1877 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::AutoScopeNativeCallWrapperNoStackCheck(_Dart_NativeArguments*, void (*)(_Dart_NativeArguments*)) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /third_party/dart/runtime/vm/native_entry.cc:198
Stack frame #08 pc 003b1db5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::LinkNativeCall(_Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/.. /.. /third_party/dart/runtime/vm/native_entry.cc:348
Stack frame #09 pc 0000241c /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr
Copy the code

Dart exceptions are simpler. By default, Dart code is coded into machine code without removing symbol table information, so the Dart exception stack itself identifies the actual code file and line count information where the exception occurred:

FlutterException: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'num' in type cast
#0 _$CategoryGroupFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:29)
#1 new CategoryGroup.fromJson (package:wm_app/all_category/model/category_model.dart:51)
#2 _$CategoryListDataFromJson.
      
        (package:wm_app/lib/all_category/model/category_model.g.dart:5)
      
#3 MappedListIterable.elementAt (dart:_internal/iterable.dart:414)
#4 ListIterable.toList (dart:_internal/iterable.dart:219)
#5 _$CategoryListDataFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:6)
#6 new CategoryListData.fromJson (package:wm_app/all_category/model/category_model.dart:19)
#7 _$AllCategoryResponseFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:19)
#8 new AllCategoryResponse.fromJson (package:wm_app/all_category/model/category_model.dart:29)
#9 AllCategoryPage.build.
      
        (package:wm_app/all_category/category_page.dart:46)
      
<asynchronous suspension>
#10 _WaimaiLoadingState.build (package:wm_app/all_category/widgets/progressive_loading_page.dart:51)
#11 StatefulElement.build (package:flutter/src/widgets/framework.dart:3730)
#12 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3642)
#13 Element.rebuild (package:flutter/src/widgets/framework.dart:3495)
#14 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2242)
# 15 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&Widge tsBinding.drawFrame (package:flutter/src/widgets/binding.dart:626)
# 16 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._hand lePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208)
#17 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990)
#18 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930)
#19 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842)
#20 _rootRun (dart:async/zone.dart:1126)
#21 _CustomZone.run (dart:async/zone.dart:1023)
#22 _CustomZone.runGuarded (dart:async/zone.dart:925)
#23 _invoke (dart:ui/hooks.dart:122)
#24 _drawFrame (dart:ui/hooks.dart:109)
Copy the code

The Flutter versus the original performance

While the full-category pages using the native implementation (left) and the Flutter implementation (right) are almost indistinguishable in actual use:

But we also need a clear comparison in terms of performance.

The two most important page performance metrics we care about are page load time and page rendering speed. The page loading speed can be tested directly using meituan’s internal Metrics performance testing tool. We use the page Activity object creation as the page loading start time and the page API data return as the page loading end time. As you can see from the data of over 400 page launches for both implementations, the median load time for the native implementation (AllCategoryActivity) was 210ms and the median load time for the Flutter implementation (FlutterCategoryActivity) was 231ms. Considering that we do not currently cache and reuse the FlutterView, each creation of the FlutterView requires initializing the entire Flutter environment and loading the associated code, the additional 20ms is within the expected range:

Because the UI logic and drawing code for the Flutter are not executed on the main thread, the original FPS function of Metrics does not count the actual conditions of the Flutter page, and we needed a special way to compare the rendering efficiency of the two implementations. The interface rendering time of Android’s native implementation is monitored using the FrameMetrics interface provided by the system:

public class AllCategoryActivity extends WmBaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            getWindow().addOnFrameMetricsAvailableListener(new Window.OnFrameMetricsAvailableListener() {
                List<Integer> frameDurations = new ArrayList<>(100);
                @Override
                public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                    frameDurations.add((int) (frameMetrics.getMetric(TOTAL_DURATION) / 1000000));
                    if (frameDurations.size() == 100) {
                        getWindow().removeOnFrameMetricsAvailableListener(this);
                        L.w("AllCategory", Arrays.toString(frameDurations.toArray())); }}},new Handler(Looper.getMainLooper()));
        }
        super.onCreate(savedInstanceState);
        // ...}}Copy the code

The GPU operations are all implemented inside the Flutter engine, so the engine needs to be modified to monitor the full render time. In the SRC/Flutter Flutter engine directory/shell/common/rasterizer. Cc file:

void Rasterizer::DoDraw(std: :unique_ptr<flow::LayerTree> layer_tree) {
  if(! layer_tree || ! surface_) {return;
  }

  if (DrawToSurface(*layer_tree)) {
    last_layer_tree_ = std::move(layer_tree);
#if defined(OS_ANDROID)
    if (compositor_context_->frame_count().count() == 101) {
      std: :ostringstream os;
      os << "[";
      const std: :vector<TimeDelta> &engine_laps = compositor_context_->engine_time().Laps();
      const std: :vector<TimeDelta> &frame_laps = compositor_context_->frame_time().Laps();
      size_t i = 1;
      for (auto engine_iter = engine_laps.begin() + 1, frame_iter = frame_laps.begin() + 1;
           i < 101&& engine_iter ! = engine_laps.end(); i++, engine_iter++, frame_iter++) { os << (*engine_iter + *frame_iter).ToMilliseconds() <<",";
      }
      os << "]";
      __android_log_write(ANDROID_LOG_WARN, "AllCategory", os.str().c_str());
    }
#endif}}Copy the code

You can get the actual time spent in drawing each frame. In the test, we opened the pages of the two implementations 100 times respectively, and performed two scrolling operations after each opening to make them draw 100 frames, and recorded the time of each frame of the 100 frames:

for (( i = 0; i < 100; i++ )); doopenWMPage allcategory sleep 1 adb shell input swipe 500 1000 500 300 900 adb shell input swipe 500 1000 500 300 900 adb  shell input keyevent 4done
Copy the code

The average time of each frame (the horizontal axis is the sequence of frames and the vertical axis is the time of each frame, in milliseconds) is calculated by taking the average time of each frame (the horizontal axis is the frame sequence and the vertical axis is the time of each frame, in milliseconds) :

Both the Android native implementation and the Flutter version take over 16ms in the first 5 frames of the page being opened. The native implementation needs to create a lot of views and the Flutter needs to create a lot of widgets. Most of the controls and render nodes (the native RenderNode and the RenderObject of Flutter) can be reused in subsequent frames, so the layout and render operations at startup are the most time consuming.

For 10,000 frames (100 times ×100 frames each), the total average value of the Android test was 10.21ms and the total average value of the Flutter was 12.28ms. The total loss of the Android test was 851 frames, 8.51%, and the total loss of the Android test was 987 frames, 9.87%. The performance of Flutter is comparable to that of the native implementation with the touch event handling and over-drawing optimizations.

conclusion

Flutter is still in its early stages and has not yet been released, but we can see that the team has been working towards this goal. Although the development ecosystem of Flutter is not as mature as the Android and iOS native apps, many of the commonly used complex controls still need to be implemented and some are even difficult to implement (such as the official Listview.scrollto (index) feature which is not yet available), But in terms of high performance and cross-platform Flutter has a big advantage over many UI frameworks.

The Flutter application can only be developed in the Dart language, which has features of a static language and some features of a dynamic language. Dart has a low barrier to entry for Java and JavaScript developers. It takes 3-5 days to get started and 1-2 weeks to become proficient. While developing the full-page version of The Flutter, we were also impressed by the Dart language, which makes the interface construction process of the Flutter more intuitive than Android’s native XML+JAVA. The amount of code has also been reduced from more than 900 lines to more than 500 lines (excluding referenced common components). The integration of the Flutter page into the App will increase the APK volume by at least 5.5MB, including 3.3MB of SO library files and 2.2MB of ICU data files, in addition to about 2MB of business code compiled on 1300 lines.

The characteristics of Flutter are suitable for scenarios that pursue consistent experience across iOS and Android platforms and high performance UI interaction, but not for scenarios that pursue dynamic deployment. The Flutter can already be deployed dynamically on Android, but due to Apple’s limitations, it is very difficult to deploy the Flutter dynamically on iOS, and the team is in active communication with Apple.

The meituanmai front end team will continue to use the Flutter implementation in more scenarios in the future, and actively report the problems discovered and fixed in the practice to the open source community to help the development of Flutter. If you are interested in Flutter, please join us.

The resources

  1. 英 文 website

  2. Overview of the Flutter framework technology

  3. Flutter plug-in Warehouse

  4. A Tour of the Dart Language

  5. A Tour of the Dart Libraries

  6. Why Flutter Uses Dart

  7. Overview of the mechanism of Flutter Layout

  8. Flutter’s Layered Design

  9. Flutter’s Rendering Pipeline

  10. Flutter: The Best Way to Build for Mobile? @GOTO conf

  11. Flutter Engine

  12. Writing custom platform-specific code with platform channels

  13. Flutter Engine Operation in AOT Mode

  14. Flutter’s modes

  15. Symbolicating-production-crash-stacks

Author’s brief introduction

Shao Jie, senior engineer of Meituan, joined meituan in 2017. Currently, he is mainly responsible for infrastructure construction such as monitoring of takeaway App.

recruitment

Meituan.com is looking for senior/senior engineers and technical experts of Android, iOS, FE, Base in Beijing, Shanghai, chengdu, welcome to send your resume to wukai05#meituan.com.