Best Practice of Continuous Integration of flutter Hybrid Engineering

1. Introduction

This article focuses on some specific implementations of Flutter's direct dependence on relief in Flutter hybrid engineering.

2. Thinking

Because the current idle fish is a mixed development mode of Flutter and Native, there are some students who only do Native development and are not familiar with Flutter technology.

(1) If the Flutter engineering structure is directly used as the daily development, then this part of the Native development students also need to configure the Flutter environment, understand some of the Flutter technology, the cost is relatively high.

(2) At present, the construction system of Ali Group does not support the direct construction of Flutter project, which also requires us to relieve the direct dependence of Native project on Flutter.

For these two reasons, we hope that we can design a Flutter dependency extraction module, which can publish Flutter dependency extraction as a Flutter dependency library to remote for pure Native engineering reference. As shown in the following figure:

image

Flutter's direct dependency relief

3. Realization

3.1 Flutter Analysis of Native Engineering Dependence

When we analyze the Flutter project, we will find that the dependence of the Native project on the Flutter project mainly consists of three parts:
1. Flutter libraries and engines: Flutter's Framework libraries and engine libraries.
2. Flutter Project: The function of Flutter module implemented by ourselves is mainly realized by dart code in lib directory under Flutter Project.
3. Flutter Plugin realized by ourselves: Flutter Plugin realized by ourselves.

We unravel the APP files of Android and iOS and find that the main files Flutter relies on are as follows:

image

Flutter-dependent files (Flutter products)

Among them,
Android's Flutter-dependent files:
1. Flutter libraries and engines:

icudtl.dat, libflutter.so, and some class files. These are encapsulated in flutter.jar, which is located in the Flutter Library Directory [flutter/bin/cache/artifacts/engine].
2. Flutter engineering products:

isolate_snapshot_data,isolate_snapshot_instr,vm_snapshot_data,vm_snapshot_instr,flutter_assets.
3. Flutter Plugin:

aar files compiled by each plugin.
Among them:
isolate_snapshot_data application data segment

isolate_snapshot_instr application instruction segment
vm_snapshot_data VM virtual machine data segment

vm_snapshot_instr VM Virtual Machine Instruction Segment

iOS Flutter-dependent files:
1. Flutter libraries and engines: Flutter.framework
2. The product of the Flutter project: App.framework
3. Flutter Plugin: framework for compiled plugins, other frameworks in the diagram

Then we only need to extract the compiled results of these three parts and pack them into an SDK dependency form for the Native Project, which can relieve the direct dependence of the Native Project on the Flutter Project.

3.2 Android Dependent Flutter Library Extraction

3.2.1 Analysis of Flutter Compilation Tasks in Android

The Android packaging of the Flutter project is actually just inserting a flutter.gradle task into Android's Gradle task, and the flutter.gradle does three main things: (This file can be found in the flutter Library under the [flutter/packages/flutter_tools/gradle] directory.)

  1. Increase the dependency of flutter.jar.
  2. Insert compilation dependencies for Flutter Plugin.
  3. Insert the compilation task of the Flutter project, and finally copy the product (two isolaate_snapshot files, two vm_snapshot files and the flutter_assets folder) to mergeAssets.outputDir, and finally merge to the APK's assets directory.

3.2.2 Implementation of Flutter Dependent Extraction for Android

Having figured out the Android compilation product of the Flutter project, we have the following steps for extracting Android's Flutter dependency:
1. Compile Flutter project.

The main work of this part is to compile Flutter's dart and resource parts, which can be compiled with AOT and Bundle commands.

echo "Clean old build"
find . -d -name "build" | xargs rm -rf
./flutter/bin/flutter clean

echo "Get packages"
./flutter/bin/flutter packages get

echo "Build release AOT"
./flutter/bin/flutter build aot --release --preview-dart-2 --output-dir=build/flutteroutput/aot
echo "Build release Bundle"
./flutter/bin/flutter build bundle --precompiled --preview-dart-2 --asset-dir=build/flutteroutput/flutter_assets

2. Package the products of flutter.jar and Flutter Project into an aar.

The main work of this part is to encapsulate the product of flutter.jar and step 1 compilation into an aar.

(1) Adding flutter.jar dependencies

project.android.buildTypes.each {
    addFlutterJarImplementationDependency(project, releaseFlutterJar)
}
project.android.buildTypes.whenObjectAdded {
    addFlutterJarImplementationDependency(project, releaseFlutterJar)
}

