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.gradleSource 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.gradleSource 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/flutterCompile 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.yamlAnd 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.