background
This part of the distribution diagram and source code has recently been updated and revised based on Flutter 2.2.3. The purpose is to understand the whole process of Flutter compilation at the Android application layer so that you can be aware of any problems with Flutter compilation. The other purpose is to be able to customize Flutter compilation at the application layer. Full text is relatively long, illustrated, from the engineering structure into the source code analysis.
Several forms of the Flutter module
Earlier versions of Flutter did not support the creation of a Flutter Module. There were only three other types of Flutter Module. All of these types were created with their own wheels and scripts. Therefore, Flutter currently supports the creation of four modules.
The corresponding project structure of these four modules is roughly as follows, and their use scenarios are also different. We need to create suitable modules according to our own needs.
Overview of Flutter module dependencies and products
When we add dependencies to the YAML file and execute the flutter pub get command, the dependencies are automatically downloaded or copied from where they were configured. For pure Dart dependencies (the download location of the Flutter Package) is in your Flutter SDK directory at the. Pub-cache\ hoste\ pub.dartlang.org\dio-4.0.0 location (MAC) Pub -cache) and pub.flutter-io.cn/packages/di… For example, lib is the project’s main dependency in this directory, as follows:
The corresponding dependency expansion in Android Studio looks like this:
For dependencies on the Flutter Plugin download location in your Flutter SDK directory. Pub-cache\hosted\pub.dartlang.org \webview_flutter-2.0.10 position (MAC) Pub -cache) and pub.flutter-io.cn/packages/we… For example, lib and the corresponding platform directories are the main dependencies of the project, as follows:
The corresponding dependency expansion in Android Studio looks like this:
For a Flutter App, the result of the compilation of the Flutter build apk command is as follows:
It is important to have a brief understanding of the structure of the output shown above, as the focus of the source code analysis below will be on how to compile these things.
Flutter App android source code compilation process
Let’s start with the process of compiling android APK from the App of the Pure Flutter project.
settings.gradle
Source code flow analysis
Android /settings.gradle builds android/settings.gradle.
// Current App Module
include ':app'
/** * get the value of flutter. SDK from android/local.properties Apply the packages/flutter_tools/gradle/app_plugin_loader.gradle files */ under the apply Flutter SDK path
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assertflutterSdkPath ! =null."flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
Copy the code
The following steps refer to the /packages/ Flutter_tools /gradle/app_plugin_loader.gradle file in your Flutter SDK installation directory:
import groovy.json.JsonSlurper
// Get the root path of your new Flutter project. This is the root path of your project because it has been created by your new Project Apply
def flutterProjectRoot = rootProject.projectDir.parentFile
// Obtain the json configuration file of your project's root path. Throwing-plugins-dependencies
// Note: if this logic is changed, also change the logic in module_plugin_loader.gradle.
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins-dependencies')
if(! pluginsFile.exists()) {return
}
/** * 1. Parse json file contents using Groovy's JsonSlurper. * 2. Simply verify the validity of the JSON content field type. * * include all android dependencies on Flutter plugins */
def object = new JsonSlurper().parseText(pluginsFile.text)
assert object instanceof Map
assert object.plugins instanceof Map
assert object.plugins.android instanceof List
// Includes the Flutter plugins that support the Android platform.
object.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String
assert androidPlugin.path instanceof String
def pluginDirectory = new File(androidPlugin.path, 'android')
assert pluginDirectory.exists()
include ":${androidPlugin.name}"
project(":${androidPlugin.name}").projectDir = pluginDirectory
}
Copy the code
The gradle script above is very simple, you can read the comments. To illustrate, here is a new typical demo project with the following pubspec.yaml file dependency configuration:
dependencies:
flutter:
sdk: flutter
dio: ^ 4.0.0 # Flutter Package from pub. Dev repository
webview_flutter: ^ 2.0.10 # The Flutter Plugin package from the pub.dev repository
f_package: Create a new Flutter Package from your local folder
path: . /.. /f_package
f_plugin: # Create a Plugin package from your own folder
path: . /.. /f_plugin
Copy the code
The.flutter-plugins-dependencies file at the root of the project reads as follows:
{
"info":"This is a generated file; do not edit or check into version control."."plugins": {"ios":[
{"name":"f_plugin"."path":"E:\\\\f_plugin\\\\"."dependencies": []}, {"name":"webview_flutter"."path":"D:\\\\software\\\\flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org \ \ \ \ webview_flutter - 2.0.10 \ \ \ \"."dependencies":[]}
],
"android":[
{"name":"f_plugin"."path":"E:\\\\f_plugin\\\\"."dependencies": []}, {"name":"webview_flutter"."path":"D:\\\\software\\\\flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org \ \ \ \ webview_flutter - 2.0.10 \ \ \ \"."dependencies":[]}
],
"macos": []."linux": []."windows": []."web":[
{"name":"f_plugin"."path":"E:\\\\f_plugin\\\\"."dependencies": []}},"dependencyGraph":[
{"name":"f_plugin"."dependencies": []}, {"name":"webview_flutter"."dependencies":[]}
],
"date_created":"202-0 x x - 15 21:41:39. 225336"."version":"Then"
}
Copy the code
Gradle is an android/settings.gradle project. During the initialization phase of gradle’s life cycle, settings.gradle is applied from: “$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader gradle” after processing automatically into the following pseudo code:
include ':app'
// Automatically generated by matching dependencies and then parsing app_plugin_loader.gradle
//include ":${androidPlugin.name}"
//project(":${androidPlugin.name}").projectDir = pluginDirectory
include ":f_plugin"
project(":f_plugin").projectDir = new File("E:\\\\f_plugin\\\\".'android')
include ":webview_flutter"
project(":webview_flutter").projectDir = new File("D:\\\\software\\\\flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org \ \ \ \ webview_flutter - 2.0.10 \ \ \ \".'android')
Copy the code
Zha say! It’s a software engineering principle of “convention over configuration”. You just follow the rules, and you end up looking like we do on standard Android projects.
build.gradle
Source code flow analysis
Build. Gradle = build.gradle = build.gradle = build.gradle = build.gradle
/ /... Omit common configuration that doesn't matter
// See, he moved all android-based builds to the root directory, so they're all there
rootProject.buildDir = '.. /build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app') // Run app dependencies before running other configurations
}
Copy the code
Build. Gradle = build.gradle = build.gradle = build.gradle = build.gradle
/** * 1. Read the local.properties configuration information. * 2. Obtain the path of flutter. SDK. * 3. Get the value of flutter. VersionCode, which automatically reads the assignment from pubspec.yaml at compile time, so change yaml to change the version number. * 4. Get the value of flutter. VersionName, which automatically reads the assignment from pubspec.yaml at compile time, so change yaml to change the version number. * /
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")}def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
// Do not explain the general operation
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
/ / key 1: apply the flutter SDK packages below/flutter_tools/gradle/flutter. Gradle script file
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "cn.yan.f1"
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger() // Assign to the value read in YAMl
versionName flutterVersionName // Assign to the value read in YAMl
}
/ /... Omit general operations without explanation
}
// Emphasis 2: an extended configuration that specifies the source path as the current two parent levels, i.e. the project root directory
flutter {
source '.. /.. '
}
/ /... Omit general operations without explanation
Copy the code
We look at the above mentioned below point 1, namely Flutter in the SDK packages/flutter_tools/gradle/Flutter. Gradle, when we are in accordance with the script is running way to analyze the macro to the detail, as follows:
/ /... Omit a bunch of import headers
/** * General script configuration: Repository and AGP version dependencies * If you do not have a domestic maven image of your own, modify this repository. * If your project is not compatible with this version of AGP, modify it yourself and make it compatible. * /
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com. Android. Tools. Build: gradle: 4.1.0'}}// Java8 compiles and configures
android {
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8}}// A plugin is applied, but the source code of the plugin is defined directly below
apply plugin: FlutterPlugin
//FlutterPlugin plugin implementation source code, refer to the standard plug-in writing method, the basic syntax is not explained, here focus on logic.
class FlutterPlugin implements Plugin<Project> {
/ /...
// Key entrance !!!!!!
@Override
void apply(Project project) {
this.project = project
FLUTTER_STORAGE_BASE_URL will be used first if the environment variable is configured, and default if it is notString hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ? : DEFAULT_MAVEN_HOST String repository = useLocalEngine() ? project.property('local-engine-repo')
: "$hostedRepository/download.flutter.io"
project.rootProject.allprojects {
repositories {
maven {
url repository
}
}
}
// create app module configured flutter{source: '.. /.. / '} closure extensions
project.extensions.create("flutter", FlutterExtension)
// add tasks related to flutter construction
this.addFlutterTasks(project)
// check whether the build command flutter build apk --split-per-abi adds --split-per-abi. If yes, split it into multiple ABI packages.
if (shouldSplitPerAbi()) {
project.android {
splits {
abi {
// Enables building multiple APKs per ABI.
enable true
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies that we do not want to also generate a universal APK that includes all ABIs.
universalApk false}}}}// Check whether to add deferred-component-names to the build command. If yes, configure android dynamicFeatures bundle.
if (project.hasProperty('deferred-component-names')) {
String[] componentNames = project.property('deferred-component-names').split(', ').collect {":${it}"}
project.android {
dynamicFeatures = componentNames
}
}
If no --target-platform=xxxABI is added to the build command, use the default one. If the ABI is added to the build command, use the default one. If the ABI is supported by the flutter, use the default one.
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android {
if (shouldSplitPerAbi()) {
splits {
abi {
include abiValue
}
}
}
}
}
Flutter. SDK, or the environment variable FLUTTER_ROOT.
String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT)
if (flutterRootPath == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
}
flutterRoot = project.file(flutterRootPath)
if(! flutterRoot.isDirectory()) {throw new GradleException("flutter.sdk must point to the Flutter SDK directory")}Get the version of the Flutter Engine. If the local Engine is used with the local-engine-repo parameter, the version of the Flutter Engine is +. Otherwise, read the bin\internal\ Engine.
engineVersion = useLocalEngine()
? "+" // Match any version since there's only one.
: "1.0.0 -" + Paths.get(flutterRoot.absolutePath, "bin"."internal"."engine.version").toFile().text.trim()
Obtain the corresponding flutter command scripts according to the platform, which are located in the SDK directory bin\ and named flutter
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
Package \flutter_tools\gradle\ Flutter_proguard_rules.pro;
Plugin.** and -dontwarn android.**
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages"."flutter_tools"."gradle"."flutter_proguard_rules.pro")
project.android.buildTypes {
//11. Add profile buildTypes, set in android.buildTypes under current project
profile {
initWith debug //initWith replicates all debug properties
if (it.hasProperty("matchingFallbacks")) {
matchingFallbacks = ["debug"."release"]}}/ /...
}
/ /...
Add dependencies to all buildTypes, addFlutterDependencies
project.android.buildTypes.all this.&addFlutterDependencies
}
/ /...
}
//flutter{} closure Extension definition
class FlutterExtension {
String source
String target
}
/ /...
Copy the code
As you can see, the script above is essentially a standard plug-in that does some configuration internally based on the parameters we pass. The performance of step 4 above depends on the product, which is not shown here. Step 11 actually added a new compilation type, corresponding to the project is the performance mode, as follows:
Step 12 The script for adding dependencies is as follows:
/** * adds the dependencies of the Flutter project to each buildType, including embedding and libflutter
void addFlutterDependencies(buildType) {
// Get the build type. The values are debug, profile, release
String flutterBuildMode = buildModeFor(buildType)
// Ignore this condition when using the local Engine, and continue
if(! supportsBuildMode(flutterBuildMode)) {return
}
// If the plugin is not of applicationarty type, i.e. Android Library, or the number of Android plugins in the '. Flight-plugins' file under the project root directory is empty.
if(! isFlutterAppProject() || getPluginList().size() ==0) {
Add a build dependency to the Android Plugin for the Flutter Plugin
// For example IO. Flutter :flutter_embedding_debug:1.0.0, from maven repository
addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")}// Add the corresponding build dependency to project
// For example IO. Flutter: arm64_v8A_debug :1.0.0, from maven repository
List<String> platforms = getTargetPlatforms().collect()
// Debug mode includes x86 and x64, which are commonly used in emulators.
if (flutterBuildMode == "debug" && !useLocalEngine()) {
platforms.add("android-x86")
platforms.add("android-x64")
}
platforms.each { platform ->
String arch = PLATFORM_ARCH_MAP[platform].replace("-"."_")
// Add the `libflutter.so` dependency.
addApiDependencies(project, buildType.name,
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")}}private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) {
String configuration;
// `compile` dependencies are now `api` dependencies.
if (project.getConfigurations().findByName("api")) {
configuration = "${variantName}Api";
} else {
configuration = "${variantName}Compile";
}
project.dependencies.add(configuration, dependency, config)
}
Copy the code
The essence of this script is to automatically add build dependencies to the Flutter project. This dependency is also a Maven repository, just like the okhttp dependencies we added to gradle. < span style = “box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;”
Next we put the focus back to step 3 (addFlutterTasks). This is the focus of our whole Flutter app compilation and the most complex part, as follows:
private void addFlutterTasks(Project project) {
// The gradle project configuration evaluation fails
if (project.state.failure) {
return
}
//1, a bunch of attribute fetch and assignment operations
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()
......
}
Copy the code
As you can see, the first part of the addFlutterTasks method is relatively simple, basically reading the configuration attributes from the Project for subsequent steps. So let’s move on to the section after step 1 of the addFlutterTasks method:
private void addFlutterTasks(Project project) {
// A bunch of attribute fetch and assignment operations
/ /...
// Define addFlutterDeps arrow function, variant is the build type corresponding to the standard build
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
//2, general operation: if you are building multiple variants of APK pattern to deal with the VC problem
variant.outputs.each { output ->
// Since the GP store does not allow multiple APKs of the same app to all have the same version information, you need to ensure that each APK has its own unique versionCode before uploading to the Play Store, which is what is done here.
/ / https://developer.android.com/studio/build/configure-apk-splits specific can see the official document
def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))
if(abiVersionCode ! =null) {
output.versionCodeOverride =
abiVersionCode * 1000 + variant.versionCode
}
}
}
// get the build type. VariantBuildMode is debug, profile, release
String variantBuildMode = buildModeFor(variant.buildType)
/ / 4, according to the parameters to generate a task name, such as the compileFlutterBuildDebug here, compileFlutterBuildProfile, compileFlutterBuildRelease
String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])
/ / 5, to the current project to create compileFlutterBuildDebug, compileFlutterBuildProfile, compileFlutterBuildRelease Task
// Implemented as FlutterTask, mainly used to compile Flutter code. This task will be analyzed separately later
FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
// All task attribute assignments come from the above attributes or matching analysis
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode variantBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
Dart: lib/main.dart is the default dart entry
targetPath getFlutterTarget()
verbose isVerbose()
fastStart isFastStart()
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
targetPlatformValues = targetPlatforms
sourceDir getFlutterSourceDirectory()
// Learn a little skill, the original intermediate API is androidProject. FD_INTERMEDIATES, which is also the folder of flutter intermediates
intermediateDir project.file("${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
// Finally do a wave of permission related processing
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine('cmd'.'/c'."attrib -r ${assetsDirectory}/* /s")}else {
commandLine('chmod'.'-R'.'u+w', assetsDirectory)
}
}
}
}
/ / project build of the intermediate file, which is the root directory build/intermediates/flutter/debug/libs. The jar file
File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
/ / 6, create packLibsFlutterBuildProfile, packLibsFlutterBuildDebug, packLibsFlutterBuildRelease tasks, main product is copy and position of the operation, the Jar type of task
/ / role is to build/intermediates/flutter/debug/down according to the abi generated app. So through the jar command packaged into the build/intermediates/flutter/debug/libs. The jar
Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}".type: Jar) {
/ / the target path to build/intermediates/flutter/debug directory
destinationDir libJar.parentFile
// The file name is libs.jar
archiveName libJar.name
// Depending on compileFlutterBuildDebug defined in step 5 above, that is, the task is basically a product handler
dependsOn compileTask
//targetPlatforms The value can be Android-arm, Android-arm64, android-x86, or Android-x64
targetPlatforms.each { targetPlatform ->
// The abi can be armeabi-v7A, arm64-v8A, x86, or x86_64
String abi = PLATFORM_ARCH_MAP[targetPlatform]
// The data source is from the compileFlutterBuildDebug task intermediate directory of Step 5
/ / where the build/intermediates/flutter/debug/down according to the abi generated app. So through the jar command packaged into a build/intermediates/flutter/debug/libs. The jar file
from("${compileTask.intermediateDir}/${abi}") {
include "*.so"
// Move `app.so` to `lib/<abi>/libapp.so`
rename { String filename ->
return "lib/${abi}/lib${filename}"}}}}// addApiDependencies adds packFlutterAppAotTask to the dependencies
// Like implementation Files ('libs.jar'), then the so inside will be packaged into the standard lib directory when the project executes the standard mergeDebugNativeLibs task
addApiDependencies(project, variant.name, project.files {
packFlutterAppAotTask
})
// The AAR is compiled when the IS-plugin property is built
boolean isBuildingAar = project.hasProperty('is-plugin')
If a Flutter Module relies on an AAR as an existing native Android project, there will be no task attached to the Flutter Module
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")
// Check whether it is a FlutterModule dependency
booleanisUsedAsSubproject = packageAssets && cleanPackageAssets && ! isBuildingAar// Create a copyFlutterAssetsDebug task to copy assets
// Merge intermediates are similar to mergeAssets by copying the assets directory of the step 5 task product into the main package intermediates directory
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. }Copy the code
The above paragraph is intuitive. We will analyze this FlutterTask later in step 5. For step 6, this should be intuitive. The product directory of flutter build APk is as follows:
The JAR is not a class, but an ABI corresponding to app.so, which is the SO compiled by the Dart app. So libs.jar unzip as follows:
This will be added to our project compile dependencies like implementation Files (‘libs.jar’), and then the so inside will be packaged into the standard lib directory when the project executes the standard mergeDebugNativeLibs task. So app.so ends up in the lib directory in apK.
For Step 8, the effects of assets merge and copy operation in the intermediate products of app main package are as follows:
Therefore, the final compilation of the products of Step 6 and Step 8 is the corresponding thing in APK, and the corresponding APK decompression is as follows:
Let’s move on to the rest of the addFlutterTasks method from Step 5 above:
private void addFlutterTasks(Project project) {
/ /... The above analysis, the following analysis
// project will become the app Module if it is named applicationarty
if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant ->
Assemble task
Task assembleTask = getAssembleTask(variant)
// Normal fault tolerance, do not care
if(! shouldConfigureFlutterTask(assembleTask)) {return
}
// Take the copyFlutterAssetsTask task returned by the addFlutterDeps function call defined earlier as a dependency
// The functions and products of this product have been pasted in the picture on the front
Task copyFlutterAssetsTask = addFlutterDeps(variant)
def variantOutput = variant.outputs.first()
def processResources = variantOutput.hasProperty("processResourcesProvider")? variantOutput.processResourcesProvider.get() : variantOutput.processResources processResources.dependsOn(copyFlutterAssetsTask)Execute a file on the products of flutter run or Flutter build APK
// Not much explanation, illustrated below
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("$outputDirectoryStr/${output.outputFileName}")
into new File("${project.buildDir}/outputs/flutter-apk");
rename {
return "${filename}.apk"
}
}
}
}
}
//3. Small points
configurePlugins()
return
}
/ / 3, integration module source dependent manner to the existing project, see https://flutter.cn/docs/development/add-to-app/android/project-setup
// If yes, do the same for the module
String hostAppProjectName = project.rootProject.hasProperty('flutter.hostAppProjectName')? project.rootProject.property('flutter.hostAppProjectName') : "app"
Project appProject = project.rootProject.findProject(":${hostAppProjectName}")
assertappProject ! =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 {
assertappProject.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(":${hostAppProjectName}:merge${appProjectVariant.name.capitalize()}Assets")
assert mergeAssets
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
}
}
configurePlugins()
}
Copy the code
The essence of step 2 in this code analysis is to re-file the standard Android build in the format. This can be seen directly if the split API mode is used. The following diagram shows the effect of Step 2 directly running the Flutter build APk:
For step 3 in the code snippet above, we can examine it in detail:
/** * The dependencies of the flutter are added to pubspec.yaml * and then the flutter pub get is executed, The tool then generates files in the folders that add them to the dependencies of the module */
private void configurePlugins() {
if(! buildPluginAsAar()) {// The.flutter-plugins file in the project root directory
getPluginList().each this.&configurePluginProject
// In the root directory of the project. Flouse-plugins-dependencies file
getPluginDependencies().each this.&configurePluginDependencies
return
}
project.repositories {
maven {
url "${getPluginBuildDir()}/outputs/repo"
}
}
getPluginList().each { pluginName, pluginPath ->
configurePluginAar(pluginName, pluginPath, project)
}
}
Copy the code
This completes the analysis of the entire addFlutterTasks core method. Let’s look at the implementation of FlutterTask. If you don’t know the basics of Gradle Task, you can learn the basics of Gradle Task by using @taskAction.
abstract class BaseFlutterTask extends DefaultTask {
/ /... A bunch of task property declarations, ignored
@OutputFiles
FileCollection getDependenciesFiles() {
FileCollection depfiles = project.files()
// Includes all sources used in the flutter compilation.
depfiles += project.files("${intermediateDir}/flutter_build.d")
return depfiles
}
// key !!!!!!!!!!!!!!!!!!!!!
The core implementation of the entire Flutter Android compilation is here at !!!!
void buildBundle() {
if(! sourceDir.isDirectory()) {throw new GradleException("Invalid Flutter source directory: ${sourceDir}")}/ / 1, the default app, for example to create the build/app/intermediates/flutter
intermediateDir.mkdirs()
//2. Calculate the list of rules for Flutter Assemble
String[] ruleNames;
if (buildMode == "debug") {
ruleNames = ["debug_android_application"]}else if (deferredComponents) {
ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it"}}else {
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it"}}//3
project.exec {
logging.captureStandardError LogLevel.ERROR
Windows is the bin/flutter. Bat file in the flutter SDK path. Unix is the bin/flutter file
executable flutterExecutable.absolutePath
Build. Gradle {source '.. /.. /'} closure, path, in the project root directory
workingDir sourceDir
Use the parameters required by the locally compiled Flutter engine
if(localEngine ! =null) {
args "--local-engine", localEngine
args "--local-engine-src-path", localEngineSrcPath
}
//7. Similar to standard Gradle build parameter print control
if (verbose) {
args "--verbose"
} else {
args "--quiet"
}
// add a bunch of compile parameters
args "assemble"
args "--no-version-check"
args "--depfile"."${intermediateDir}/flutter_build.d"
// The output path of the flutter compilation product
args "--output"."${intermediateDir}"
if(performanceMeasurementFile ! =null) {
args "--performance-measurement-file=${performanceMeasurementFile}"
}
// The entry to the Flutter dart program is lib/main.dart by default
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 {
/ / the default app, for example, to build/app/intermediates/flutter.
@OutputDirectory
File getOutputDirectory() {
return intermediateDir
}
/ / the default app, for example, to build/app/intermediates/flutter/flutter_assets catalog, we have screenshots show that this directory is in front of the product.
@Internal
String getAssetsDirectory() {
return "${outputDirectory}/flutter_assets"
}
// Assets copy operation definition, intermediateDir is getOutputDirectory path
@Internal
CopySpec getAssets() {
return project.copySpec {
from "${intermediateDir}"
include "flutter_assets/**" // the working dir and its files}}// Dart builds the artifact copy operation definition (note: Release and profile patterns are so artifacts), intermediateDir is the getOutputDirectory path
@Internal
CopySpec getSnapshots() {
return project.copySpec {
from "${intermediateDir}"
if (buildMode == 'release' || buildMode == 'profile') {
targetPlatformValues.each {
include "${PLATFORM_ARCH_MAP[targetArch]}/app.so"}}}}// Dependency format parsing generates a collection of file paths
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();
}
// The input source is a collection of pubspec.yaml files for all dependent modules
@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
}
// focus on !!!!!!!
@TaskAction
void build() {
buildBundle()
}
}
Copy the code
As can be seen intuitively, the whole build and compilation is done by executing the Flutter script in the bin directory of the Flutter SDK. A large chunk of the code is just to prepare the configuration information for executing the script. The underlying command for flutter compilation is as follows:
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
Copy the code
This leads to the pure Flutter command script in the SDK.
Under the Flutter SDKbin/flutter
Compile command analysis
Following the analysis above, the last command of the previous section is essentially the script of this section. Let’s turn our attention to the Flutter script in the bin directory of the Flutter SDK as follows:
#! /usr/bin/env bash
#1. If the return value is not zero, the entire script will exit immediately, thus avoiding some dangerous operations of the script.
set -e
#2, clear the CDPATH variable value
unset CDPATH
# On Mac, readlink -f doesn't work, so follow_Links traverses the path of one link at a time, then traverses the CD into the link destination and finds it.
The file system path returned must be in a format available to Dart's URI parser, because the Dart command line tool treats its arguments as file URIs, not file names.
# For example, multiple consecutive slashes should be reduced to a single slash, because double slashes represent the authority of the URI.
function follow_links() (
cd -P "$(dirname -- "The $1")"
file="$PWD/$(basename -- "The $1")"
while [[ -h "$file"]].do
cd -P "$(dirname -- "$file")"
file="$(readlink -- "$file")"
cd -P "$(dirname -- "$file")"
file="$PWD/$(basename -- "$file")"
done
echo "$file"
)
The value of this variable is bin/ Flutter in the root folder of the Flutter SDK
PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
OS="$(uname -s)"
# Platform compatibility
if [[ $OS =~ MINGW.* || $OS =~ CYGWIN.* ]]; then
exec "${BIN_DIR}/flutter.bat" "$@"
fi
After importing the shell script, execute its internal shared::execute method
source "$BIN_DIR/internal/shared.sh"
shared::execute "$@"
Copy the code
It is obvious that we need to focus on Flutter SDKbin/internal/Shared. Sh file, and pay attention to its internal Shared: : the execute method, as follows:
#...
function shared::execute() {
The default value of FLUTTER_ROOT is the FlutterSDK root path
export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"
#2. If it exists, execute the bootstrap script first. The default SDK does not have this file, I guess it is reserved for our customized initial mount.
BOOTSTRAP_PATH="$FLUTTER_ROOT/bin/internal/bootstrap.sh"
if [ -f "$BOOTSTRAP_PATH" ]; then
source "$BOOTSTRAP_PATH"
fi
#3. A bunch of location definitions based on the FlutterSDK path
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, path file platform compatible, routine operation, ignore
case "$(uname -s)" in
MINGW*)
DART="$DART.exe"
PUB="$PUB.bat"
;;
esac
#5. Test whether the account running the script is a super account. If it is, a warning will be given.
if [[ "$EUID"= ="0"&&! -f /.dockerenv &&"$CI"! ="true" && "$BOT"! ="true" && "$CONTINUOUS_INTEGRATION"! ="true"]].then2 > &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
If the git command line configuration is not normal, throw an error.
if ! hash git 2>/dev/null; then2 > &echo "Error: Unable to find git in your PATH."
exit 1
fi
#7 whether FlutterSDK comes from Clone and other tests.
if [[ ! -e "$FLUTTER_ROOT/.git"]].then2 > &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"
Create /bin/cache directory and maintain lock status.
upgrade_flutter 7< "$PROG_NAME"
Echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo 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 = empty
# SNAPSHOT_PATH=FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot
# @=build apk
BIN_NAME="$(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.
"$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
;;
dart*)
"$DART" "$@";; *) > & 2echo "Error! Executable name $BIN_NAME not recognized!"
exit1;;esac
}
Copy the code
As you can see, since Dart is built into the Flutter SDK, both the Flutter and Dart commands can be used when environment variables are configured. The first thing we do after installing the Flutter SDK is to configure the bin directory of the SDK to environment variables. Therefore, the execution of the flutter build APk, flutter upgrade, flutter pub XXX and other commands essentially walk into the above scripts, and the flutter command is just a wrapper of the DART command. So performing flutter pub get is actually equivalent to dart pub get. So suppose we execute the flutter build apk command, which essentially goes up to the script and ends up executing the following command:
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
Copy the code
FLUTTER_SDK_DIR represents the root directory of the Flutter SDK, –packages are sdK-related dependencies, FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot is a compilation of FLUTTER_SDK_DIR/packages/ Flutter_tools. Dart execution of flutter_tools.snapshot is equivalent to executing the main() method of flutter_tools.dart. So the command continues to be simplified as follows:
dart --disable-dart-dev --packages=xxx flutter_tools.dart build apk
Copy the code
That is, any flutter command we execute essentially passes its parameters to the main method of the FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart source code. So really do things in this part of the source code. Here because of the length of the problem is not explained, the back of a special write an analysis, and then associated with this article can be thoroughly understood reading.
The Flutter Plugin android compilation process
The Android part of the Flutter Plugin module, which contains Android code, is a standard native Android library without any additional intervention scripts, so it will not be analyzed. It is just a reminder that when we create a flutter plugin, the project will help us generate an example module by default. The purpose is just to facilitate demo verification when we independently develop the Flutter plugin without our own main project. The general catalogue is as follows:
Flutter Module android compilation process
The Best isolation option for native existing engineering integration of Flutter is the Flutter Module, which results in some of the differences and commonalities in compilation with the Flutter app. In this part, we will focus on analyzing the differences between the Flutter Module and the APP compilation process analyzed above. The common part will not be analyzed.
Android /settings.gradle looks like this:
// App is a test module to verify the nature of a Flutter Module. Finally, a Flutter Module generates an integrable AAR
include ':app'
// Import configuration android/include_flutter. Groovy
rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir, 'include_flutter.groovy'))
Copy the code
Turn your attention to the current flutter Module project. Android /include_flutter. Groovy file:
//1. Find the current project root path using the current script as the coordinate
def scriptFile = getClass().protectionDomain.codeSource.location.toURI()
def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
Import a flutter Module whose name is relative to the current directory
gradle.include ":flutter"
The real implementation of flutter Module android is located under the. Android/flutter directory
gradle.project(":flutter").projectDir = new File(flutterProjectRoot, ".android/Flutter")
// Get the flutter SDK path and import the script
def localPropertiesFile = new File(flutterProjectRoot, ".android/local.properties")
def properties = new Properties()
assert localPropertiesFile.exists(), "❗️The Flutter module doesn't have a '$localPropertiesFile' file. +
"\nYou must run `flutter pub get` in `$flutterProjectRoot`."
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assertflutterSdkPath ! =null."flutter.sdk not set in local.properties"
Apply imports a script from the Flutter SDK directory similar to the previous one
gradle.apply from: "$flutterSdkPath/packages/flutter_tools/gradle/module_plugin_loader.gradle"
Copy the code
Go to the packages/ Flutter_tools /gradle/module_plugin_loader.gradle script file in the Flutter SDK directory. Settings. gradle automatically configures dependency modules for the apply script.
. Then take a look. The android/app/build gradle, you will find that he is a standard android app scripts, dependencies in more than just the above Settings. Gradle flutter in the module, The implementation project (‘ : flutter ‘).
Then look at the real flutter module android related script, namely. Android/flutter/build gradle, as follows:
/ /...
apply plugin: 'com.android.library'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
/ /...
flutter {
source '.. /.. '
}
Copy the code
Zha say? I do not need to explain more, the essence of Flutter. Gradle, which we have analyzed before, is clear.
pubspec.yaml
And related process analysis
Take a look at its internal contents, which are roughly as follows:
# Project name and description
name: f1
description: A new f1 project.
# Want to publish location, delete is to publish to pub.dev
publish_to: 'none'
The versionName and versionCode in the local.properties file under android project will be automatically changed
version: 1.01.+ 10
Dart SDK version range
environment:
sdk: "> = 2.13.0 < 3.0.0"
# build dependencies
dependencies:
flutter:
sdk: flutter
dio: ^ 4.0.0 # Pure DART dependency from pub.dev, i.e. Flutter Package
webview_flutter: ^ 2.0.10 # Plugin dependencies from pub.dev, i.e. Flutter Plugin
f_package: # Pure DART dependencies from local, i.e., Flutter Package
path: . /.. /f_package
f_plugin: # Plugin dependencies from the local, i.e., the Flutter Plugin
path: . /.. /f_plugin
# Development pattern dependency
dev_dependencies:
flutter_test:
sdk: flutter
#...
Copy the code
Yaml file version: 1.0.1+10 will automatically override flutter. VersionName and flutter. VersionCode in Android /local.properties. When we add dependencies, we usually execute the commands including the flutter pub get or flutter pub upgrade to update the dependencies. The logic behind this command is also analyzed by the bin/flutter compilation commands under the flutter SDK.
conclusion
At this point, all aspects of Flutter Android application layer compilation are in place. FLUTTER_SDK_DIR/packages/flutter_tools/flutter_tools.dart: FLUTTER_SDK_DIR/packages/flutter_tools/flutter_tools.