private static void addFlutterJarImplementationDependency(Project project, releaseFlutterJar) {
    project.dependencies {
        String configuration
        if (project.getConfigurations().findByName("implementation")) {
            configuration = "implementation"
        } else {
            configuration = "compile"
        }
        add(configuration, project.files {
            releaseFlutterJar
        })
    }
}

(2) Merge Flutter products to assets

// merge flutter assets
def allertAsset ="${project.projectDir.getAbsolutePath()}/flutter/assets/release"
Task mergeFlutterAssets = project.tasks.create(name: "mergeFlutterAssets${variant.name.capitalize()}", type: Copy) {
    dependsOn mergeFlutterMD5Assets
    from (allertAsset){
        include "flutter_assets/**" // the working dir and its files
        include "vm_snapshot_data"
        include "vm_snapshot_instr"
        include "isolate_snapshot_data"
        include "isolate_snapshot_instr"
    }
    into variant.mergeAssets.outputDir
}
variant.outputs[0].processResources.dependsOn(mergeFlutterAssets)

2. At the same time, this aar and the compiled aar of Flutter Plugin are released to the maven repository.

(1) Releasing aar packaged Flutter engineering products

echo 'Clean packflutter input(flutter build)'
rm -f -r android/packflutter/flutter/

# Copy flutter.jar
echo 'Copy flutter jar'
mkdir -p android/packflutter/flutter/flutter/android-arm-release && cp flutter/bin/cache/artifacts/engine/android-arm-release/flutter.jar "$_"

