Flutter build aar Android Hybrid

749 阅读3分钟

Flutter 和 Android Native 混合 Android 原生项目接入 flutter 模块

创建 flutter Module 模块

mkdir flutter_hybrid
cd hybrid
flutter create -t module flutter_module

image.png

image.png

cd flutter_module
flutter pub get

image.png

新建Hybrid Android

cd ..
  • open AndroidStudio
  • create AndroidNativeProject「hybridAndroid」

image.png

集成Flutter_module

集成flutter module 一般有两种方式 aar 集成 或 module 源码集成

源码集成 导入flutter_module到hybridAndroid

hybridAndroid/setting.gradle

//dependencyResolutionManagement {
//    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
//    repositories {
//        google()
//        mavenCentral()
//        jcenter() // Warning: this repository is going to shut down soon
//    }
//}
rootProject.name = "hybridAndroid"
include ':app'
 // assumed existing content
setBinding(new Binding([gradle: this]))                               
evaluate(new File(                                                     
        settingsDir.parentFile,                                             
        'flutter_module/.android/include_flutter.groovy'                        
))       

hybridAndroid/build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.3"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

//添加这一项 排除接入错误
allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

hybridAndroid/app/build.gradle

dependencies {
    implementation project(':flutter') //添加flutter 模块的引用
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

新建HybridActivity 继承 FlutterActivity MainActivity 模拟跳转到HybridActivity

package com.feima.hybridandroid

import io.flutter.embedding.android.FlutterActivity

class HybridActivity: FlutterActivity() {
}

package com.feima.hybridandroid

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        startActivity(Intent(this,HybridActivity::class.java))
    }
}

output.gif

aar 集成导入flutter_module build aar到hybridAndroid

cd flutter_module
flutter build aar

image.png 根据输出内容提示 导入到hybridAndroid

hybridAndroid/setting.gradle

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven {
            allowInsecureProtocol = true
            url '/Users/zangmingzheng/Desktop/flutter_hybrid/flutter_module/build/host/outputs/repo'
        }
        maven {
            url 'https://storage.googleapis.com/download.flutter.io'
        }
    }
}
rootProject.name = "hybridAndroid"
include ':app'
// // assumed existing content
//setBinding(new Binding([gradle: this]))
//evaluate(new File(
//        settingsDir.parentFile,
//        'flutter_module/.android/include_flutter.groovy'
//))

hybridAndroid/build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.3"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

//allprojects {
//    repositories {
//        google()
//        mavenCentral()
//    }
//}

task clean(type: Delete) {
    delete rootProject.buildDir
}

hybridAndroid/app/build.gradle

