Flutter 混合工程持续集成实践

文章目录

      • 1. 引言
      • 2. 思考
      • 3. 实现
        • 3.1 Native工程依赖的Flutter分析
        • 3.2 Android 依赖的 Flutter 库抽取
          • 3.2.1 Android 中 Flutter 编译任务分析
          • 3.2.2 Android 的 Flutter 依赖抽取实现
        • 3.3 iOS 依赖的 Flutter 库的抽取
          • 3.3.1 iOS 中 Flutter 依赖文件如何产生
          • 3.3.2 iOS 的 Flutter 依赖抽取实现
      • 4. Flutter 混合工程的持续集成流程

1. 引言


本文重点来讲一讲 Flutter 混合工程中的 Flutter 直接依赖解除的一些具体实现。

2. 思考


Flutter 和 Native 混合开发的模式,存在一部分同学只做 Native 开发,并不熟悉 Flutter 技术。

  1. 如果直接采用 Flutter 工程结构来作为日常开发,那这部分 Native 开发同学也需要配置 Flutter 环境,了解 Flutter 一些技术,成本比较大。

  2. 阿里集团的构建系统目前并不支持直接构建 Flutter 项目,这个也要求我们解除 Native 工程对 Flutter 的直接依赖。

鉴于这两点原因,我们希望可以设计一个 Flutter 依赖抽取模块,可以将 Flutter 的依赖抽取为一个 Flutter 依赖库发布到远程,供纯 Native 工程引用。如下图所示:
这里写图片描述

Flutter直接依赖解除

3. 实现

3.1 Native工程依赖的Flutter分析

我们分析 Flutter 工程,会发现 Native 工程对 Flutter 工程的依赖主要有三部分:

  1. Flutter 库和引擎:Flutter 的 Framework 库和引擎库;

  2. Flutter 工程:我们自己实现的 Flutter 模块功能,主要为 Flutter 工程下 lib 目录下的 dart 代码实现的这部分功能;

  3. 自己实现的 Flutter Plugin:我们自己实现的 Flutter Plugin。

我们解开 Android 和 iOS 的 APP 文件,发现 Flutter 依赖的主要文件如下图所示:

这里写图片描述

Flutter依赖的文件(Flutter产物)

其中,Android 的 Flutter 依赖的文件:

  1. Flutter库和引擎:icudtl.dat、libflutter.so、还有一些 class 文件。这些都封装在 flutter.jar 中,这个 jar 文件位于 Flutter 库目录下的 flutter/bin/cache/artifacts/engine 下;

  2. Flutter工程产物:isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr、flutter_assets;

  3. Flutter Plugin:各个plugin编译出来的aar文件。

其中:

  • isolate_snapshot_data:应用程序数据段;
  • isolate_snapshot_instr:应用程序指令段;
  • vm_snapshot_data:VM 虚拟机数据段;
  • vm_snapshot_instr:VM 虚拟机指令段;

iOS 的 Flutter 依赖的文件:

  1. Flutter库和引擎:Flutter.framework;

  2. Flutter工程的产物:App.framework;

  3. Flutter Plugin:编译出来的各种 plugin 的 framework,图中的其他 framework;

那我们只需要将这三部分的编译结果抽取出来,打包成一个 SDK 依赖的形式提供给 Native 工程,就可以解除 Native 工程对 Flutter 工程的直接依赖。

3.2 Android 依赖的 Flutter 库抽取
3.2.1 Android 中 Flutter 编译任务分析

Flutter 工程的 Android 打包,其实只是在 Android 的 Gradle 任务中插入了一个 flutter.gradle 的任务,而这个 flutter.gradle 主要做了三件事:(这个文件可以在 Flutter 库中的 flutter/packages/flutter_tools/gradle 目录下能找到)。

  1. 增加 flutter.jar 的依赖。

  2. 插入 Flutter Plugin 的编译依赖。

  3. 插入 Flutter 工程的编译任务,最终将产物(两个 isolaate_snapshot 文件、两个 vm_snapshot 文件和 flutter_assets 文件夹)拷贝到 mergeAssets.outputDir,最终 merge 到 APK 的 assets 目录下。

3.2.2 Android 的 Flutter 依赖抽取实现

弄明白 Flutter 工程的 Android 编译产物之后,因此我们对 Android 的 Flutter 依赖抽取步骤如下:

  1. 编译 Flutter 工程

这部分主要工作是编译 Flutter 的 dart 和资源部分,可以用 AOT 和 Bundle 命令编译。

echo "Clean old build"
find . -d -name "build" | xargs rm -rf
./flutter/bin/flutter cleanecho "Get packages"
./flutter/bin/flutter packages getecho "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
  1. 将 flutter.jar 和 Flutter 工程的产物打包成一个 aar

这边部分的主要工作是将 flutter.jar 和第 1 步编译的产物封装成一个 aar。

  1. 添加 flutter.jar 依赖
