The official plan
Dev /docs/develo… There are two access modes:
- Access in Module mode
- Access the system in local Maven mode
Both access methods are relatively simple. The premise is to create the Flutter Module first (the code for the Flutter is written here).
Access in Module mode
Android project settings.gradle:
setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'my_flutter/.android/include_flutter. Groovy ')Copy the code
This magically adds a flutter Module to the project. You can introduce this flutter module into the project. For example, in your app build.gradle add:
implementation project(':flutter')
Copy the code
Analysis of the
SetBinding and evaluate are groovy syntax. What you do here is run the include_flutter. Groovy script; The setBinding will pass the Gradle environment into include_flutter. Groovy because it needs to use the Gradle environment. The groovy file runs in a Script object with a binding property that stores variables of the current environment (including variables declared by the current Script and parameters passed by the startup Script). Evaluate passes the binding of the current Script to the next Script. Here is the key code in include_flutter. Groovy:
gradle.include ":flutter"
gradle.project(":flutter").projectDir = new File(flutterProjectRoot, ".android/Flutter")
def flutterSdkPath = properties.getProperty("flutter.sdk")
gradle.apply from: "$flutterSdkPath/packages/flutter_tools/gradle/module_plugin_loader.gradle"
Copy the code
Add the flutter module to the Android project and import the module_plugin_loader.gradle script fragment. Take a quick look at the key code in module_plugin_loader.gradle
def pluginsFile = new File(moduleProjectRoot, '.flutter-plugins-dependencies')
if (pluginsFile.exists()) {
def object = new JsonSlurper().parseText(pluginsFile.text)
object.plugins.android.each { androidPlugin ->
def pluginDirectory = new File(androidPlugin.path, 'android')
include ":${androidPlugin.name}"
project(":${androidPlugin.name}").projectDir = pluginDirectory
}
}
Copy the code
Simply add all the plug-in modules to the Android project, then where do these plug-in modules come from? The official plugin hosting platform for Flutter is pub.dev. Flutter uses the configuration file Pubspec. yaml to manage tripartite plug-ins (similar to the front-end NPM).
Dependencies: path_provider: ^ 1.6.18Copy the code
Then executing pub. get does two things:
- Download the source code of the plugin to the local directory, flutterRoot/.pub-cache
- Update the. Flutter -plugins and. Flutter -plugins-dependencies files
.flutter-plugins and.flutter-plugins-dependencies store the name of the plugin and the corresponding local project address as json.
Path_provider_macos = / Users/liuxiaoshuai/flutter /. Pub - cache/hosted/pub. The flutter - IO. Cn/path_provider_macos - 0.0.4 plus 3 /Copy the code
Pub management is not as smart as Gradle dependency management.
To summarize what settings.gradle adds to the configuration:
- Android /Flutter project in include FlutterModule
- Include FlutterModule Includes the Android module in the flutter project path contained in the flutter-plugins file
- The build.gradle configuration execution phase of all projects depends on the Flutter project, which executes the configuration phase first
How did the Dart code and engine we wrote in FlutterModule get incorporated into the Android project? The answer lies in the build.gradle of the Flutter Module, which contains one particularly critical line of code
def flutterRoot = localProperties.getProperty('flutter.sdk')
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
Copy the code
All the rest of the work is done in flutter. Gradle. The code in flutter. Gradle is too long and I won’t post it here, but the code inside flutter is very important. Here’s a summary of what’s going on inside:
- Select a Flutter engine that matches the corresponding architecture
- Build dependencies for inserting the Flutter Plugin (default AAR dependency)
- Hook mergeAssets/processResources Task, advance execution FlutterTask, called flutter commands to compile the Dart layer code construct flutter_assets product, and copy to the assets directory product code (Dart)
Here the plug-in and dart code are added to the Android project
Access the system in local Maven mode
The corresponding product should be typed by the flutter shell script. The common commands are as follows:
- flutter build aar
- Flutter build aar –no-debug –no-profile
- Flutter build aar–build-number=2.0 // Version control
After executing the command, view the product at the location shown below:
There is no additional configuration in settings.gradle to use the product:
repositories { maven { url '.. / FlutterModule/build/host/outputs/repo '/ / pay attention to the correctness of the path dependencies}} {implementation 'com. Example. Flutter_module: flutter_release: 1.0'}Copy the code
Defects in the official scheme
Module access:
- Team members are required to install the flutter development environment, which is too invasive for students who do not develop flutter. At the same time, the new students need to install more environment, increasing the burden.
- The CI process needs to be modified. The original CI process must not include the FLUTTER process.
Local Maven access:
- Bad version management
- Local artifacts need to be copied to other students who do not develop flutter, otherwise you will essentially have to install the Flutter development environment.
Consider taking advantage of the creation of a Flutter Build AAR and submitting it to the company’s private storehouse so that all students can download and use it normally. In theory it is possible to iterate through all folders under build/host/outputs/repo and commit the artifacts in turn. The scheme is not practiced, so you can try it.
Brand new scheme
My solution is to build and upload the product myself, the whole operation is a shell script, and will depend on external configuration.
The first configuration file is gradle.properties
# remote maven url and account MAVEN_URL MAVEN_ACCOUNT_PWD = = http://172.16.9.30:8081/artifactory/ MAVEN_ACCOUNT_NAME = * * * * * * GROUP = com. LXS. Flutter VERSION_NAME = 0.0.8Copy the code
Used to set the address, user name and password of the private warehouse. The group and version of the product
The second configuration file is build.gradle
Jcenter buildscript {repositories {Google () ()} dependencies {classpath 'com. Android. View the build: gradle: 3.5.0' Classpath "org. Jfrog. Buildinfo: build - info - extractor - gradle: 4.8.1"}} allprojects {repositories {Google jcenter () ()} apply plugin: 'com.jfrog.artifactory' apply plugin: 'maven-publish' } task clean(type: Delete) { delete rootProject.buildDir } subprojects { project.afterEvaluate { project.plugins.withId('com.android.library') { project.group = GROUP project.version = VERSION_NAME def mavenScriptPath = project.rootProject.file('./config/flutter_jfrog.gradle') project.apply from: mavenScriptPath } } }Copy the code
Note that the Android directory is rebuilt after every pub get, so it cannot be modified directly in the. Android project, which is overwritten by an external configuration file. Build. Gradle is configured for JFrog because the product upload relies on the JFrog plugin. There is also group and version correction, because adding the plugin binaries to the Flutter Module will use groups and versions. The code is in flutter. Gradle
private void configurePluginAar(String pluginName, String pluginPath, Project project) { File pluginBuildFile = project.file(Paths.get(pluginPath, "android", "build.gradle")); if (! pluginBuildFile.exists()) { throw new GradleException("Plugin $pluginName doesn't have the required file $pluginBuildFile.") } Matcher groupParts = GROUP_PATTERN.matcher(pluginBuildFile.text) String groupId = groupParts[0][1] File pluginSettings = project.file(Paths.get(pluginPath, "android", "settings.gradle")); if (! pluginSettings.exists()) { throw new GradleException("Plugin $pluginName doesn't have the required file $pluginSettings.") } Matcher projectNameParts = PROJECT_NAME_PATTERN.matcher(pluginSettings.text) String artifactId = "${projectNameParts[0][1]}_release" project.dependencies.add("api", "$groupId:$artifactId:+") }Copy the code
The third configuration file is flutter_jfrog.gradle
import com.sun.tools.classfile.Dependency task androidSourcesJar(type: Jar) { classifier = 'sources' from android.sourceSets.main.java.srcDirs } assemble.dependsOn androidSourcesJar publishing { publications { aar(MavenPublication) { groupId = GROUP version = VERSION_NAME artifactId = project.name artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") artifact androidSourcesJar pom.withXml { def dependenciesNode = asNode().appendNode('dependencies') def compileTimeDependencies = configurations.implementation.allDependencies.withType(ModuleDependency) + configurations.releaseImplementation.allDependencies.withType(ModuleDependency) appendDependencies(compileTimeDependencies, dependenciesNode) } } } } artifactory { contextUrl = MAVEN_URL publish { repository { repoKey = 'gradle-dev-local' username = MAVEN_ACCOUNT_NAME password = MAVEN_ACCOUNT_PWD } defaults { // Tell the Artifactory Plugin which artifacts should be published to Artifactory. publications('aar') publishArtifacts = true // Properties to be attached to the published artifacts. properties = ['qa.level': 'basic', 'dev.team': 'core'] // Publish generated POM files to Artifactory (true by default) publishPom = true } } } ext { appendDependencies = { Set<Dependency> compileTimeDependencies, DependenciesNode - > compileTimeDependencies. Each {/ / filter library reference if (it version! = "unspecified") { def dependencyNode = dependenciesNode.appendNode('dependency') dependencyNode.appendNode('groupId', it.group) dependencyNode.appendNode('artifactId', it.name) dependencyNode.appendNode('version', it.version) if (! it.excludeRules.isEmpty()) { def exclusionsNode = dependencyNode.appendNode('exclusions') it.excludeRules.each { rule -> def exclusionNode = exclusionsNode.appendNode('exclusion') exclusionNode.appendNode('groupId', rule.group) exclusionNode.appendNode('artifactId', rule.module ? : '*')}}}}}}Copy the code
This is mainly about collecting and uploading products. The releaseImplementation is not included in the implementation dependencies. You need to pay attention to the aggregate implementation and releaseImplementation. (Because the engine dependencies in the Flutter Module are releaseImplementation)
Next, the script is analyzed, and the first step is to update the version number. Two methods are available: 1. Script parameters 2. Automatic upgrade Cancel automatic upgrade if script parameters are added
num=$# if [ $num -eq 0 ]; then updateVersion else v=$(grep VERSION_NAME configs/gradle.properties|cut -d'=' -f2) sed -i '' 's/VERSION_NAME =' $v '/ VERSION_NAME =' $1 '/ g' configs/gradle. Properties echo 'update version number success... ' fiCopy the code
The version number is automatically upgraded. The automatic upgrade is based on the last version
The function updateVersion () = ${v (grep VERSION_NAME configs/gradle properties | the cut - d '=' - f2) echo the old version v1 = $$v (echo | awk '{split("'$v'",array,"."); print array[1]}') v2=$(echo | awk '{split("'$v'",array,"."); print array[2]}') v3=$(echo | awk '{split("'$v'",array,"."); print array[3]}') y=$(expr $v3 + 1) if [ $y -ge 10 ]; then y=$(expr $y % 10) v2=$(expr $v2 + 1) fi if [ $v2 -ge 10 ]; Then v2=$(expr $v2%10) v1=$(expr $v1 + 1) fi vv=$v1"."$v2"." 's/VERSION_NAME='$v'/VERSION_NAME='$vv'/g' configs/gradle.properties if [ $? -eq 0 ]; Then echo '' else echo 'failed to update version number... ' exit fi }Copy the code
The second step is to copy the configuration to the. Android project
if [ -d '.android/config/' ]; Then echo '. Android /config 'else: mkdir .android/config fi cp configs/gradle.properties .android/gradle.properties cp configs/flutter_jfrog.gradle .android/config/flutter_jfrog.gradle cp configs/build.gradle .android/build.gradleCopy the code
In the third step, each Plugin module launches aar separately to build and collect products and upload
for line in $(cat .flutter-plugins | grep -v '^ *#') do plugin_name=${line%%=*} plugin_path=${line##*=} res=$(doesSupportAndroidPlatform ${plugin_path}) if [ $res -eq 0 ]; then ./gradlew "${plugin_name}":clean ./gradlew "${plugin_name}":assembleRelease ./gradlew "${plugin_name}":artifactoryPublish fi doneCopy the code
The fourth step is to upload the flutter module to build and collect products
./gradlew clean assembleRelease
./gradlew flutter:artifactoryPublish
Copy the code
After the product is uploaded to the private warehouse, it can be used normally. The use mode is similar to local Maven. Another task is the configuration of source code and binary switching. The development phase is still dependent on modules because debugging and hot reload are easier. The project remote branch maintains binary dependencies and opens source dependencies through local configuration, which is the least intrusive way to do it, such as adding configuration to local.properties
flutterAar=false
Copy the code
Settings. gradle has been modified
def localProperties = readPropertiesIfExist(new File("local.properties")) if (! localProperties.getProperty("flutterAar", "true").toBoolean()) { def flutterFile = new File(settingsDir.parentFile, 'flutter_module/.android/include_flutter.groovy') if (flutterFile.exists()) { setBinding(new Binding([gradle: this])) evaluate(flutterFile) } else { throw new GradleException("flutter module does not exit,please check the path") } } private static Properties readPropertiesIfExist(File propertiesFile) { Properties result = new Properties() if (propertiesFile.exists()) { propertiesFile.withReader('UTF-8') { reader -> result.load(reader) } } return result }Copy the code
Change the place where module is added to
def localProperties = readPropertiesIfExist(new File("local.properties")) def flutterAar = localProperties.getProperty("flutterAar", "True"). ToBoolean () if (flutterAar) {API com. LXS. Flutter, flutter: 0.0.8} else {API project (' : flutter ')}Copy the code
Small optimization
Now every time a script is executed, all the plug-ins are going through aArs, version upgrades, and uploads, which can seem time-consuming and pointless. Not all plugin modules need to do this every time, and it is not necessary if the plugin content is not updated. We can use a file to record the plugin and all versions of it, and then compare them to.flutter-plugins. Updates to flutter- aArs, upgrades, and uploads are performed, while those that are not updated remain in their original versions (or each plugin can upload maven individually, according to.flutter-plugins). Git dependencies are the Achilles heel of this approach and need to be checked further (perhaps by comparing the file summary).
A better solution
Github has a multi-module aar solution github.com/adwiv/andro… This might have been a good idea to incorporate the AAR of plugin Module with the AAR of a flutter Module, but it was not implemented because we didn’t know how to incorporate all three dependencies inside the Module. In fact, fat-AAR can also incorporate three-party dependencies into the entire AAR, potentially invalidating gradle’s default resolution of dependency conflicts (the default is to use older versions of dependencies). I don’t know whether it can be solved by merging POM files. These are all I can think of, and I hope to provide you with some ideas.