dependencies {
    //    implementation project(':flutter')
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
    //profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
    //releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}

aar 导入到Native工程 或者源码导入 无法做到真正的分离版本控制 ,所以要要建立maven 私仓 版本控制aar

aar生成版本上传到maven私仓

  • 打包flutter aar debug、release other
  • upload maven 「仓库地址 版本信息」
  • 调用shell 命令

image.png

cd config
sh flutter.sh
#!/usr/bin/env sh
flutter pub get
cd ../.android
./gradlew build
./gradlew \
  -I=../config/arr_build.gradle \
  -Pmaven-url=xxxx\ 
  -Pmaven-user=xxxx \
  -Pmaven-pwd=xxxx \
  -Pis-plugin=false \
  -PbuildNumber=1.0.3 \
  -Ptarget-platform=android-arm,android-arm64,android-x64 assembleAarRelease
  • maven-url 私仓地址
  • maven-user
  • maven-pwd
  • buildNumber Version
// This script is used to initialize the build in a module or plugin project.
// During this phase, the script applies the Maven plugin and configures the
// destination of the local repository.
// The local repository will contain the AAR and POM files.

void configureProject(Project project, String mavenUrl, String mavenUser, String mavenPwd, String version) {
    if (!project.hasProperty("android")) {
        throw new GradleException("Android property not found.")
    }
    if (!project.android.hasProperty("libraryVariants")) {
        throw new GradleException("Can't generate AAR on a non Android library project.")
    }

    project.apply plugin: "maven"

    // Snapshot versions include the timestamp in the artifact name.
    // Therefore, remove the snapshot part, so new runs of `flutter build aar` overrides existing artifacts.
    // This version isn't relevant in Flutter since the pub version is used
    // to resolve dependencies.

    project.version = version
    if (mavenUrl.startsWith("file:")) {
        //本地路径时,原逻辑
        project.version = project.version.replace("-SNAPSHOT", "")
    }


    project.android.libraryVariants.all { variant ->
        addAarTask(project, variant)
    }

    project.uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: mavenUrl) {
                    if (mavenUser != null) {
                        authentication(userName: mavenUser, password: mavenPwd)
                    }
                }
                //默认本地路径:
                //repository(url: "file://${outputDir}/outputs/repo")
            }
        }
    }
    if (!project.property("is-plugin").toBoolean()) {
        return
    }

    if (project.hasProperty('localEngineOut')) {
        // TODO(egarciad): Support local engine.
        // This most likely requires refactoring `flutter.gradle`, so the logic can be reused.
        throw new GradleException(
                "Local engine isn't supported when building the plugins as AAR. " +
                        "See: https://github.com/flutter/flutter/issues/40866")
    }

    // This is a Flutter plugin project. Plugin projects don't apply the Flutter Gradle plugin,
    // as a result, add the dependency on the embedding.
    project.repositories {
        maven {
            url "https://storage.googleapis.com/download.flutter.io"
        }
    }
    String engineVersion = flutterEngineVersion()
    project.dependencies {
        // Add the embedding dependency.
        //使用api方式打入aar
        compileOnly("io.flutter:flutter_embedding_release:1.0.0-$engineVersion") {
            //    api("io.flutter:flutter_embedding_release:1.0.0-$engineVersion") {
            // We only need to expose io.flutter.plugin.*
            // No need for the embedding transitive dependencies.
            transitive = true
        }
    }
}

void configurePlugin(Project project, String outputDir, String mavenUser, String mavenPwd) {
    if (!project.hasProperty("android")) {
        // A plugin doesn't support the Android platform when this property isn't defined in the plugin.
        return
    }
    //flutter plugin 版本号
    File pubspecFile = project.file("../pubspec.yaml")
    String versionLine = pubspecFile.readLines().find { line->
        line.startsWith("version:")
    }
    def pluginVersion = versionLine.split(":")[1].trim()
    println("configurePlugin: " + project.toString() + " v" + pluginVersion)
    configureProject(project, outputDir, mavenUser, mavenPwd, pluginVersion)
}

void addAarTask(Project project, variant) {
    String variantName = variant.name.capitalize()
    String taskName = "assembleAar$variantName"
    project.tasks.create(name: taskName) {
        // This check is required to be able to configure the archives before `uploadArchives` runs.
        if (!project.gradle.startParameter.taskNames.contains(taskName)) {
            return
        }
        project.uploadArchives.repositories.mavenDeployer {
            pom {
                artifactId = "${project.name}_${variant.name.toLowerCase()}"
            }
        }
        overrideDefaultPublishConfig(project, variant)
        // Generate the Maven artifacts.
        finalizedBy "uploadArchives"
    }
}