# Copy asset
echo 'Copy flutter asset'
mkdir -p android/packflutter/flutter/assets/release && cp -r build/flutteroutput/aot/* "$_"
mkdir -p android/packflutter/flutter/assets/release/flutter_assets && cp -r build/flutteroutput/flutter_assets/* "$_"

# publish the flutter library and flutter_app as aar to Ali-maven at the same time
echo 'Build and publish idlefish flutter to aar'
cd android
if [ -n "$1" ]
then
    ./gradlew :packflutter:clean :packflutter:publish -PAAR_VERSION=$1
else
    ./gradlew :packflutter:clean :packflutter:publish
fi
cd ../

(2) aar for publishing Flutter Plugin

# Publish plugin to Ali-maven
echo "Start publish flutter-plugins"
for line in $(cat .flutter-plugins)
do
    plugin_name=${line%%=*}
    echo 'Build and publish plugin:' ${plugin_name}

    cd android
    if [ -n "$1" ]
    then
        ./gradlew :${plugin_name}:clean :${plugin_name}:publish -PAAR_VERSION=$1
    else
        ./gradlew :${plugin_name}:clean :${plugin_name}:publish
    fi
    cd ../
done

3. Pure Native projects only need to compile the aar we publish to maven.

In the normal development phase, we need to be able to rely on the latest aar in real time, so we use the SNAPSHOT version.

configurations.all {
    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}

ext {
    flutter_aar_version = '6.0.2-SNAPSHOT'
}

dependencies {
    //Flutter Master Engineering Dependency: Includes functions based on flutter development, flutter engine lib
    compile("com.taobao.fleamarket:IdleFishFlutter:${getFlutterAarVersion(project)}") {
        changing = true
    }
    //... other dependencies
}

static def getFlutterAarVersion(project) {
    def resultVersion = project.flutter_aar_version
    if (project.hasProperty('FLUTTER_AAR_VERSION')) {
        resultVersion = project.FLUTTER_AAR_VERSION
    }
    return resultVersion
}

3.3 Extraction of iOS-dependent Flutter Library

3.3.1 How Flutter-dependent files are generated in iOS

Executing the compilation command "flutter build ios" will eventually execute the compilation script of Flutter [xcode_backend.sh], which mainly does the following things:

  1. Getting various parameters (such as project_path, target_path, build_mode, etc.) mainly comes from various definitions of Generated.xcconfig.
  2. Delete App.framework and app.flx from the Flutter directory.
  3. Compare Flutter/Flutter.framework withFlutter.framework in the {artifact_variant} directory, if not equal, overrides the former with the latter.
  4. Get the parameters needed to generate the App.framework command (build_dir, local_engine_flag, preview_dart_2_flag, aot_flags).
  5. Generate App.framework and copy the generated App.framework and AppFrameworkInfo.plist to the Flutter directory of the XCode project.

3.3.2 iOS Implementation of Flutter Dependent Extraction

The extraction steps of iOS Flutter dependency are as follows:
1. Compile Flutter project to generate App.framework.

echo "===Clear flutter History Compilation==="
./flutter/bin/flutter clean

echo "===Renewal plugin Indexes==="
./flutter/bin/flutter packages get

echo "===generate App.framework and flutter_assets==="
./flutter/bin/flutter build ios --release

2. Packing plug-ins as static libraries.

There are two main steps: one is to type plugin into binary file, the other is to type plugin's registration entry into binary file.

echo "===Generate each plugin Binary Library File==="
cd ios/Pods
#/usr/bin/env xcrun xcodebuild clean
#/usr/bin/env xcrun xcodebuild build -configuration Release ARCHS='arm64 armv7' BUILD_AOT_ONLY=YES VERBOSE_SCRIPT_LOGGING=YES -workspace Runner.xcworkspace -scheme Runner BUILD_DIR=../build/ios -sdk iphoneos
for plugin_name in ${plugin_arr}
do
    echo "generate lib${plugin_name}.a..."
    /usr/bin/env xcrun xcodebuild build -configuration Release ARCHS='arm64 armv7' -target ${plugin_name} BUILD_DIR=../../build/ios -sdk iphoneos -quiet
    /usr/bin/env xcrun xcodebuild build -configuration Debug ARCHS='x86_64' -target ${plugin_name} BUILD_DIR=../../build/ios -sdk iphonesimulator -quiet
    echo "merge lib${plugin_name}.a..."
    lipo -create "../../build/ios/Debug-iphonesimulator/${plugin_name}/lib${plugin_name}.a" "../../build/ios/Release-iphoneos/${plugin_name}/lib${plugin_name}.a" -o "../../build/ios/Release-iphoneos/${plugin_name}/lib${plugin_name}.a"
done

echo "===Generating binary library files for registration entries==="
for reg_enter_name in "flutter_plugin_entrance" "flutter_service_register"
do
    echo "generate lib${reg_enter_name}.a..."
    /usr/bin/env xcrun xcodebuild build -configuration Release ARCHS='arm64 armv7' -target ${reg_enter_name} BUILD_DIR=../../build/ios -sdk iphoneos
    /usr/bin/env xcrun xcodebuild build -configuration Debug ARCHS='x86_64' -target ${reg_enter_name} BUILD_DIR=../../build/ios -sdk iphonesimulator
    echo "merge lib${reg_enter_name}.a..."
    lipo -create "../../build/ios/Debug-iphonesimulator/${reg_enter_name}/lib${reg_enter_name}.a" "../../build/ios/Release-iphoneos/${reg_enter_name}/lib${reg_enter_name}.a" -o "../../build/ios/Release-iphoneos/${reg_enter_name}/lib${reg_enter_name}.a"
done

3. Upload these to remote warehouse and generate new Tag.
4. Pure Native projects only need to update pod dependencies.

4. Continuous Integration Process of Flutter Hybrid Engineering

In this way, we can get rid of the direct dependence of the Native Project on the Flutter Project, but there are still some problems in the daily development.

  1. Flutter project updates, remote dependency library updates are not timely.
  2. When integrating versions, it is easy to forget to update remote dependency libraries, resulting in versions that do not integrate the latest Flutter functionality.
  3. At the same time, when Flutter is developed in parallel with multiple lines, version management is confused and remote libraries are easily covered.
  4. At least one student is required to follow up the release continuously, and the labor cost is high.

In view of these problems, we have introduced our team's CI automation framework to solve them from two aspects:

(For the CI Automation Framework, we will write and share later.)
On the one hand, automation reduces labor costs and human errors through automation.
On the other hand, version control should be done well in the form of automation.
Specific operation:
First of all, the compilation and publication of remote libraries corresponding to Flutter project need to be completed automatically before constructing pure Native project. The whole process does not need manual intervention.
Secondly, in the development and testing phase, the version number of the five-segment version is adopted, and the last bit is automatically incremented, so that the version number of all parallel development Flutter libraries in the testing phase will not conflict.
Finally, in the release phase, the three-or four-segment version number can be used to keep in line with the APP version number, so as to facilitate follow-up traceability.

The whole process is shown in the following figure:

image

Last

Share an outline of mobile architecture, including all the technical systems that mobile architects need to master. You can compare your shortcomings or deficiencies to learn and improve in a direction.

Require high-definition architecture diagram and video data and article project source code can join my technical exchange group: 825106898 private chat group hosts and sisters free access

image

Keywords: iOS Android SDK Gradle

Added by kpasiva on Mon, 05 Aug 2019 11:34:16 +0300