浅谈Flutter构建(1)

可以用下面这张图来总结上面的四种模式:

图片来源于 flutters-compilation-patterns

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四种模式下的产物大小和启动速度比较:

图片来源于 exploring-flutter-in-android

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Flutter 构建过程

下面的分析都是基于 v1.5.4-hotfix.2

执行 flutter build apk 时,默认会生成 release APK,实际是执行的 sdk/bin/flutter 命令,参数为 build apk:

DART_SDK_PATH=“ F L U T T E R R O O T / b i n / c a c h e / d a r t − s d k " D A R T = " FLUTTER_ROOT/bin/cache/dart-sdk" DART=" FLUTTERROOT/bin/cache/dartsdk"DART="DART_SDK_PATH/bin/dart”
SNAPSHOT_PATH=“ F L U T T E R R O O T / b i n / c a c h e / f l u t t e r t o o l s . s n a p s h o t " " FLUTTER_ROOT/bin/cache/flutter_tools.snapshot" " FLUTTERROOT/bin/cache/fluttertools.snapshot""DART” F L U T T E R T O O L A R G S " FLUTTER_TOOL_ARGS " FLUTTERTOOLARGS"SNAPSHOT_PATH" “$@”

上面的命令完整应该为:dart flutter_tools.snapshot args,snapshot 文件我们上面提过,是 Dart 的一种代码集合,类似 Java 中 Jar 包。

flutter_tools 的源码位于 sdk/packages/flutter_tools/bin 下的 flutter_tools.dart,跟 Java 一样,这里也有个 main() 函数,其中调用的是 executable.main() 函数。

在 main 函数中,会注册多个命令处理器,比如:

  • DoctorCommand 对应 flutter doctor 命令
  • CleanCommand 对应 flutter clean 命令
  • 等等

我们这次要研究的目标是 BuildCommand,它里面又包含了以下子命令处理器:

  • BuildApkCommand 对应 flutter build apk 命令
  • BuildAppBundleCommand 对应 flutter build bundle 命令
  • BuildAotCommand 对应 flutter build aot 命令
  • 等等
Build APK

首先我们要理清 build apk 或者 build ios,和 build bundle、build aot 之间的关系。这里我们以 build apk 为例:

当执行 flutter build apk 时,最终会调用到 gradle.dart 中的 _buildGradleProjectV2() 方法,在这里最后也是调用 gradle 执行 assemble task。而在项目中的 android/app/build.gradle 中可以看到:

apply from: “$flutterRoot/packages/flutter_tools/gradle/flutter.gradle”

也就是说,会在 flutter.gradle 这里去插入一些编译 Flutter 产物的脚本。

flutter.gradle 是通过插件的形式去实现的,这个插件命名为 FlutterPlugin。这个插件的主要作用有一些几点:

  • 除了默认的 debug 和 release 之外,新增了 profile、dynamicProfile、dynamicRelease 这三种 buildTypes:

project.android.buildTypes {
profile {
initWith debug
if (it.hasProperty(‘matchingFallbacks’)) {
matchingFallbacks = [‘debug’, ‘release’]
}
}
dynamicProfile {
initWith debug
if (it.hasProperty(‘matchingFallbacks’)) {
matchingFallbacks = [‘debug’, ‘release’]
}
}
dynamicRelease {
initWith debug
if (it.hasProperty(‘matchingFallbacks’)) {
matchingFallbacks = [‘debug’, ‘release’]
}
}
}

  • 动态添加 flutter.jar 依赖:

private void addFlutterJarApiDependency(Project project, buildType, Task flutterX86JarTask) {
project.dependencies {
String configuration;
if (project.getConfigurations().findByName(“api”)) {
configuration = buildType.name + “Api”;
} else {
configuration = buildType.name + “Compile”;
}
add(configuration, project.files {
String buildMode = buildModeFor(buildType)
if (buildMode == “debug”) {
[flutterX86JarTask, debugFlutterJar]
} else if (buildMode == “profile”) {
profileFlutterJar
} else if (buildMode == “dynamicProfile”) {
dynamicProfileFlutterJar
} else if (buildMode == “dynamicRelease”) {
dynamicReleaseFlutterJar
} else {
releaseFlutterJar
}
})
}
}

  • 动态添加第三方插件依赖:

project.dependencies {
if (project.getConfigurations().findByName(“implementation”)) {
implementation pluginProject
} else {
compile pluginProject
}

  • 在 assemble task 中添加一个 FlutterTask,这个 task 非常重要,这里会去生成 Flutter 所需要的产物:

首先,当 buildType 是 profile 或 release 时,会执行 flutter build aot

if (buildMode == “profile” || buildMode == “release”) {
project.exec {
executable flutterExecutable.absolutePath
workingDir sourceDir
if (localEngine != null) {
args “–local-engine”, localEngine
args “–local-engine-src-path”, localEngineSrcPath
}
args “build”, “aot”
args “–suppress-analytics”
args “–quiet”
args “–target”, targetPath
args “–target-platform”, “android-arm”
args “–output-dir”, “KaTeX parse error: Expected '}', got 'EOF' at end of input: …end-options", "{extraFrontEndOptions}”
}
if (extraGenSnapshotOptions != null) {
args “–extra-gen-snapshot-options”, “KaTeX parse error: Expected 'EOF', got '}' at position 37: …ons}" }̲ …{targetPlatform}”
}
args “–${buildMode}”
}
}