// This method mimics the logic in AGP when `android.defaultPublishConfig` is set in `build.gradle`:
// https://android.googlesource.com/platform/tools/base/+/studio-master-dev/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/variant/VariantHelper.java
//
// Unfortunately, `android.defaultPublishConfig` cannot be overriden at this point since
// AGP already run this code.
void overrideDefaultPublishConfig(Project project, variant) {
    String variantName = variant.name.capitalize()
    Task bundle = project.tasks.findByName("bundle${variantName}Aar")
    // gradle:3.2.0
    if (bundle == null) {
        bundle = project.tasks.findByName("bundle${variantName}") // gradle:3.1.0
    }
    if (bundle == null) {
        throw new GradleException("Can't generate AAR for variant ${variantName}.")
    }
    // Clear the current archive artifacts since the artifacts are based on `android.defaultPublishConfig`.
    project.configurations["archives"].artifacts.clear()
    // Add the artifact that will be published.
    project.artifacts.add("archives", bundle)

    def scopeMappings = project.uploadArchives.repositories.mavenDeployer.pom.scopeMappings
    // Clear the scope mappings added by AGP since they are based on the current `android.defaultPublishConfig`.
    scopeMappings.mappings.clear()
    // Add the new mappings.
    for (Configuration configuration : flattenConfiguration(variant.runtimeConfiguration)) {
        scopeMappings.addMapping(/* priority = */ 300, configuration, "compile")
    }
}

Set<Configuration> flattenConfiguration(Configuration configuration) {
    Set<Configuration> configs = [configuration]
    for (Configuration extend : configuration.extendsFrom) {
        configs.addAll(flattenConfiguration(extend))
    }
    return configs
}

projectsEvaluated {
    assert rootProject.hasProperty("is-plugin")
    if (rootProject.property("is-plugin").toBoolean()) {
        assert rootProject.hasProperty("maven-url")
        // In plugin projects, the root project is the plugin.
        configureProject(rootProject, rootProject.property("maven-url"),
                rootProject.property("maven-user"), rootProject.property("maven-pwd"),
                project.property("buildNumber")
        )
        return
    }
    // The module project is the `:flutter` subproject.
    Project moduleProject = rootProject.subprojects.find { it.name == "flutter" }

    assert moduleProject != null
    assert moduleProject.hasProperty("maven-url")
    configureProject(moduleProject, moduleProject.property("maven-url"),
            moduleProject.property("maven-user"), moduleProject.property("maven-pwd"),
            moduleProject.property("buildNumber")
    )

    // Gets the plugin subprojects.
    Set<Project> modulePlugins = rootProject.subprojects.findAll {
        it.name != "flutter" && it.name != "app"
    }
    // When a module is built as a Maven artifacts, plugins must also be built this way
    // because the module POM's file will include a dependency on the plugin Maven artifact.
    // This is due to the Android Gradle Plugin expecting all library subprojects to be published
    // as Maven artifacts.
    String mavenUrl = moduleProject.property("maven-url")
    String version = moduleProject.property("buildNumber")

    println("Version: $version")
    println("MavenUrl: " + mavenUrl)

    //输出 配置
    String buildMode = moduleProject.gradle.startParameter
            .taskNames.find { it.startsWith("assembleAar") }.substring(11)
    println("BuildMode: $buildMode")

    println("================================================================================")
    //配置插件
    modulePlugins.each { pluginProject ->
        configurePlugin(pluginProject, mavenUrl,
                moduleProject.property("maven-user"), moduleProject.property("maven-pwd"))
        moduleProject.android.libraryVariants.all { variant ->
            // Configure the `assembleAar<variantName>` task for each plugin's projects and make
            // the module's equivalent task depend on the plugin's task.
            String variantName = variant.name.capitalize()
            moduleProject.tasks.findByPath("assembleAar$variantName")
                    .dependsOn(pluginProject.tasks.findByPath("assembleAar$variantName"))
        }
    }
    //结束
    println("================================================================================")

    String mUrl = mavenUrl
    if (mavenUrl.startsWith("file://")) {
        mUrl = mavenUrl.substring(7)
    }

    String groupId = moduleProject.group

    println("""
1. 添加maven地址
repositories {
  maven {
      url '${mUrl}'
  }
  maven {
      url 'https://storage.googleapis.com/download.flutter.io'
  }
}
2. 添加依赖
dependencies {
  implementation '${groupId}:flutter_${buildMode.toLowerCase()}:${version}'
}
""")
}

参考:优雅地将Flutter项目打包aar上传maven

咸鱼-Flutter混合工程改造实践