作者:闲鱼技术-然道

1. 引言

在之前的文章《Flutter混合工程改造实践》中,有些同学留言想了解抽取Flutter依赖到远程的一些实现细节,所以本文重点来讲一讲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
复制代码

2. 将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})}
}
复制代码

(2)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)
复制代码

2. 同时将这个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
复制代码

3. 纯粹的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],而这个脚本主要做了下面几件事:
1. 获取各种参数(如project_path,target_path,build_mode等),主要来自于Generated.xcconfig的各种定义。
2. 删除Flutter目录下的App.framework和app.flx。
3. 对比Flutter/Flutter.framework与{artifact_variant}目录下的Flutter.framework,若不相等,则用后者覆盖前者。
4. 获取生成App.framework命令所需参数(build_dir,local_engine_flag,preview_dart_2_flag,aot_flags)。
5. 生成App.framework,并将生成的App.framework和AppFrameworkInfo.plist拷贝到XCode工程的Flutter目录下。

3.3.2 iOS的Flutter依赖抽取实现

iOS的Flutter依赖的抽取步骤如下:
1. 编译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
复制代码

2. 打包各插件为静态库。
这里主要有两步:一是将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 iphoneos
for 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
复制代码

3. 将这些上传到远程仓库,并生成新的Tag。
4. 纯Native项目只需要更新pod依赖即可。

##4. Flutter混合工程的持续集成流程 按上述方式,我们就可以解除Native工程对Flutter工程的直接依赖了,但是在日常开发中还是存在一些问题:
1. Flutter工程更新,远程依赖库更新不及时。
2. 版本集成时,容易忘记更新远程依赖库,导致版本没有集成最新Flutter功能。
3. 同时多条线并行开发Flutter时,版本管理混乱,容易出现远程库被覆盖的问题。
4. 需要最少一名同学持续跟进发布,人工成本较高。
鉴于这些问题,我们引入了我们团队的CI自动化框架,从两方面来解决:
(关于CI自动化框架,我们后续会撰文分享)
一方面是自动化,通过自动化减少人工成本,也减少人为失误。
另一方面是做好版本控制, 自动化的形式来做版本控制。
具体操作:
**首先,**每次需要构建纯粹Native工程前自动完成Flutter工程对应的远程库的编译发布工作,整个过程不需要人工干预。
**其次,**在开发测试阶段,采用五段式的版本号,最后一位自动递增产生,这样就可以保证测试阶段的所有并行开发的Flutter库的版本号不会产生冲突。
**最后,**在发布阶段,采用三段式或四段式的版本号,可以和APP版本号保持一致,便于后续问题追溯。

整个流程如下图所示:

Standalone模式下构建流程

5. 最后

闲鱼技术团队是一只短小精悍的工程技术团队。我们不仅关注于业务问题的有效解决,同时我们在推动打破技术栈分工限制(android/iOS/Html5/Server 编程模型和语言的统一)、计算机视觉技术在移动终端上的前沿实践工作。作为闲鱼技术团队的软件工程师,您有机会去展示您所有的才能和勇气,在整个产品的演进和用户问题解决中证明技术发展是改变生活方式的动力。

简历投递:guicai.gxy@alibaba-inc.com

