flutter build aar時(shí)"Android property not found."問(wèn)題

android端在執(zhí)行flutter build aar命令時(shí),有時(shí)候會(huì)遇到如下錯(cuò)誤(問(wèn)題版本1.17.1,目前最新的flutter版本1.22.4已不存在此問(wèn)題)

FAILURE: Build failed with an exception.

  • Where:

    Initialization script '....\packages\flutter_tools\gradle\aar_init_script.gradle' line: 15

    aar_init_script.gradle中修改相關(guān)代碼如下
void configureProject(Project project, String outputDir) {
    if (!project.hasProperty("android")) {
        throw new GradleException(project.toString())
        // throw new GradleException("Android property not found.")
    }
    ...
}

再次執(zhí)行flutter build aar,可定位到具體出錯(cuò)插件項(xiàng)目。如:
引入path_provider:1.6.8的項(xiàng)目,執(zhí)行以上命令時(shí)會(huì)有如下錯(cuò)誤信息提示

  • What went wrong:

    project ':path_provider_macos'

    由此猜測(cè)可能是某些插件依賴(lài)了macos相關(guān),查看path_provider-1.6.8中的pubspec.yaml,可發(fā)現(xiàn)其依賴(lài)了path_provider_macos
dependencies:
  flutter:
    sdk: flutter
  path_provider_platform_interface: ^1.0.1
  path_provider_macos: ^0.0.4

目前我們的項(xiàng)目中這三個(gè)插件path_provider:1.6.8、shared_preferences: 0.5.7+2、webview_flutter: 0.3.22+1
都會(huì)有同樣的"Android property not found."問(wèn)題。

在網(wǎng)上搜了一下,大部分解決辦法都是說(shuō)臨時(shí)刪除掉.pub-cache/hosted/pub.flutter-io.cn下出錯(cuò)的相關(guān)包,
這個(gè)的確可以解決,但下次執(zhí)行flutter clean, flutter pub get后,再次執(zhí)行flutter build aar時(shí)還會(huì)有同樣的問(wèn)題,
于是想看能否看下源碼了解下有沒(méi)有好的解決辦法。

在flutter的packages/flutter_tools/gradle/flutter.gradle中,看到如下項(xiàng)目配置的代碼

/**
 * Configures the Flutter plugin dependencies.
 *
 * The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
 * the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location.
 * Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject.
 */
private void configurePlugins() {
    if (!buildPluginAsAar()) {
        getPluginList().each this.&configurePluginProject
        getPluginDependencies().each this.&configurePluginDependencies
        return
    }
    project.repositories {
        maven {
            url "${getPluginBuildDir()}/outputs/repo"
        }
    }
    getPluginList().each { pluginName, pluginPath ->
        configurePluginAar(pluginName, pluginPath, project)
    }
}

配置插件依賴(lài)部分的代碼如下,其會(huì)過(guò)濾掉不支持android platform的依賴(lài)插件

// Add the dependencies on other plugin projects to the plugin project.
// A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
// making the Gradle plugin project A depend on the Gradle plugin project B.
private void configurePluginDependencies(Object dependencyObject) {
    ...
    dependencyObject.dependencies.each { pluginDependencyName ->
        ...
        Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName")
        if (dependencyProject == null ||
            !doesSupportAndroidPlatform(dependencyProject.projectDir.parentFile.path)) {
            return
        }
        // Wait for the Android plugin to load and add the dependency to the plugin project.
        pluginProject.afterEvaluate {
            pluginProject.dependencies {
                implementation dependencyProject
            }
        }
    }
}

關(guān)鍵代碼doesSupportAndroidPlatform如下,主要是判斷插件項(xiàng)目中是否有android目錄和android/build.gradle文件

// Returns `true` if the given path contains an `android/build.gradle` file.
//
// TODO(egarciad): Fix https://github.com/flutter/flutter/issues/39657.
// Android Studio may create empty android directories due to the logic in <app>/android/settings.gradle,
// which imports all plugins regardless of whether they support the android platform.
private Boolean doesSupportAndroidPlatform(String path) {
    File editableAndroidProject = new File(path, 'android' + File.separator + 'build.gradle')
    return editableAndroidProject.exists()
}

根據(jù)之前代碼configurePlugins中的注釋?zhuān)覀兇蟾帕私忭?xiàng)目中插件配置流程如下

1.首先在pubspec.yaml中添加相關(guān)插件

2.執(zhí)行flutter pub get命令生成.flutter-plugins文件

3.項(xiàng)目的settings.gradle加載每一個(gè)插件的android目錄作為子項(xiàng)目

在.android項(xiàng)目中,我們先查看生成的settings.gradle文件

// Generated file. Do not edit.
include ':app'

rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir, 'include_flutter.groovy'))

再查看生成的文件include_flutter.groovy,部分代碼如下

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile()
    gradle.include ":$name"
    gradle.project(":$name").projectDir = pluginDirectory
}

其先從.flutter-plugins文件中加載插件,然后配置插件項(xiàng)目作為subproject
其中可以看到,在添加插件的android目錄作為子目錄時(shí),并未判斷插件是否含有android目錄,
這樣上面的path_provider_macos就會(huì)被添加到子項(xiàng)目中作為依賴(lài)項(xiàng)目,然后在構(gòu)建時(shí)校驗(yàn)失敗。

projectsEvaluated {
    ...
    // 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.
    modulePlugins.each { pluginProject ->
        configureProject(pluginProject, moduleProject.property("output-dir"))
        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"))
        }
    }
}

大概了解依賴(lài)配置流程后,我們可以想到如下解決辦法

1.修改include_flutter.groovy中的插件項(xiàng)目配置規(guī)則,加上android目錄的校驗(yàn)

2.修改aar_init_script.gradle中的配置規(guī)則

具體修改如下:

1.include_flutter.groovy是通過(guò)模版生成的,修改的話可修改flutter下packages/flutter_tools/templates/module/android/library_new_embedding/include_flutter.groovy.copy.tmpl

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile()
    if (pluginDirectory.exists()) {
        gradle.include ":$name"
        gradle.project(":$name").projectDir = pluginDirectory
    } else {
        println("plugins " + name + " pluginDirectory : " + pluginDirectory + " does not exist")
    }
}

2.修改flutter下文件packages/flutter_tools/gradle/aar_init_script.gradle中projectsEvaluated配置部分代碼:

    Set<Project> modulePlugins = rootProject.subprojects.findAll {
        it.name != "flutter" && it.name != "app" && it.hasProperty("android")
    }

以上兩個(gè)方式都需要修改flutter中的代碼,暫時(shí)沒(méi)找到更好的解決方法,有了解其他解決辦法的,也希望分享下。

追記:
flutter 1.22.4版本生成的include_flutter.groovy中代碼變更

gradle.apply from: "$flutterSdkPath/packages/flutter_tools/gradle/module_plugin_loader.gradle"

查看module_plugin_loader.gradle代碼可見(jiàn)其中添加插件依賴(lài)時(shí),檢查了是否支持android

def pluginsFile = new File(moduleProjectRoot, '.flutter-plugins-dependencies')
if (pluginsFile.exists()) {
    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
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容