project.dependencies.add(configuration, dependency, config)
}
上面这段脚本的本质就是给 Flutter 项目自动添加编译依赖,这个依赖本质也是 maven 仓库的,很像我们自己编写 gradle 中添加的 okhttp 等依赖,没啥区别。譬如我们创建的 demo 项目导入 Android Studio 后自动 sync 的 dependencies 依赖如下:
接下来我们把重心放回步骤 3(addFlutterTasks),这才是我们整个 Flutter app 编译的重点,也是最复杂的部分,如下:
private void addFlutterTasks(Project project) {
//gradle项目配置评估失败则返回,常规操作,忽略
if (project.state.failure) {
return
}
//1、一堆属性获取与赋值操作
String[] fileSystemRootsValue = null
if (project.hasProperty(‘filesystem-roots’)) {
fileSystemRootsValue = project.property(‘filesystem-roots’).split(‘\|’)
}
String fileSystemSchemeValue = null
if (project.hasProperty(‘filesystem-scheme’)) {
fileSystemSchemeValue = project.property(‘filesystem-scheme’)
}
Boolean trackWidgetCreationValue = true
if (project.hasProperty(‘track-widget-creation’)) {
trackWidgetCreationValue = project.property(‘track-widget-creation’).toBoolean()
}
String extraFrontEndOptionsValue = null
if (project.hasProperty(‘extra-front-end-options’)) {
extraFrontEndOptionsValue = project.property(‘extra-front-end-options’)
}
String extraGenSnapshotOptionsValue = null
if (project.hasProperty(‘extra-gen-snapshot-options’)) {
extraGenSnapshotOptionsValue = project.property(‘extra-gen-snapshot-options’)
}
String splitDebugInfoValue = null
if (project.hasProperty(‘split-debug-info’)) {
splitDebugInfoValue = project.property(‘split-debug-info’)
}
Boolean dartObfuscationValue = false
if (project.hasProperty(‘dart-obfuscation’)) {
dartObfuscationValue = project.property(‘dart-obfuscation’).toBoolean();
}
Boolean treeShakeIconsOptionsValue = false
if (project.hasProperty(‘tree-shake-icons’)) {
treeShakeIconsOptionsValue = project.property(‘tree-shake-icons’).toBoolean()
}
String dartDefinesValue = null
if (project.hasProperty(‘dart-defines’)) {
dartDefinesValue = project.property(‘dart-defines’)
}
String bundleSkSLPathValue;
if (project.hasProperty(‘bundle-sksl-path’)) {
bundleSkSLPathValue = project.property(‘bundle-sksl-path’)
}
String performanceMeasurementFileValue;
if (project.hasProperty(‘performance-measurement-file’)) {
performanceMeasurementFileValue = project.property(‘performance-measurement-file’)
}
String codeSizeDirectoryValue;
if (project.hasProperty(‘code-size-directory’)) {
codeSizeDirectoryValue = project.property(‘code-size-directory’)
}
Boolean deferredComponentsValue = false
if (project.hasProperty(‘deferred-components’)) {
deferredComponentsValue = project.property(‘deferred-components’).toBoolean()
}
Boolean validateDeferredComponentsValue = true
if (project.hasProperty(‘validate-deferred-components’)) {
validateDeferredComponentsValue = project.property(‘validate-deferred-components’).toBoolean()
}
def targetPlatforms = getTargetPlatforms()
…
}
可以看到,addFlutterTasks 方法的第一部分比较简单,基本都是从 Project 中读取各自配置属性供后续步骤使用。所以我们接着继续看 addFlutterTasks 这个方法步骤 1 之后的部分:
private void addFlutterTasks(Project project) {
//一堆属性获取与赋值操作
//…
//1、定义 addFlutterDeps 箭头函数,参数variant为标准构建对应的构建类型
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
//2、常规操作:如果是构建多个变体apk模式就处理vc问题
variant.outputs.each { output ->
//由于GP商店不允许同一个应用的多个APK全都具有相同的版本信息,因此在上传到Play商店之前,您需要确保每个APK都有自己唯一的versionCode,这里就是做这个事情的。
//具体可以看官方文档 https://developer.android.com/studio/build/configure-apk-splits
def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride =
abiVersionCode * 1000 + variant.versionCode
}
}
}
//3、获取编译类型,variantBuildMode值为debug、profile、release之一
String variantBuildMode = buildModeFor(variant.buildType)
//4、依据参数生成一个task名字,譬如这里的compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease
String taskName = toCammelCase([“compile”, FLUTTER_BUILD_PREFIX, variant.name])
//5、给当前project创建compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease Task
//实现为FlutterTask,主要用来编译Flutter代码,这个task稍后单独分析
FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
//各种task属性赋值操作,基本都来自上面的属性获取或者匹配分析
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode variantBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
//默认dart入口lib/main.dart、可以通过target属性自定义指向
targetPath getFlutterTarget()
verbose isVerbose()
fastStart isFastStart()
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
targetPlatformValues = targetPlatforms
sourceDir getFlutterSourceDirectory()
//学到一个小技能,原来中间API是AndroidProject.FD_INTERMEDIATES,这也是flutter中间产物目录
intermediateDir project.file(“ p r o j e c t . b u i l d D i r / {project.buildDir}/ project.buildDir/{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/”)
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
splitDebugInfo splitDebugInfoValue
treeShakeIcons treeShakeIconsOptionsValue
dartObfuscation dartObfuscationValue
dartDefines dartDefinesValue
bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue
deferredComponents deferredComponentsValue
validateDeferredComponents validateDeferredComponentsValue
//最后做一波权限相关处理
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine(‘cmd’, ‘/c’, “attrib -r ${assetsDirectory}/* /s”)
} else {
commandLine(‘chmod’, ‘-R’, ‘u+w’, assetsDirectory)
}
}
}
}
//项目构建中间产物的文件,也就是根目录下build/intermediates/flutter/debug/libs.jar文件
File libJar = project.file(“ p r o j e c t . b u i l d D i r / {project.buildDir}/ project.buildDir/{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar”)
//6、创建packLibsFlutterBuildProfile、packLibsFlutterBuildDebug、packLibsFlutterBuildRelease任务,主要是产物的复制挪位置操作,Jar 类型的 task
//作用就是把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成build/intermediates/flutter/debug/libs.jar
Task packFlutterAppAotTask = project.tasks.create(name: “packLibs F L U T T E R B U I L D P R E F I X {FLUTTER_BUILD_PREFIX} FLUTTERBUILDPREFIX{variant.name.capitalize()}”, type: Jar) {
//目标路径为build/intermediates/flutter/debug目录
destinationDir libJar.parentFile
//文件名为libs.jar
archiveName libJar.name
//依赖前面步骤5定义的compileFlutterBuildDebug,也就是说,这个task基本作用是产物处理
dependsOn compileTask
//targetPlatforms取值为android-arm、android-arm64、android-x86、android-x64
targetPlatforms.each { targetPlatform ->
//abi取值为armeabi-v7a、arm64-v8a、x86、x86_64
String abi = PLATFORM_ARCH_MAP[targetPlatform]
//数据来源来自步骤5的compileFlutterBuildDebug任务中间产物目录
//即把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成一个build/intermediates/flutter/debug/libs.jar文件
from(“ c o m p i l e T a s k . i n t e r m e d i a t e D i r / {compileTask.intermediateDir}/ compileTask.intermediateDir/{abi}”) {
include “*.so”
// Move app.so
to lib/<abi>/libapp.so
rename { String filename ->
return “lib/ a b i / l i b {abi}/lib abi/lib{filename}”
}
}
}
}
//前面有介绍过addApiDependencies作用,把 packFlutterAppAotTask 产物加到依赖项里面参与编译
//类似implementation files(‘libs.jar’),然后里面的so会在项目执行标准mergeDebugNativeLibs task时打包进标准lib目录
addApiDependencies(project, variant.name, project.files {
packFlutterAppAotTask
})
// 当构建有is-plugin属性时则编译aar
boolean isBuildingAar = project.hasProperty(‘is-plugin’)
//7、当是Flutter Module方式,即Flutter以aar作为已存在native安卓项目依赖时才有这些:flutter:模块依赖,否则没有这些task
//可以参见新建的FlutterModule中.android/include_flutter.groovy中gradle.project(“:flutter”).projectDir实现
Task packageAssets = project.tasks.findByPath(“:flutter:package${variant.name.capitalize()}Assets”)
Task cleanPackageAssets = project.tasks.findByPath(“:flutter:cleanPackage${variant.name.capitalize()}Assets”)
//判断是否为FlutterModule依赖
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
//8、新建copyFlutterAssetsDebug task,目的就是copy产物,也就是assets归档
//常规merge中间产物类似,不再过多解释,就是把步骤5 task产物的assets目录在mergeAssets时复制到主包中间产物目录
Task copyFlutterAssetsTask = project.tasks.create(
name: “copyFlutterAssets${variant.name.capitalize()}”,
type: Copy,
) {
dependsOn compileTask
with compileTask.assets
if (isUsedAsSubproject) {
dependsOn packageAssets
dependsOn cleanPackageAssets
into packageAssets.outputDir
return
}
// variant.mergeAssets
will be removed at the end of 2019.
def mergeAssets = variant.hasProperty(“mergeAssetsProvider”) ?
variant.mergeAssetsProvider.get() : variant.mergeAssets
dependsOn mergeAssets
dependsOn “clean${mergeAssets.name.capitalize()}”
mergeAssets.mustRunAfter(“clean${mergeAssets.name.capitalize()}”)
into mergeAssets.outputDir
}
if (!isUsedAsSubproject) {
def variantOutput = variant.outputs.first()
def processResources = variantOutput.hasProperty(“processResourcesProvider”) ?
variantOutput.processResourcesProvider.get() : variantOutput.processResources
processResources.dependsOn(copyFlutterAssetsTask)
}
return copyFlutterAssetsTask
} // end def addFlutterDeps
…
}
上面这段比较直观,步骤5细节我们后面会分析这个 FlutterTask;对于步骤 6 其实也蛮直观,我们执行 flutter build apk 后看产物目录如下:
这个 jar 也是重点,它里面其实不是 class,而是上图中的 abi 对应 app.so,也就是 dart app 编译的 so。所以 libs.jar 解压如下:
这货会被类似 implementation files(‘libs.jar’) 添加进我们 project 的编译依赖项中,然后里面的 so 会在项目执行标准 mergeDebugNativeLibs task 时打包进标准 lib 目录,所以最终 apk 中 app.so 位于 lib 目录下(好奇反思:官方这里为什么不直接弄成 aar,而是把 so 打进 jar,感觉回到了 eclipse 时代,没整明白为什么)。
对于步骤 8 来说,assets 合并复制操作在 app 主包的中间产物中效果如下:
因此,步骤 6、步骤 8 的产物最终编译后就是 apk 中对应的东西,对应 apk 解压如下:
上面步骤5中的 FlutterTask 我们先放一放,让我们先继续看 addFlutterTasks 这个方法剩下的部分:
private void addFlutterTasks(Project project) {
//…上面已分析,下面接续分析
//1、如果是applicationVariants就走进去,也就是说project是app module
if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant ->
//也就是assemble task咯
Task assembleTask = getAssembleTask(variant)
//正常容错,不用关心
if (!shouldConfigureFlutterTask(assembleTask)) {
return
}
//把前面定义的addFlutterDeps函数调用返回的copyFlutterAssetsTask任务拿到作为依赖项
//这货的作用和产物前面已经图示贴了产物
Task copyFlutterAssetsTask = addFlutterDeps(variant)
def variantOutput = variant.outputs.first()
def processResources = variantOutput.hasProperty(“processResourcesProvider”) ?
variantOutput.processResourcesProvider.get() : variantOutput.processResources
processResources.dependsOn(copyFlutterAssetsTask)
//2、执行flutter run或者flutter build apk的产物apk归档处理
//不多解释,下面会图解说明
variant.outputs.all { output ->
assembleTask.doLast {
// packageApplication
became packageApplicationProvider
in AGP 3.3.0.
def outputDirectory = variant.hasProperty(“packageApplicationProvider”)
-
? variant.packageApplicationProvider.get().outputDirectory
- variant.packageApplication.outputDirectory
// outputDirectory
is a DirectoryProperty
in AGP 4.1.
String outputDirectoryStr = outputDirectory.metaClass.respondsTo(outputDirectory, “get”)
-
? outputDirectory.get()
- outputDirectory
String filename = “app”
String abi = output.getFilter(OutputFile.ABI)
if (abi != null && !abi.isEmpty()) {
filename += “-${abi}”
}
if (variant.flavorName != null && !variant.flavorName.isEmpty()) {
filename += “-${variant.flavorName.toLowerCase()}”
}
filename += “-${buildModeFor(variant.buildType)}”
project.copy {
from new File(“ o u t p u t D i r e c t o r y S t r / outputDirectoryStr/ outputDirectoryStr/{output.outputFileName}”)
into new File(“${project.buildDir}/outputs/flutter-apk”);
rename {
return “${filename}.apk”
}
}
}
}
}
//3、小重点
configurePlugins()
return
}
//3、是不是模块源码依赖方式集成到现有项目,参见 https://flutter.cn/docs/development/add-to-app/android/project-setup
//是的话对模块也做类似一堆处理即可,不再重复分析了,也是 assets 合并
String hostAppProjectName = project.rootProject.hasProperty(‘flutter.hostAppProjectName’) ? project.rootProject.property(‘flutter.hostAppProjectName’) : “app”
Project appProject = project.rootProject.findProject(“😒{hostAppProjectName}”)
assert appProject != null : “Project 😒{hostAppProjectName} doesn’t exist. To custom the host app project name, set org.gradle.project.flutter.hostAppProjectName=<project-name>
in gradle.properties.”
// Wait for the host app project configuration.
appProject.afterEvaluate {
assert appProject.android != null
project.android.libraryVariants.all { libraryVariant ->
Task copyFlutterAssetsTask
appProject.android.applicationVariants.all { appProjectVariant ->
Task appAssembleTask = getAssembleTask(appProjectVariant)
if (!shouldConfigureFlutterTask(appAssembleTask)) {
return
}
// Find a compatible application variant in the host app.
//
// For example, consider a host app that defines the following variants:
// | ----------------- | ----------------------------- |
// | Build Variant | Flutter Equivalent Variant |
// | ----------------- | ----------------------------- |
// | freeRelease | release |
// | freeDebug | debug |
// | freeDevelop | debug |
// | profile | profile |
// | ----------------- | ----------------------------- |
//
// This mapping is based on the following rules:
// 1. If the host app build variant name is profile
then the equivalent
// Flutter variant is profile
.
// 2. If the host app build variant is debuggable
// (e.g. buildType.debuggable = true
), then the equivalent Flutter
// variant is debug
.
// 3. Otherwise, the equivalent Flutter variant is release
.
String variantBuildMode = buildModeFor(libraryVariant.buildType)
if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
return
}
if (copyFlutterAssetsTask == null) {
copyFlutterAssetsTask = addFlutterDeps(libraryVariant)
}
Task mergeAssets = project
.tasks
.findByPath(“: h o s t A p p P r o j e c t N a m e : m e r g e {hostAppProjectName}:merge hostAppProjectName:merge{appProjectVariant.name.capitalize()}Assets”)
assert mergeAssets
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
}
}
configurePlugins()
}
上面这段代码分析中的步骤2本质就是对标准安卓构建产物进行一次重新按照格式归档,如果是 split api 模式就能很直观看出来效果,下面图示是直接运行 flutter build apk 的步骤 2 效果:
对于上面代码片段中的步骤 3,我们可以详细来分析下:
/**
-
flutter的依赖都添加在pubspec.yaml中
-
接着都会执行flutter pub get,然后工具会生成跟目录下.flutter-plugins等文件
-
这里做的事情就是帮忙给module自动添加上这些插件dependencies依赖模块
*/
private void configurePlugins() {
if (!buildPluginAsAar()) {
//项目根目录下的.flutter-plugins文件
getPluginList().each this.&configurePluginProject
//项目根目录下的.flutter-plugins-dependencies文件
getPluginDependencies().each this.&configurePluginDependencies
return
}
project.repositories {
maven {
url “${getPluginBuildDir()}/outputs/repo”
}
}
getPluginList().each { pluginName, pluginPath ->
configurePluginAar(pluginName, pluginPath, project)
}
}
到此整个 addFlutterTasks 核心方法我们就分析完毕。接下来让我们把目光转向 FlutterTask 的实现,Task 机制不懂就自己去补习 gradle 基础吧,重点入口就是 @TaskAction,如下(比较长,但是比较直观简单):
abstract class BaseFlutterTask extends DefaultTask {
//…一堆task属性声明,忽略
@OutputFiles
FileCollection getDependenciesFiles() {
FileCollection depfiles = project.files()
// Includes all sources used in the flutter compilation.
depfiles += project.files(“${intermediateDir}/flutter_build.d”)
return depfiles
}
//重点!!!!!!!!!!!!!!!!!!!!!
//整个flutter android编译的核心实现在此!!!!
void buildBundle() {
if (!sourceDir.isDirectory()) {
throw new GradleException(“Invalid Flutter source directory: ${sourceDir}”)
}
//1、默认以app为例创建build/app/intermediates/flutter目录
intermediateDir.mkdirs()
//2、计算flutter assemble的规则名称列表
String[] ruleNames;
if (buildMode == “debug”) {
ruleNames = [“debug_android_application”]
} else if (deferredComponents) {
ruleNames = targetPlatformValues.collect { “android_aot_deferred_components_bundle_KaTeX parse error: Expected group after '_' at position 12: {buildMode}_̲it” }
} else {
ruleNames = targetPlatformValues.collect { “android_aot_bundle_KaTeX parse error: Expected group after '_' at position 12: {buildMode}_̲it” }
}
//3、重点执行命令
project.exec {
logging.captureStandardError LogLevel.ERROR
//4、windows的话就是flutter SDK路径下 bin/flutter.bat文件,unix就是bin/flutter
executable flutterExecutable.absolutePath
//5、我们app的build.gradle中配置的flutter { source ‘…/…/’ }闭包,路径,也就是项目根目录下
workingDir sourceDir
//6、使用本地自己编译的flutter engine才需要的参数
if (localEngine != null) {
args “–local-engine”, localEngine
args “–local-engine-src-path”, localEngineSrcPath
}
//7、类似标准gradle构建参数打印控制
if (verbose) {
args “–verbose”
} else {
args “–quiet”
}
//8、追加一堆编译参数
args “assemble”
args “–no-version-check”
args “–depfile”, “${intermediateDir}/flutter_build.d”
//flutter 编译产物输出路径
args “–output”, “${intermediateDir}”
if (performanceMeasurementFile != null) {
args “–performance-measurement-file=${performanceMeasurementFile}”
}
//Flutter dart程序入口,默认为lib/main.dart
if (!fastStart || buildMode != “debug”) {
args “-dTargetFile=${targetPath}”
} else {
args “-dTargetFile=${Paths.get(flutterRoot.absolutePath, “examples”, “splash”, “lib”, “main.dart”)}”
}
args “-dTargetPlatform=android”
args “-dBuildMode=${buildMode}”
if (trackWidgetCreation != null) {
args “-dTrackWidgetCreation=${trackWidgetCreation}”
}
if (splitDebugInfo != null) {
args “-dSplitDebugInfo=${splitDebugInfo}”
}
if (treeShakeIcons == true) {
args “-dTreeShakeIcons=true”
}
if (dartObfuscation == true) {
args “-dDartObfuscation=true”
}
if (dartDefines != null) {
args “–DartDefines=${dartDefines}”
}
if (bundleSkSLPath != null) {
args “-iBundleSkSLPath=${bundleSkSLPath}”
}
if (codeSizeDirectory != null) {
args “-dCodeSizeDirectory=${codeSizeDirectory}”
}
if (extraGenSnapshotOptions != null) {
args “–ExtraGenSnapshotOptions=${extraGenSnapshotOptions}”
}
if (extraFrontEndOptions != null) {
args “–ExtraFrontEndOptions=${extraFrontEndOptions}”
}
args ruleNames
}
}
}
class FlutterTask extends BaseFlutterTask {
//默认以app为例则为build/app/intermediates/flutter目录。
@OutputDirectory
File getOutputDirectory() {
return intermediateDir
}
//默认以app为例则为build/app/intermediates/flutter/flutter_assets目录,前面我们已经截图展示过这个目录产物。
@Internal
String getAssetsDirectory() {
return “${outputDirectory}/flutter_assets”
}
//assets复制操作定义,intermediateDir就是getOutputDirectory路径
@Internal
CopySpec getAssets() {
return project.copySpec {
from “${intermediateDir}”
include “flutter_assets/**” // the working dir and its files
}
}
//dart编译的产物复制操作定义(注意:release和profile模式才是so产物),intermediateDir就是getOutputDirectory路径
@Internal
CopySpec getSnapshots() {
return project.copySpec {
from “${intermediateDir}”
if (buildMode == ‘release’ || buildMode == ‘profile’) {
targetPlatformValues.each {
include “${PLATFORM_ARCH_MAP[targetArch]}/app.so”
}
}
}
}
//依赖格式解析生成文件路径集合
FileCollection readDependencies(File dependenciesFile, Boolean inputs) {
if (dependenciesFile.exists()) {
// Dependencies file has Makefile syntax:
// :
String depText = dependenciesFile.text
// So we split list of files by non-escaped(by backslash) space,
def matcher = depText.split(': ')[inputs ? 1 : 0] =~ /(\ |[^\s])+/
// then we replace all escaped spaces with regular spaces
def depList = matcher.collect{it[0].replaceAll("\\ ", " ")}
return project.files(depList)
}
return project.files();
}
//输入源为所有依赖模块的pubspec.yaml文件集合
@InputFiles
FileCollection getSourceFiles() {
FileCollection sources = project.files()
for (File depfile in getDependenciesFiles()) {
sources += readDependencies(depfile, true)
}
return sources + project.files(‘pubspec.yaml’)
}
@OutputFiles
FileCollection getOutputFiles() {
FileCollection sources = project.files()
for (File depfile in getDependenciesFiles()) {
sources += readDependencies(depfile, false)
}
return sources
}
//重点实现!!!!!!!
@TaskAction
void build() {
buildBundle()
}
}
可以很直观的看到,整个构建编译的核心都是通过执行 Flutter SDK 中 bin 目录下的 flutter 脚本完成的,大段代码只是为了为执行这个脚本准备参数配置信息。也就是说 flutter 编译本质命令大致如下:
flutter assemble --no-version-check \
–depfile build/app/intermediates/flutter/release/flutter_build.d \
–output build/app/intermediates/flutter/release/ \
-dTargetFile=lib/main.dart \
-dTargetPlatform=android \
-dBuildMode=release \
-dDartObfuscation=true \
android_aot_bundle_release_android-arm \
android_aot_bundle_release_android-arm64 \
android_aot_bundle_release_android-x86 \
android_aot_bundle_release_android-x64
这就走到了 SDK 里面的纯 flutter 命令脚本了。
Flutter SDK 下bin/flutter
编译命令分析
承接上面分析,上一小节最后的命令本质就是本小节的脚本,我们把目光转向 Flutter SDK 中 bin 目录下的 flutter 脚本,如下:
#!/usr/bin/env bash
#1、该命令之后出现的代码,一旦出现了返回值非零,整个脚本就会立即退出,那么就可以避免一些脚本的危险操作。
set -e
#2、清空CDPATH变量值
unset CDPATH
在Mac上,readlink -f不起作用,因此follow_links一次遍历一个链接的路径,然后遍历cd进入链接目的地并找出它。
返回的文件系统路径必须是Dart的URI解析器可用的格式,因为Dart命令行工具将其参数视为文件URI,而不是文件名。
例如,多个连续的斜杠应该减少为一个斜杠,因为双斜杠表示URI的authority。
function follow_links() (
cd -P “$(dirname – “$1”)”
file=“ P W D / PWD/ PWD/(basename – “$1”)”
while [[ -h “$file” ]]; do
cd -P “ ( d i r n a m e − − " (dirname -- " (dirname−−"file”)"
file=“ ( r e a d l i n k − − " (readlink -- " (readlink−−"file”)"
cd -P “ ( d i r n a m e − − " (dirname -- " (dirname−−"file”)"
file=“ P W D / PWD/ PWD/(basename – “$file”)”
done
echo “$file”
)
这个变量的值就是Flutter SDK根目录下的bin/flutter
PROG_NAME=“ ( f o l l o w l i n k s " (follow_links " (followlinks"{BASH_SOURCE[0]}”)"
BIN_DIR=“ ( c d " (cd " (cd"{PROG_NAME%/*}” ; pwd -P)"
OS=“$(uname -s)”
平台兼容
if [[ $OS =~ MINGW.* || $OS =~ CYGWIN.* ]]; then
exec “ B I N D I R / f l u t t e r . b a t " " {BIN_DIR}/flutter.bat" " BINDIR/flutter.bat""@”
fi
#3、source导入这个shell脚本后执行其内部的shared::execute方法
source “$BIN_DIR/internal/shared.sh”
shared::execute “$@”
很明显,我们需要将目光转向 Flutter SDKbin/internal/shared.sh
文件,且关注其内部的shared::execute
方法,如下:
#…
function shared::execute() {
#1、默认FLUTTER_ROOT值为FlutterSDK根路径
export FLUTTER_ROOT=“ ( c d " (cd " (cd"{BIN_DIR}/…” ; pwd -P)"
#2、如果存在就先执行bootstrap脚本,默认SDK下面是没有这个文件的,我猜是预留给我们自定义初始化挂载用的。
BOOTSTRAP_PATH=“$FLUTTER_ROOT/bin/internal/bootstrap.sh”
if [ -f “$BOOTSTRAP_PATH” ]; then
source “$BOOTSTRAP_PATH”
fi
#3、一堆基于FlutterSDK路径的位置定义
FLUTTER_TOOLS_DIR=“$FLUTTER_ROOT/packages/flutter_tools”
SNAPSHOT_PATH=“$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot”
STAMP_PATH=“$FLUTTER_ROOT/bin/cache/flutter_tools.stamp”
SCRIPT_PATH=“$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart”
DART_SDK_PATH=“$FLUTTER_ROOT/bin/cache/dart-sdk”
DART=“$DART_SDK_PATH/bin/dart”
PUB=“$DART_SDK_PATH/bin/pub”
#4、路径文件平台兼容,常规操作,忽略
case “$(uname -s)” in
MINGW*)
DART=“$DART.exe”
PUB=“$PUB.bat”
;;
esac
#5、测试运行脚本的账号是否为超级账号,是的话警告提示,Docker和CI环境不警告。
if [[ “KaTeX parse error: Expected 'EOF', got '&' at position 14: EUID" == "0" &̲& ! -f /.docker…CI” != “true” && “KaTeX parse error: Expected 'EOF', got '&' at position 16: BOT" != "true" &̲& "CONTINUOUS_INTEGRATION” != “true” ]]; then
&2 echo " Woah! You appear to be trying to run flutter as root."
&2 echo " We strongly recommend running the flutter tool without superuser privileges."
&2 echo " /"
&2 echo “📎”
fi
#6、测试git命令行环境配置是否正常,不正常就抛出错误。
if ! hash git 2>/dev/null; then
&2 echo “Error: Unable to find git in your PATH.”
exit 1
fi
#7、FlutterSDK是否来自clone等测试。
if [[ ! -e “$FLUTTER_ROOT/.git” ]]; then
&2 echo “Error: The Flutter directory is not a clone of the GitHub project.”
&2 echo " The flutter tool requires Git in order to operate properly;"
&2 echo " to install Flutter, see the instructions at:"
&2 echo " https://flutter.dev/get-started"
exit 1
fi
To debug the tool, you can uncomment the following lines to enable checked
mode and set an observatory port:
FLUTTER_TOOL_ARGS=“–enable-asserts $FLUTTER_TOOL_ARGS”
FLUTTER_TOOL_ARGS=“$FLUTTER_TOOL_ARGS --observe=65432”
#7、日常编译遇到命令lock文件锁住问题就是他,本质该方法就是创建/bin/cache目录并维持锁状态等事情,不是我们关心的重点。
upgrade_flutter 7< “$PROG_NAME”
#8、相关参数值,别问我怎么知道的,问就是自己在源码对应位置echo输出打印的
BIN_NAME=flutter、PROG_NAME=FLUTTER_SDK_DIR/bin/flutter
DART=FLUTTER_SDK_DIR/bin/cache/dart-sdk/bin/dart
FLUTTER_TOOLS_DIR=FLUTTER_SDK_DIR/packages/flutter_tools
FLUTTER_TOOL_ARGS=空
SNAPSHOT_PATH=FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot
@=build apk
BIN_NAME=“ ( b a s e n a m e " (basename " (basename"PROG_NAME”)"
case “$BIN_NAME” in
flutter*)
FLUTTER_TOOL_ARGS aren’t quoted below, because it is meant to be
considered as separate space-separated args.
“ D A R T " − − d i s a b l e − d a r t − d e v − − p a c k a g e s = " DART" --disable-dart-dev --packages=" DART"−−disable−dart−dev−−packages="FLUTTER_TOOLS_DIR/.packages” F L U T T E R T O O L A R G S " FLUTTER_TOOL_ARGS " FLUTTERTOOLARGS"SNAPSHOT_PATH" “$@”
;;
dart*)
“ D A R T " " DART" " DART""@”
;;
*)
&2 echo “Error! Executable name $BIN_NAME not recognized!”
exit 1
;;
esac
}
可以看到,由于 Flutter SDK 内部内置了 Dart,所以当配置环境变量后 flutter、dart 命令都可以使用了。而我们安装 Flutter SDK 后首先做的事情就是把 SDK 的 bin 目录配置到了环境变量,所以执行的 flutter build apk、flutter upgrade、flutter pub xxx 等命令本质都是走进了上面这些脚本,且 flutter 命令只是对 dart 命令的一个包装,所以执行flutter pub get
其实等价于dart pub get
。所以假设我们执行flutter build apk
命令,本质走到上面脚本最终执行的命令如下:
FLUTTER_SDK_DIR/bin/cache/dart-sdk/bin/dart \
–disable-dart-dev --packages=FLUTTER_SDK_DIR/packages/flutter_tools/.packages \
FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot \
build apk
上面命令行中 FLUTTER_SDK_DIR 代表的就是 Flutter SDK 的根目录,--packages
可以理解成是一堆 SDK 相关依赖,FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot
就是FLUTTER_SDK_DIR/packages/flutter_tools
的编译产物。所以,上面其实通过 dart 命令执行flutter_tools.snapshot
文件也就是等价于执行flutter_tools.dart
的main()
方法。因此上面命令继续简化大致如下:
dart --disable-dart-dev --packages=xxx flutter_tools.dart build apk
也就是说,我们执行的任何 flutter 命令,本质都是把参数传递到了FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart
源码的 main 方法中,所以真正做事情的都在这部分源码里。这里由于篇幅问题不展开说明,后面专门写一篇解析,然后与本文关联阅读即可彻底搞懂。
=================================================================================
对于包含 android 代码的 flutter plugin 模块来说,其 android 部分就是一个标准的原生 android library,没有任何额外的干预脚本,所以就不分析了。这里只是提醒下,当我们新建一个 flutter plugin 时,其项目默认除过 plugin 会帮我们生成一个 example 的模块,目的只是为了方便我们独立开发 flutter plugin 时能脱离自己主项目进行 demo 验证,大致目录如下:
=================================================================================
对于原生现有工程集成 flutter 来说,flutter module 就是最好的隔离选择,这也就造就了其与 flutter app 在编译上的一些差异与共性。这部分我们重点分析 flutter module 与 上面分析的 app 编译流程差异,共性部分不再分析。
同样先从.android/settings.gradle
看起来:
// app 是测试 module,用来验证 flutter module 的,本质最后 flutter module 会生成可集成的 aar
include ‘:app’
//导入配置.android/include_flutter.groovy
rootProject.name = ‘android_generated’
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir, ‘include_flutter.groovy’))
目光转向当前 flutter module 项目.android/include_flutter.groovy
文件,如下:
//1、以当前脚本为坐标找到当前项目根路径
def scriptFile = getClass().protectionDomain.codeSource.location.toURI()
def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
//2、导入flutter module名称为相对当前目录的flutter
gradle.include “:flutter”
//3、flutter module android真正的实现位于.android/Flutter目录下
gradle.project(“:flutter”).projectDir = new File(flutterProjectRoot, “.android/Flutter”)
//4、前面见过了,就是获取 flutter sdk 路径,然后导入脚本
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。
加油,共勉。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
//4、前面见过了,就是获取 flutter sdk 路径,然后导入脚本
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-PQNfAwKs-1712785490102)]
[外链图片转存中…(img-txj95wQJ-1712785490103)]
[外链图片转存中…(img-GWeq7XRV-1712785490103)]
[外链图片转存中…(img-z8eJRVl0-1712785490103)]
[外链图片转存中…(img-2088kjC3-1712785490103)]
[外链图片转存中…(img-k8InPlaS-1712785490104)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-wNmt3yHP-1712785490104)]
最后
为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。
加油,共勉。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-PkPuO86J-1712785490104)]