闲鱼Flutter混合工程持续集成的最佳实践相关推荐

  1. 基于Team Foundation Server 2010 Scrum 1.0与持续集成的最佳实践

    本文适合对Team Foundation Server 2010的部署和管理.模板配置有经验的人员阅读. 在阅读本文之前,需了解Scrum的一些基本知识:其次,需对Visual Studio Scru ...

  2. Now直播iOS Flutter混合工程实践

    作者:腾讯NOW直播 - mirageqliu(刘强) 前言 腾讯Now直播iOS App在近期版本嵌入了Flutter功能开发模块,本文旨在讨论我们Now直播终端团队对iOS Flutter混合开发 ...

  3. 重磅发布 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开

    简介: 闲鱼是国内最早接触使用 Flutter 的团队,经过多次研讨验证并大规模上线,在App性能.稳定性.开发效率上收益甚多.现在,闲鱼将这个过程中的一手实践知识和技术沉淀,整理成册 --<F ...

  4. 《Flutter in action》开放下载!闲鱼Flutter企业级实践精选

    复制链接到浏览器 https://yq.aliyun.com/download/3792?utm_content=g_1000081730 下载. 闲鱼是国内最早使用Flutter的团队,也是Flut ...

  5. 从原生到黑科技:闲鱼 Flutter 图片优化经历了什么?

    简介:阿里妹导读:图片加载是 APP 最常见也最基本的功能,也是影响用户体验的因素之一.在看似简单的图片加载背后却隐藏着很多技术难题.本文介绍闲鱼技术团队在 Flutter 图片优化上所做的尝试,分享 ...

  6. 闲鱼Flutter图片框架架构演进(超详细)

    那些年 早在闲鱼使用Flutter之初,图片就是我们核心关注和重点优化的功能.图片展示体验的好坏会对闲鱼用户的使用体验产生巨大影响.你们是否也曾遇到过: 图片加载内存占用过多? 使用Flutter以后 ...

  7. 闲鱼Flutter互动引擎系列——整体设计篇

    什么是Candy引擎 Candy引擎是闲鱼技术团队设计开发的一款: APP嵌入式的.轻量级的.易于开发.性能稳定的互动引擎: 绘制系统高度融合Flutter体系,游戏场景和Flutter UI支持无缝 ...

  8. 不用 H5,闲鱼 Flutter 如何玩转小游戏?

    简介: 最近APP游戏化成为了一个新的风口,把在游戏中一些好玩的.能吸引用户的娱乐方式或场景应用在应用当中,以达到增加用户粘性,提升DAU的效果,成本较低.同时在一些需要对用户有引导性的场景,游戏化还 ...

  9. 骨骼动画实现秘密!闲鱼 Flutter 互动引擎告诉你

    简介: 代表骨骼动画是一种通过控制骨骼参数来实现多帧动画的方式,区别于 GIF 的不连贯和序列帧的体积大,骨骼动画有较好的灵活性和流畅性.目前骨骼动画已经被大规模地在游戏和动画中所使用,大有一种取代帧 ...

  10. Archsummit 2019重磅分享|闲鱼Flutter&FaaS云端一体化架构

    作者:闲鱼技术-国有 讲师介绍 国有,闲鱼架构团队负责人.在7月13号落幕的2019年Archsummit峰会上就近一年来闲鱼在Flutter&FaaS一体化项目上的探索和实践进行了分享. 传 ...

最新文章

  1. WindDbug应用
  2. boost::count相关的测试程序
  3. EBS模块介绍和概念解释
  4. 扫地机器人返充原理_扫地机器人全解析
  5. C++或C 实现AES ECB模式加密解密,支持官方验证
  6. java借口案例实现_java实现接口的典型案例
  7. Python 二叉树实现
  8. 上周ASP.NET英文技术文章推荐[05/06 - 05/12]
  9. IT管理人才必备的十大能力(转)
  10. 连续七天熬夜3D建模师终于出手,让老板增加薪资待遇,分享使用3D建模软件的6个行业
  11. python metaclass应用
  12. POI 读取 Excel 文件(2003版本与2007版本的差异之处)
  13. 1、安装Lync Server 2013前的准备工作
  14. maven依赖和传递
  15. 【知识图谱】关系抽取
  16. linux 安装sz,linux中rz、sz命令的安装配置方法
  17. matlab命令窗口中常用快捷键命令(一)
  18. led时间代码html,LED旋转时钟制作(有源代码)
  19. AndroidStudio导入Bmob后端云一系列错误
  20. 英语单词辨析(同类单词)

热门文章

  1. qt调用import sys库_【开源库】使用Qt.py进行开发
  2. 机器学习和ai哪个好_AI可以使您成为更好的运动员吗? 使用机器学习分析网球发球和罚球...
  3. Mybatis Sql 大于号小于号不兼容
  4. axure树与表格结合_语言开发7:语言迟缓孩子,家庭日常训练,干货满满!【附:表格及图片】...
  5. matlab扫雷代码及运用解释,MATLAB_百科
  6. 新建连接mysql编码选择_redhat5.432位安装mysql5.6.17数据库及创建数据库实例、配置编码...
  7. php批量导入txt文件,如何把一个文本文件中的十几万数据快速的导入到sql表中
  8. java map sort_Map 按值排序 (Map sort by value) – Java | 学步园
  9. 一些认识或对不清楚知识的猜想
  10. 软考 2015年下半年卷 错题知识点记录