其次,执行 flutter build bundle 命令,当 buildType 为 profile 或 release 时,添加额外的 --precompiled 选项。

project.exec {
executable flutterExecutable.absolutePath
workingDir sourceDir
if (localEngine != null) {
args “–local-engine”, localEngine
args “–local-engine-src-path”, localEngineSrcPath
}
args “build”, “bundle”
args “–suppress-analytics”
args “–target”, targetPath
if (verbose) {
args “–verbose”
}
if (fileSystemRoots != null) {
for (root in fileSystemRoots) {
args “–filesystem-root”, root
}
}
if (fileSystemScheme != null) {
args “–filesystem-scheme”, fileSystemScheme
}
if (trackWidgetCreation) {
args “–track-widget-creation”
}
if (compilationTraceFilePath != null) {
args “–compilation-trace-file”, compilationTraceFilePath
}
if (createPatch) {
args “–patch”
args “–build-number”, project.android.defaultConfig.versionCode
if (buildNumber != null) {
assert buildNumber == project.android.defaultConfig.versionCode
}
}
if (baselineDir != null) {
args “–baseline-dir”, baselineDir
}
if (extraFrontEndOptions != null) {
args “–extra-front-end-options”, “KaTeX parse error: Expected 'EOF', got '}' at position 40: … }̲ …{extraGenSnapshotOptions}”
}
if (targetPlatform != null) {
args “–target-platform”, “KaTeX parse error: Expected 'EOF', got '}' at position 48: … }̲ …{intermediateDir}/snapshot_blob.bin.d”
}
args “–asset-dir”, “${intermediateDir}/flutter_assets”
if (buildMode == “debug”) {
args “–debug”
}
if (buildMode == “profile” || buildMode == “dynamicProfile”) {
args “–profile”
}
if (buildMode == “release” || buildMode == “dynamicRelease”) {
args “–release”
}
if (buildMode == “dynamicProfile” || buildMode == “dynamicRelease”) {
args “–dynamic”
}
}

稍微总结下,当 buildType 为 debug 时,只需要执行:

flutter build bundle

而当 buildType 为 release 时,需要执行两个命令:

flutter build aot
flutter build bundle --precompiled

Build AOT

当执行 flutter build aot 时,相关的逻辑在 BuildAotCommand 中,它主要分成两个步骤:

  1. 编译 kernel。kernel 指 Dart 的一种中间语言,更多资料可以阅读 Kernel-Documentation。最终会调用到 compile.dart 中的 KernelCompiler.compile() 方法:

final List command = [
engineDartPath,
frontendServer,
‘–sdk-root’,
sdkRoot,
‘–strong’,
‘–target=$targetModel’,
];
if (trackWidgetCreation)
command.add(‘–track-widget-creation’);
if (!linkPlatformKernelIn)
command.add(‘–no-link-platform’);
if (aot) {
command.add(‘–aot’);
command.add(‘–tfa’);
}
if (targetProductVm) {
command.add(‘-Ddart.vm.product=true’);
}
if (incrementalCompilerByteStorePath != null) {
command.add(‘–incremental’);
}
Uri mainUri;
if (packagesPath != null) {
command.addAll([‘–packages’, packagesPath]);
mainUri = PackageUriMapper.findUri(mainPath, packagesPath, fileSystemScheme, fileSystemRoots);
}
if (outputFilePath != null) {
command.addAll([‘–output-dill’, outputFilePath]);
}
if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) {
command.addAll([‘–depfile’, depFilePath]);
}
if (fileSystemRoots != null) {
for (String root in fileSystemRoots) {
command.addAll([‘–filesystem-root’, root]);
}
}
if (fileSystemScheme != null) {
command.addAll([‘–filesystem-scheme’, fileSystemScheme]);
}
if (initializeFromDill != null) {
command.addAll([‘–initialize-from-dill’, initializeFromDill]);
}

if (extraFrontEndOptions != null)
command.addAll(extraFrontEndOptions);

command.add(mainUri?.toString() ?? mainPath);

