×
Community Blog Flutter Analysis and Practice: Hybrid Projects and Continuous Integration

Flutter Analysis and Practice: Hybrid Projects and Continuous Integration

This article describes the method of removing native project dependencies on Flutter in Flutter hybrid projects.

With the booming development of mobile smart devices, a mobile multi-terminal development framework has become a general trend. Download the Flutter Analysis and Practice: Evolution and Innovation of Xianyu Technologies eBook for step-by-step analyses based on real-life problems, as well as clear explanations of the important concepts of Flutter.

1.3.1 Background

1) If the Flutter project structure is directly used for daily development, native project developers also need to set up the Flutter environment and understand the Flutter technology, which increases costs.

2) Currently, Alibaba Group's building system does not support direct building of Flutter projects. This requires Xianyu to remove native project dependencies on Flutter.

From this perspective, Xianyu wants to design a module to extract Flutter dependencies as a Flutter dependency library and publish it to the remote repository for reference by native projects, as shown in Figure 1-16.

1
Figure 1-16

1.3.2 Implementation Methods

1.3.2.1 Analyze Flutter Projects That Native Projects Depend On

Based on Flutter project analysis, native projects depend on Flutter as follows:

  • Flutter library and engine files, including the framework library and engine library of Flutter.
  • Flutter projects, which can be packaged as an AAR file for reference. Custom Flutter module features are implemented through Dart code in the lib directory.
  • Custom Flutter plug-ins

Major Flutter dependency files are extracted from the app packages of Android and iOS, as shown in Figure 1-17.

2
Figure 1-17

1) Flutter dependency files for Android

  • Flutter library and engine files, including icudtl.dat, libflutter.so, and some class files. They are all encapsulated in flutter.jar, which is in flutter/bin/cache/artifacts/engine in the Flutter library directory.
  • Flutter project outputs, including isolate_snapshot_data, isolate_snapshot_instr, vm_snapshot_data, vm_snapshot_instr, and flutter_assets.
  • Flutter plug-ins. Android Archive (AAR) files compiled by each plug-in include: isolate_snapshot_data (app data segments), isolate_snapshot_instr (app instruction segments), vm_snapshot_data (virtual machine data segments), and vm_snapshot_instr (virtual machine instruction segments.)

2) Flutter dependency files for iOS

  • Flutter library and engine files, including Flutter.framework.
  • Flutter project outputs, including App.framework.
  • Flutter plug-ins, including frameworks of each compiled plug-in and other frameworks show in Figure 1-17.

The compilation results need to be extracted and packaged into a Software Development Kit (SDK) dependency for the native project, which removes native project dependencies on the Flutter project.

1.3.2.2 Extract the Flutter Library Android Depends On

1) Analyze the Flutter compilation task on Android

Android packaging of the Flutter project is to insert a flutter.gradle task to the Android Gradle task. The flutter.gradle task (this file can be found in the flutter/packages/flutter_tools/gradle directory in the Flutter library) implements the following operations:

  • Add the flutter.jar dependency.
  • Add the compilation dependency of Flutter plug-ins.
  • Add the compilation task of the Flutter project. The output includes two isolate_snapshot files, two vm_snapshots files, and the flutter_assets folder. Copy the compiled project to mergeAssets.outputDir and then save it to the assets directory of the Android Package Kit (APK.)

2) Implement Flutter dependency extraction on Android

Perform the following steps:

(a) Compile a Flutter project.

This aims to compile Flutter's Dart code and resources by running the ahead-of-time (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

(b) Package flutter.jar and the output of the Flutter project into an AAR file.

This aims to encapsulate flutter.jar and the output compiled in step 1 into an AAR file.

Add the flutter.jar dependency.

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("api")) {
            configuration = "api"
        } else {
            configuration = "compile"
        }
        add(configuration, project.files {
            releaseFlutterJar
        })
    }
}

Merge the output of the Flutter project into assets.

// 合并 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/**" 
        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)

(c) Publish the output AAR file and the AAR file compiled by Flutter plug-ins to the Maven repository.

Publish the AAR file of the Flutter project output.

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

# 拷贝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 "$_"


# 拷贝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/* "$_"

# 将Flutter库和flutter_app打成AAR文件,同时发布到Ali-maven
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 ../

Publish the AAR file of Flutter plug-ins.

# 将Plugin发布到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

(d) Native projects only need to depend on the AAR files published to the Maven repository.

During the development, the latest AAR files are required in real time. Therefore, the snapshot version is used.

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

ext {
    flutter_aar_version = '6.0.2-SNAPSHOT'
}

dependencies {
    //Flutter主工程依赖:包含基于Flutter开发的功能、Flutter引擎lib
    compile("com.taobao.fleamarket:IdleFishFlutter:${getFlutterAarVersion(project)}") {
        changing = true
    }
    //其他依赖
}

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

1.3.3 Extract the Flutter Library That iOS Depends On

1.3.3.1 Generate Flutter Dependency Files on iOS

Run the flutter build ios command. The Flutter compilation script xcode_backend.sh is finally run to:

Obtain various parameters, such as project_path, target_path, and build_mode, which are defined in Generated.xcconfig.

Delete the App.framework and app.flx files from the Flutter directory.

Compare Flutter/Flutter.framework with Flutter.framework in the FLUTTER_ROOT/bin/cache/artifacts/engine {artifact_variant} directory. If they are different, use Flutter.framework in the directory to overwrite the former.

Obtain the parameters for generating App.framework, including build_dir, local_engine_flag, preview_dart_2_flag, and aot_flags.

Generate the App.framework file and replicate it and the AppFrameworkInfo.plist file to the Flutter directory of the Xcode project.

1.3.3.2 Implement Flutter Dependency Extraction on iOS

Compile the Flutter project to generate the App.framework file.

echo "===清理flutter历史编译==="
./flutter/bin/flutter clean

echo "===重新生成plugin索引==="
./flutter/bin/flutter packages get

echo "===生成App.framework和flutter_assets==="
./flutter/bin/flutter build ios --release

Package all plug-ins as static libraries: Package the plug-ins into a binary library file, and then package the registration portals of the plug-ins into a binary library file.

echo "===生成各个插件的二进制库文件==="
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 "生成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 "合并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 "===生成注册入口的二进制库文件==="
for reg_enter_name in "flutter_plugin_entrance" "flutter_service_register"
do
    echo "生成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 "合并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

Upload these files to the remote repository and generate new tags. Update pod dependencies for native projects.

1.3.4 Continuous Integration Process of the Flutter Hybrid Project

The following problems still exist in daily development:

  • The Flutter project is updated, but the remote dependency library is not updated in time.
  • During version integration, the remote dependency library is not updated, causing the failure to integrate the latest Flutter features in the version.
  • When Flutter is developed in parallel, versioning is confused and the remote library may be overwritten.
  • At least one developer needs to follow up on the publishing, increasing the labor cost.

Therefore, Xianyu has introduced the automatic framework of CI to solve these problems. It reduces the labor cost and manual errors through automation. In addition, it implements auto versioning.

First, before a native project is built each time, the remote library of the Flutter project is automatically compiled and released, without any manual intervention throughout the process. Second, during development and testing, five-digit version numbers in A.B.C.D.X format are used, with the last digit incremented automatically. This ensures that all versions of the Flutter library developed in parallel do not conflict with each other. Finally, in the publishing phase, three-digit (A.B.C) or four-digit (A.B.C.D) version numbers are used to match the app version number, which facilitates tracing of subsequent issues.

Figure 1-18 shows the entire process.

3
Figure 1-18

0 0 0
Share on

XianYu Tech

56 posts | 4 followers

You may also like

Comments