The problem background
What is Flutter for developers? What language is it written in, what parts are included, how is it compiled and run on the device? How does Flutter implement Hot Reload changes in Debug mode and Release mode natively? How does the Flutter project differ from our Android/iOS project, how does it relate to it, and how does it incorporate Android/iOS? How do Flutter’s rendering and event delivery mechanisms work? Does Flutter support hot update? Flutter does not officially provide ARMV7 support for iOS. Is this true? If engine bugs are found while using Flutter, how can they be corrected and implemented? How can build slowness or errors be located, modified, and implemented?
All this requires a holistic view of Flutter from its design, development and construction to its final operation.
This article will take a simple HELLO_flutter as an example to introduce the principle, customization and optimization of Flutter.
Introduction of Flutter
The Architecture of Flutter is divided into three layers :Framework, Engine, and Embedder.
Dart is used to implement the Framework, including Material Design style Widgets,Cupertino style Widgets (for iOS), basic text/image/button Widgets, rendering, animation, gestures, and more. The core code of this part is: Flutter package under the flutter repository, IO, Async, UI and other packages under the Sky_engine repository (DART: UI library provides the interface between the Flutter framework and the engine).
Engine is implemented in C++, including Skia,Dart, and Text. Skia is an open source two-dimensional graphics library that provides a common API for a variety of hardware and software platforms. It has been used as a graphics engine for Google Chrome, Chrome OS, Android, Mozilla Firefox, Firefox OS and many other products, Supported platforms also include Windows7+,macOS 10.10.5+,iOS8+,Android4.1+,Ubuntu14.04+, etc. The Dart section includes :Dart Runtime, Garbage Collection(GC), and JIT(Just In Time) support if In Debug mode. In Release and Profile mode, AOT(Ahead Of Time) compiles to native ARM code without JIT. Text is Text rendering, which is rendered at the following levels: derived from minikin’s libtxt library (for font selection and line separation); HartBuzz is used for glyphs selection and shaping; Skia works as a render /GPU back end, using FreeType rendering on Android and Fuchsia, and CoreGraphics for rendering fonts on iOS.
Embedder is an Embedder layer that allows the Flutter to be embedded to various platforms. The main tasks here include rendering Surface Settings, thread Settings, and plug-ins. From this we can see that the platform-dependent layer of Flutter is very low. Platforms (such as iOS) only provide a canvas, and all the remaining render related logic is inside Flutter, which makes it very cross-end consistent.
Flutter engineering structure
This article uses the development environment for Flutter Beta V0.3.1, corresponding to Engine Commit: 09D05A389.
Taking the Hello_FLUTTER project as an example, the structure of the Flutter project is as follows:
Ios is part of the ios code, using CocoaPods to manage dependencies, Android is part of the Android code, using Gradle to manage dependencies, lib is dart code, using pub to manage dependencies. Pubspec. yaml and pubspec.lock correspond to Cocoapods Podfile and podfile. lock in iOS.
Flutter model
For Flutter, it supports the usual debug, Release,profile and other modes, but it is different.
Debug mode: Indicates the Dart JIT mode, also called check mode or slow mode. Supports devices, emulators (iOS/Android), assertions are enabled in this mode, including all debugging information, service extensions, and debugging AIDS such as the Observatory. This pattern is optimized for rapid development and operation, but not for execution speed, package size, and deployment. In Debug mode, compilation uses JIT technology and supports the popular sub-second stateful Hot Reload.
Release mode: Corresponds to Dart’s AOT mode, which is deployed to end users. Only real machines are supported, not emulators. Turn off all assertions, remove as much debugging information as possible, and turn off all debugging tools. Package sizes are optimized for fast startup and execution. Disabled all debugging AIDS, service extensions.
Profile mode: Similar to Release mode, but with additional support for profile-mode service extensions, tracing support, and minimization of dependencies needed to use trace information, for example, observatory can be connected to processes. The reason the Profile does not support emulators is that diagnostics on emulators do not represent true performance.
Since there is no difference between Profile and Release in terms of compilation principles, this article only discusses Debug and Release modes.
In fact, the iOS/Android project under Flutter is still essentially a standard iOS/Android project, Flutter is simply generated and embedded in app. framework and flutter. Framework (iOS) by adding a shell in BuildPhase, and flutter /instr(Android) to compile and embed the Flutter related code in the native App. Therefore, this paper mainly discusses the construction, operation and other principles of flutter introduction. Although the compiler target includes ARM, X64,x86, and ARM64, this article will only discuss arm because of the similar principles (android defaults to ARMV7 unless otherwise specified).
Compilation and Running of Flutter code (iOS)
Compilation in Release mode
In Release mode, iOS project DART code construction link under Flutter is as follows:
Gen_snapshot is the DART compiler. It uses tree shaking(similar to tree logic, generating the smallest package, thus banning reflection features supported by DART in Flutter) and is responsible for generating assembly machine code. The final App. Framework is generated through a tool chain such as XCRun. All dart code, including business code, tripartite package code, and the flutter framework code they rely on, will eventually compile into app. framework.
PS. The tree shaking function in gen_snapshot, corresponding to logic see: engine/SRC/third_party/dart/runtime/vm/compiler/aot/precompiler. Cc
The dart code eventually corresponds to the symbols in the App.framework as follows:
In fact, similar to the Android Release of product (see below), the App. The framework also includes kDartVmSnapshotData, kDartVmSnapshotInstructions, KDartIsolateSnapshotData kDartIsolateSnapshotInstructions four parts. Why does iOS use app. framework instead of Android’s four-file approach? The reason is that the Flutter engine cannot mark a memory page as executable on iOS due to system limitations, while Android can.
The Flutter. Framework corresponds to the engine part of the Flutter architecture, as well as Embedder. Practice Flutter. The framework is located in the/bin/cache/artifacts of Flutter warehouse/engine/ios *, from the Google warehouse pull by default. When you need to customize your changes, you can download the Engine source code and use the Ninja build system to generate them.
The final products of Flutter related code are app. framework(Dart code generation) and Flutter. Framework (engine). From the perspective of the Xcode project, Generated. Xcconfig describes the configuration information about the Flutter environment. Xcode_backend. sh, which is added to Build Phases in the Runner project Settings, makes a copy of the Flutter. Framework (from the engine of the Flutter repository to the Flutter directory in the Runner project root directory) and embed it. Compilation and embedding of app. framework. The content of Flutter generated in Runner. App is as follows:
Where Flutter_assets is the related resource and the code is app. framework and Flutter. Framework under Frameworks.
Run in Release mode
The rendering, event, and communication logic associated with Flutter is as follows:
Dart’s main function call stack is as follows:
Compiling in Debug mode
Compilation of flutter in Debug mode has a similar structure to Release mode, with two main differences:
1.Flutter.framework
Because it is Debug, there is JIT support in the Framework in this mode, but there is no JIT part in Release mode.
2.App.framework
Unlike app. framework in AOT mode, which is the machine code corresponding to the Dart code, in JIT mode, App.framework has a few simple apis, and its Dart code is stored in snapshot_blob.bin. This part of the snapshot is a script snapshot, which contains the simple tokenized source code. All comments, whitespace characters are removed, and constants are normalized without machine code, tree shaking or obfuscation.
The symbol table in app. framework looks like this:
Run the strings command to Runner. App /flutter_assets/snapshot_blob.bin, and the following information is displayed:
In Debug mode, the call stack for the main entry is as follows:
Compilation and Execution of Flutter code (Android)
In addition to some platform-related features, Android and iOS are similar in other logic, such as Release corresponding to AOT, Debug corresponding to JIT, etc., so only differences between the two are involved here.
Compilation in Release mode
In release mode, dart code in Android project under Flutter is as follows:
The vm/ ISOLate_SNAPshot_data /instr contents are arm instructions. Vm_ involves services such as Runtime (such as GC), which are used to initialize DartVM. See Dart_Initialize(dart_api.h) for call entry. Isolate__ corresponds to our application DART code and is used to create a new ISOLATE. See Dart_CreateIsolate(dart_api.h) for the call entry. Jar is similar to iOS, including the Engine part (libflutter. And the Embedder part (FlutterMain, FlutterView, FlutterNativeView, etc.). Practice flutter. The jar is located in the/bin/cache/artifacts of flutter warehouse/engine/android *, from the Google warehouse pull by default. To customize the flutter, download the Engine source and use the Ninja build system to generate flutter. Jar.
Using isolate_snapshot_data/instr as an example, the result of executing the disarm command is as follows:
)
Its Apk structure is shown as follows:
After APK is newly installed, a judgment logic (versionCode in PackageInfo combined with lastUpdateTime) is used to decide whether to copy assets in APK. The copied contents are as follows:
The ISOLATE/VM_SNAPshot_data /instr are all located in the local data directory of the APP, and these parts are writable. You can download and replace them to update the APP dynamically.
Run in Release mode
Compiling in Debug mode
Similar to iOS Debug/Release differences, Android Debug and Release differences mainly include the following two parts:
1.flutter.jar
The difference with the iOS
2.App code
The snapshot_blob.bin file under Flutter_assets is the same as that in iOS.
After explaining how to build Flutter on iOS/Android, here is how to customize Flutter/Engine for customization and optimization. Since Flutter is an agile iteration, existing problems may not be problems later, so this part is not about how many problems can be solved, but how to solve different problems.
Customizations and optimizations related to Flutter construction
Flutter is a very complex system. In addition to the three-tier architecture mentioned above, Flutter Android Studio(Intellij) plugin, Pub repository management, etc. But our customizations and optimizations tend to be the tool-chain related logic of FLUTTER, which is located in the Flutter_tools package of the FLUTTER repository. Here is an example of how to customize this section.
The Android part
Related content includes flutter. Jar, libflutter. So (under flutter. Jar), gen_snapshot, flutter. Gradle, flutter(flutter_tools).
1. Restrict target to armeabi in Android
This section is build-related and the logic is located under flutter. Gradle. The default logic of flutter needs to be changed when apps support ARMv7 /arm64 via Armeabi. As follows:
Because of gradle’s inherent characteristics, this part of the change can be directly built to take effect.
2. Set Android to use the first launchable- Activity by default
This part is related to flutter_tools and modified as follows:
The point here is not how to change it, but how to make it work. In principle, flutter run/build/analyze/test/upgrade command execution actually is the flutter, flutter/bin/flutter) this script, Dart flutter_tools.snapshot(generated by Packages/Flutter_tools)
if [[ ! -f "SNAPSHOT_PATH" ]] || [[ ! -s "STAMP_PATH" ]] || [[ "(cat "STAMP_PATH")" ! = "revision" ]] || [[ "FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then rm -f "$FLUTTER_ROOT/version" touch "$FLUTTER_ROOT/bin/cache/.dartignore" "$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh" echo Building flutter tool... if [[ "$TRAVIS" == "true" ]] || [[ "$BOT" == "true" ]] || [[ "$CONTINUOUS_INTEGRATION" == "true" ]] || [[ "$CHROME_HEADLESS" == "1" ]] || [[ "$APPVEYOR" == "true" ]] || [[ "$CI" == "true" ]]; then PUB_ENVIRONMENT="$PUB_ENVIRONMENT:flutter_bot" fi export PUB_ENVIRONMENT="$PUB_ENVIRONMENT:flutter_install" if [[ -d "$FLUTTER_ROOT/.pub-cache" ]]; then export PUB_CACHE="${PUB_CACHE:-"$FLUTTER_ROOT/.pub-cache"}" fi while : ; do cd "$FLUTTER_TOOLS_DIR" "$PUB" upgrade --verbosity=error --no-packages-dir && break echo Error: Unable to 'pub upgrade' flutter tool. Retrying in five seconds... sleep 5 done "$DART" --snapshot="$SNAPSHOT_PATH" --packages="$FLUTTER_TOOLS_DIR/.packages" "$SCRIPT_PATH" echo "$revision" > "$STAMP_PATH" fiCopy the code
It is not hard to see how to rebuild Flutter_tools by removing flutter_repo_dir/bin/cache/flutter_tools.stamp(which is regenerated once) or by disabling if/ FI judgments (which are regenerated every time).
3. How to use Release mode Flutter in Android project Debug mode
If there is a lag in flutter during development, this may be due to logic or Debug mode. Build the APK under Release or force the flutter to change to release mode as follows:
The iOS section
Related contents include Flutter. Framework, gen_snapshot, xcode_backend.sh, and Flutter (flutter_tools).
1. Recompile caused by repeated replacement of Flutter. Framework during optimization build
This part of the logic is build-related and is in xcode_backend.sh, To ensure that a Flutter gets the correct Flutter. Framework, every time a Flutter finds and replaces the Flutter based on the configuration (see Generated. Xcconfig config). Modified as follows:
2. How to use Release Mode Flutter in iOS engineering Debug mode
Set FLUTTER_BUILD_MODE in Generated. Xcconfig to release and FLUTTER_FRAMEWORK_DIR to the path corresponding to release.
3. The armv7 support
See the original article :github.com/flutter/eng…
In fact, Flutter itself supports ARMV7 on iOS. However, there is no official support for FLUTTER under V0.3.1. You need to modify the relevant logic as follows:
A. Default logic to generate Flutter. Framework (arm64)
B. Modify flutter so that flutter_tools can be rebuilt each time. Modify build_aot.dart and mac.dart, change arm64 for iOS to ARMv7, and change gen_snapshot to I386.
Gen_snapshot in the I386 architecture can be generated by running the following command:
./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm
ninja -C out/ios_release_armCopy the code
There is an implicit logic here:
The CPU related predefined macros (x86_64/ I386, etc.) for building gen_Snapshot, the arch for target gen_Snapshot, and the architecture of the final app. framework should be consistent overall. X86_64 -> X86_64 -> ARM64 or I386 -> I386 -> ARMV7.
C. On iPhone4S, EXC_BAD_INSTRUCTION(EXC_ARM_UNDEFINED) error will occur due to gen_snapshot generating unsupported SDIV instruction. This is done by adding the parameter — no-use-INTEger-division to gen_snapshot (located in build_aot.dart). The logic behind this (dart compiling arm code logic flow) is shown below:
D. Generate a Flutter. Framework that supports both armV7 and arm64 based on the Flutter.
E. Modify info. plist under Flutter. Framework to remove it
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>Copy the code
Do the same for App. Framework to avoid being affected by App Thining.
Flutter_tools debugging
If you want to know how flutter builds apK in debug mode, you can use the following logic:
A. Learn the cli parameters of flutter_tools
B. Open packages/ Flutter_tools as a DART project, modify flutter_tools.dart based on the obtained parameters, and set the COMMAND line DART app to start debugging.
Customizing engine and debugging
Assume that we are customising and developing flutter beta v0.3.1. In order to ensure stability, we do not update the SDK for a certain period of time. At this time, flutter master fixes a bug that has been on Flutter BETA V0.3.1, which is written as fix_bug_commit. How can you track and manage this situation?
1. The flutter beta v0.3.1 specifies the corresponding engine commit as follows: 09 d05a389, see flutter/bin/internal/engine version.
2. Get the engine code
Custom_beta_v0.3.1 :custom_beta_v0.3.1 :custom_beta_v0.3.1 :custom_beta_v0.3.1
4. Based on custom_beta_v0.3.1(commit: 09D05A389), execute gclient sync to get all engine code corresponding to Flutter Beta V0.3.1.
5. Use git cherry-pick fix_bug_commit to synchronize the master changes to custom_beta_v0.3.1.
6. Execute the following code for iOS related changes:
./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm
ninja -C out/ios_debug_arm
./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm
ninja -C out/ios_release_arm
./flutter/tools/gn --runtime-mode=profile --ios --ios-cpu=arm
ninja -C out/ios_profile_arm
./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm64
ninja -C out/ios_debug
./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm64
ninja -C out/ios_release
./flutter/tools/gn --runtime-mode=profile --ios --ios-cpu=arm64
ninja -C out/ios_profileCopy the code
Generate arm/ ARM64 & DEBUG /release/profile for iOS. Can be used to build product to replace flutter/bin/cache/artifacts/engine/ios * of flutter in the framework and gen_snapshot.
To debug the source code for Flutter. Framework, run the following command to build Flutter:
./flutter/tools/gn --runtime-mode=debug --unoptimized --ios --ios-cpu=arm64
ninja -C out/ios_debug_unoptCopy the code
Debug engine source code by replacing flutter. Framework and gen_snapshot in flutter with generated products.
7. Execute the following code for Android-related changes:
./flutter/tools/gn --runtime-mode=debug --android --android-cpu=arm
ninja -C out/android_debug
./flutter/tools/gn --runtime-mode=release --android --android-cpu=arm
ninja -C out/android_release
./flutter/tools/gn --runtime-mode=profile --android --android-cpu=arm
ninja -C out/android_profileCopy the code
Generate arm&DEBUG /release/profile artifacts for Android. Can be used to build product to replace flutter/bin/cache/artifacts/engine/android * gen_snapshot and flutter under the jar.
Subsequent topics
We will continue to share the following topics:
A. How Embedder handles rendering and event (click, etc.) delivery in the utter architecture, how threads and message loops are managed, and how channels work.
B. How Dart compilation and debugging works in Ngine and how rendering is handled inside Skia.
C. Ative project how to use Flutter to achieve progressive reconstruction and migration.
D. How to set up private warehouses and realize pub’s support for multiple warehouses
.
Contact us
If you have any questions or corrections about the content of the text, please let us know.
We are looking for candidates to apply for flutter, C++, iOS/Android and Java.
Contact email: [email protected]
Reference documentation
1.Flutter’s modes
2.iOS Builds Supporting ARMv7
3.Contributing to the Flutter engine
4.Flutter System Architecture
5.The magic of flutter
6.Symbolicating production crash stacks
7.flutter.io
8. Get the source code used in this article