printTrace(command.join(’ '));
final Process server = await processManager
.start(command)
.catchError((dynamic error, StackTrace stack) {
printError(‘Failed to start frontend server $error, $stack’);
});

engineDart 指向 Dart SDK 目录,上面代码执行的最终命令可以简化为:

dart frontend_server.dart.snapshot --output-dill app.dill packages:main.dart

app.dill 这里面其实就包含了我们的业务代码了,可以使用 strings app.dill 查看:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 生成 snpshot。相关的代码位于 base/build.dart 中的 AOTSnapshotter.build() 函数中,有两种模式:app-aot-assemble 和 app-aot-blobs。
app-aot-assemble

在执行 aot 命令时,增加 --build-shared-library 选项,完整命令如下:

flutter build aot --build-shared-library

iOS 只能使用这种模式,在 Flutter SDK 1.7.x 之后,这个也是 Android 的默认选项。

// buildSharedLibrary is ignored for iOS builds.
if (platform == TargetPlatform.ios)
buildSharedLibrary = false;

if (buildSharedLibrary && androidSdk.ndk == null) {
// 需要有 NDK 环境
final String explanation = AndroidNdk.explainMissingNdk(androidSdk.directory);
printError(
‘Could not find NDK in Android SDK at ${androidSdk.directory}:\n’
‘\n’
’ $explanation\n’
‘\n’
‘Unable to build with --build-shared-library\n’
‘To install the NDK, see instructions at https://developer.android.com/ndk/guides/’
);
return 1;
}

这种模式下,会将产物编译为二进制文件,在 iOS 上为 App.framework,Android 上则为 app.so。

// Assembly AOT snapshot.
outputPaths.add(assembly);
genSnapshotArgs.add(‘–snapshot_kind=app-aot-assembly’);
genSnapshotArgs.add(‘–assembly=$assembly’);

app-aot-blobs

当使用这种模式时,会生成四个产物,分别是:

instr 全称是 Instructions

了解更多:Flutter-engine-operation-in-AOT-Mode

  • isolate_snapshot_data:

表示 isolate 堆存储区的初始状态和特定的信息。和 vm_snapshot_data 配合,更快的启动 Dart VM。

  • isolate_snapshot_instr:

包含由 Dart isolate 执行的 AOT 代码。

  • vm_snapshot_data:

表示 isolates 之间的共享的 Dart 堆存储区的初始状态,用于更快的启动 Dart VM。

  • vm_snapshot_instr:

包含 VM 中所有的 isolates 之间共享的常见例程的指令

isolate_snapshot_data 和 isolate_snapshot_instr 跟业务相关,而 vm_snapshot_data 和 vm_snapshot_instr 则是跟 VM 相关,无关业务。

我们可以使用 strings 查看下 isolate_snapshot_data 中内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小结

上面两种模式相关的代码如下:

final String assembly = fs.path.join(outputDir.path, ‘snapshot_assembly.S’);
if (buildSharedLibrary || platform == TargetPlatform.ios) {
// Assembly AOT snapshot.
outputPaths.add(assembly);
genSnapshotArgs.add(‘–snapshot_kind=app-aot-assembly’);
genSnapshotArgs.add(‘–assembly=KaTeX parse error: Expected 'EOF', got '}' at position 89: … }̲ else { …vmSnapshotData’,
‘–isolate_snapshot_data= i s o l a t e S n a p s h o t D a t a ′ , ′ − − v m s n a p s h o t i n s t r u c t i o n s = isolateSnapshotData', '--vm_snapshot_instructions= isolateSnapshotData,vmsnapshotinstructions=vmSnapshotInstructions’,

‘–isolate_snapshot_instructions=$isolateSnapshotInstructions’,
]);
}

从执行速度来看,app-aot-assemble 是要快于 app-aot-blobs 的,因为它不需要 Dart VM 环境,只需要 Dart Runtime 即可,而 snapshots 文件是需要 Dart VM 去加载执行的。但使用 snapshots 使得动态执行代码变成可能。

iOS 默认使用 app-aot-assemble 模式,更多是 App Store 本身的限制:

引用于flutters-compilation-patterns

App Store does not allow dispatch binary executable code.

而 Android 默认使用 app-aot-blobs 模式,可能更多是从性能方面考虑,必须调用从 so 文件中调用 native 函数,需要用 JNI,有性能损耗,而且调用也比较麻烦,不过在 Flutter SDK 1.7.x 已经改成 app-aot-assemble 模式。

Build Bundle

当执行 flutter build bundle 时,相关的代码逻辑在 BuildBundleCommand 中,build bundle 主要做两件事:第一,如果没有添加 --precompiled 选项时,会先编译 kernel;第二,生成 assets(图片、字体等)。

  1. 编译 kernel。这个步骤和 build aot 的第一个步骤是一样的,最终会生成 app.dill,这是业务代码编译后的产物。同时,这个会创建一个 kernelContent,这个在第二个步骤会讲到:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

[外链图片转存中…(img-92IbuT77-1713677544509)]

【延伸Android必备知识点】

[外链图片转存中…(img-snUT2e5T-1713677544510)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值