project.android.buildTypes.each {addFlutterJarImplementationDependency(project, releaseFlutterJar)
}
project.android.buildTypes.whenObjectAdded {addFlutterJarImplementationDependency(project, releaseFlutterJar)
}
private static void addFlutterJarImplementationDependency(Project project, releaseFlutterJar) {project.dependencies {String configurationif (project.getConfigurations().findByName("implementation")) {configuration = "implementation"} else {configuration = "compile"}add(configuration, project.files {releaseFlutterJar})}
}
  1. Merge Flutter 的产物到 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 mergeFlutterMD5Assetsfrom (allertAsset){include "flutter_assets/**" // the working dir and its filesinclude "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)
  1. 同时将这个 aar 和 Flutter Plugin 编译出来的 aar 一起发布到 maven 仓库。
  1. 发布 Flutter 工程产物打包的 aar
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 同时publish到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 ../2) 发布 Flutter Plugin 的 aar# 将plugin发布到Ali-maven
echo "Start publish flutter-plugins"for line in $(cat .flutter-plugins)
doplugin_name=${line%%=*}echo 'Build and publish plugin:' ${plugin_name}cd androidif [ -n "$1" ]then./gradlew :${plugin_name}:clean :${plugin_name}:publish -PAAR_VERSION=$1else./gradlew :${plugin_name}:clean :${plugin_name}:publishficd ../
done
  1. 纯粹的 Native 项目只需要 compile 我们发布到 maven 的 aar 即可。

平时开发阶段,我们需要实时能依赖最新的 aar,所以我们采用 SNAPSHOT 版本。

configurations.all {resolutionStrategy.cacheChangingModulesFor 0, 'seconds'}
ext {flutter_aar_version = '6.0.2-SNAPSHOT'
}dependencies {//flutter主工程依赖:包含基于flutter开发的功能、flutter引擎libcompile("com.taobao.fleamarket:IdleFishFlutter:${getFlutterAarVersion(project)}") {changing = true}//...其他依赖
}static def getFlutterAarVersion(project) {def resultVersion = project.flutter_aar_versionif (project.hasProperty('FLUTTER_AAR_VERSION')) {resultVersion = project.FLUTTER_AAR_VERSION}return resultVersion
}
3.3 iOS 依赖的 Flutter 库的抽取
3.3.1 iOS 中 Flutter 依赖文件如何产生

执行编译命令 “flutter build ios”,最终会执行 Flutter 的编译脚本 xcode_backend.sh,而这个脚本主要做了下面几件事:

  • 获取各种参数(如 project_path,target_path,build_mode 等),主要来自于
    Generated.xcconfig 的各种定义;
  • 删除 Flutter 目录下的 App.framework 和 app.flx;
  • 对比 Flutter/Flutter.framework 与
    F L U T T E R R O O T / b i n / c a c h e / a r t i f a c t s / e n g i n e / {FLUTTER_ROOT}/bin/cache/artifacts/engine/ FLUTTERROOT/bin/cache/artifacts/engine/{artifact_variant} 目录下的
    Flutter.framework ,若不相等,则用后者覆盖前者;
  • 获取生成 App.framework
    命令所需参数(build_dir,local_engine_flag,preview_dart_2_flag,aot_flags);
  • 生成 App.framework,并将生成的 App.framework 和 AppFrameworkInfo.plist 拷贝到
    XCode 工程的 Flutter 目录下。
3.3.2 iOS 的 Flutter 依赖抽取实现

iOS 的 Flutter 依赖的抽取步骤如下:

  • 编译Flutter工程生成App.framework
echo "===清理flutter历史编译==="
./flutter/bin/flutter cleanecho "===重新生成plugin索引==="
./flutter/bin/flutter packages getecho "===生成App.framework和flutter_assets==="
./flutter/bin/flutter build ios --release
  • 打包各插件为静态库

这里主要有两步:一是将 plugin 打成二进制文件,二是将 plugin 的注册入口打成二进制文件。

echo "===生成各个plugin的二进制库文件==="
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 iphoneosfor plugin_name in ${plugin_arr}
doecho "生成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 -quietecho "合并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"
doneecho "===生成注册入口的二进制库文件==="
for reg_enter_name in "flutter_plugin_entrance" "flutter_service_register"
doecho "生成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 iphonesimulatorecho "合并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
  • 将这些上传到远程仓库,并生成新的Tag。
  • 纯Native项目只需要更新pod依赖即可。

4. Flutter 混合工程的持续集成流程


按上述方式,我们就可以解除 Native 工程对 Flutter 工程的直接依赖了,但是在日常开发中还是存在一些问题:

  • Flutter 工程更新,远程依赖库更新不及时;
  • 版本集成时,容易忘记更新远程依赖库,导致版本没有集成最新 Flutter 功能;
  • 同时多条线并行开发 Flutter 时,版本管理混乱,容易出现远程库被覆盖的问题;
  • 需要最少一名同学持续跟进发布,人工成本较高;

鉴于这些问题,我们引入了我们团队的 CI 自动化框架,从两方面来解决:一方面是自动化,通过自动化减少人工成本,也减少人为失误;另一方面是做好版本控制, 自动化的形式来做版本控制。

具体操作:

  • 首先,每次需要构建纯粹 Native 工程前自动完成 Flutter 工程对应的远程库的编译发布工作,整个过程不需要人工干预;
  • 其次,在开发测试阶段,采用五段式的版本号,最后一位自动递增产生,这样就可以保证测试阶段的所有并行开发的 Flutter
    库的版本号不会产生冲突;
  • 最后,在发布阶段,采用三段式或四段式的版本号,可以和 APP 版本号保持一致,便于后续问题追溯。

整个流程如下图所示:
这里写图片描述


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部