返回

搞定 Flutter Positivo APK 构建失败: Gradle 与签名详解

java

搞定 Flutter 集成 Positivo 终端的 APK 构建失败

写 Flutter 应用,要集成 Stone 服务跑在 Positivo 支付终端上?这挺常见的。但有时候,满心欢喜敲下 flutter run --flavor dev,想生成个带 Positivo 凭证的 APK,结果终端给你甩来一堆错误,告诉你 APK 没找到,或者 Gradle 构建失败了。就像下面这样:

错误截图 1
错误截图 2

你可能已经按照 Positivo 的文档(比如那篇《Positivo: Build Process for Application on Debug Terminal》)一步步配置了,检查了 local.properties 里的 Flutter SDK 路径也没问题,甚至跑了 flutter clean 清理缓存,可错误依旧。别急,这问题多半出在 Android 项目的 Gradle 配置或者 Positivo 的签名设置上。咱们来捋一捋。

问题根源分析

碰上这种构建错误,原因通常跑不出这几个圈:

  1. Gradle 配置混淆: 这是最常见的“肇事者”。build.gradle 文件里管理着 Flavors(不同渠道或环境的版本)、Build Types(构建类型,如 debug/release)和 Signing Configs(签名配置)。当你要引入像 Positivo 这样的特殊签名需求时,这里的配置稍微有点乱,就可能导致 Gradle 不知道该用哪个签名、为哪个版本构建,最终引发错误。特别是当 Flavors 和 Build Types 的签名设置互相“打架”时。
  2. Positivo 签名脚本问题: 你应用了一个外部的 Gradle 脚本来处理 Positivo 签名 (apply from: file('../positivo/positivo-signing-config.gradle'))。这个脚本本身可能有问题:路径不对、缺少必要的证书文件、脚本内部逻辑错误,或者它依赖的环境变量没设置好。
  3. Flavor 与 Build Type 组合错误: flutter run --flavor dev 命令不仅指定了 Flavor (dev),它还会关联一个 Build Type。默认情况下,flutter run 使用 debug Build Type。你需要确保 dev Flavor 和 debug Build Type 的组合能正确关联到 positivo 签名配置。如果你的 build.gradledebug Build Type 没有明确使用 positivo 签名,或者 dev Flavor 的配置不正确,就会出问题。
  4. 环境问题: 虽然你检查了 local.properties,但 Gradle 缓存损坏、JDK 版本不兼容或配置错误,也可能掺和进来捣乱。

从你提供的 build.gradle 文件来看,有几个地方值得关注:

  • Flavor 配置 : standarddev 两个 flavor 都强制使用了 signingConfigs.positivostandard 设为 isDefault true,这可能在你没指定 flavor 时产生影响。你需要确认 dev flavor 使用 positivo 签名是你确实想要的。
  • Build Type 配置 :
    • release build type 使用的是 signingConfigs.debug,这很奇怪。通常 release 应该用专门的发布签名。
    • 你定义了一个名为 positivo 的 build type,它基于 release (initWith release),但又把签名设置成了 null (signingConfig null)。这个 positivo build type 的目的看起来和通过 flavor 应用 Positivo 签名的做法有点冲突或冗余。它不仅没用 Positivo 签名,反而清空了签名设置。这很可能是问题的关键所在。

看起来,你的配置试图同时通过 Flavor 和一个奇怪的 Build Type 来处理 Positivo,造成了混乱。

可行的解决方案

咱们来试试几个方法,逐一排查。

方案一:清理并简化 build.gradle 配置

既然问题很可能出在 Gradle 配置的复杂性和潜在冲突上,那第一步就是简化它,让意图更清晰。

原理与作用:

Gradle 构建是基于 build.gradle 文件指令的。简化配置,移除冗余或冲突的设置,特别是围绕 signingConfig 的部分,能让 Gradle 更准确地理解你的构建意图。移除那个看起来有问题的 positivo build type,专注于通过 Flavor 应用签名,通常是更标准的做法。

操作步骤:

  1. 备份: 先备份你的 android/app/build.gradle 文件,以防万一。

  2. 修改 build.gradle:

    • 移除 positivo Build Type: 删除 buildTypes 下的整个 positivo 代码块。它看起来与你的目标(通过 flavor 应用 Positivo 签名)相悖。
    • 修正 release Build Type 的签名: 如果你有正式的发布签名密钥,应该在这里配置。如果暂时没有,或者 Positivo 就是你的发布签名,那么这里应该指向 signingConfigs.positivo 或你定义的发布签名配置。但暂时为了解决 flutter run --flavor dev 的问题(通常关联 debug build),可以先保留 signingConfigs.debug 或者也指向 signingConfigs.positivo(如果 Positivo 签名也用于调试)。关键是确保 debug build type 的签名配置是正确的。
    • 检查 Flavors: 确认 dev flavor 正确使用了 signingConfigs.positivo。考虑一下 standard flavor 是否真的也需要用 positivo 签名,或者它应该用 debug 签名?如果 standard 只是一个普通的开发版本,或许应该指向 signingConfigs.debug

    下面是一个修改后的 build.gradle 示例片段,假设你的目标是:

    • dev flavor 使用 Positivo 签名,用于在 Positivo 终端上调试。
    • standard flavor (默认) 使用标准的 debug 签名,用于普通模拟器或设备调试。
    • release build type 使用 debug 签名 (仅作示例,实际发布应替换为 release key)。
    android {
        // ... 其他配置保持不变 ...
    
        // 确保 positivo 签名配置已通过外部脚本正确加载
        // apply from: file('../positivo/positivo-signing-config.gradle') 这一行要保留且确保脚本有效
    
        signingConfigs {
            // 你可能需要在这里明确定义 debug (虽然默认有)
            debug {
                // 默认的 debug keystore 配置,通常无需显式写
            }
            // 'positivo' 签名配置应该由 ../positivo/positivo-signing-config.gradle 文件定义
        }
    
        defaultConfig {
            // ...
            minSdkVersion 23 // 注意: Positivo 终端可能有特定的 minSdkVersion 要求,请核对文档
            targetSdkVersion flutter.targetSdkVersion
            versionCode flutterVersionCode.toInteger()
            versionName flutterVersionName
        }
    
        flavorDimensions "manufacturer"
    
        productFlavors {
            // 标准 flavor,用于普通开发/模拟器,使用默认 debug 签名
            standard {
                dimension "manufacturer"
                signingConfig signingConfigs.debug // 改用 debug 签名
                isDefault true
                // applicationIdSuffix ".standard" // 可以考虑加后缀区分
                // versionCodeSuffix 100 // 可以考虑用 versionCode 区分
            }
            // 开发 flavor,用于 Positivo 终端,使用 Positivo 签名
            dev {
                dimension "manufacturer"
                signingConfig signingConfigs.positivo // 保持 Positivo 签名
                applicationId "dev.ltag.stone_payments_example.dev" // 强烈建议为不同 flavor 设置不同 applicationId
                // versionCodeSuffix 200 // 使用 versionCode 或 applicationId 区分不同 flavor 的 APK
            }
        }
    
        buildTypes {
            debug {
                // flutter run 默认使用 debug 构建类型
                // 确保这里的签名设置不会覆盖 flavor 的设置
                // 通常留空即可,它会采用 flavor 中指定的 signingConfig
                // 如果想强制所有 debug 构建(不论 flavor)都用特定签名,可以在这里设置,但不推荐
            }
            release {
                // 注意:这里的 signingConfig.debug 只是示例
                // 正式发布时应替换为你的发布密钥配置
                signingConfig signingConfigs.debug // 或者 signingConfigs.positivo 如果 Positivo 是唯一签名
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
            // 删除 'positivo' build type
            // positivo {
            //     initWith release
            //     signingConfig null
            // }
        }
    }
    

    重要: 上述示例假设 ../positivo/positivo-signing-config.gradle 文件正确定义了名为 positivosigningConfig。你需要检查那个文件的内容。

  3. 清理项目: 修改完后,执行以下命令彻底清理:

    flutter clean
    cd android
    ./gradlew clean
    cd ..
    
  4. 重新尝试: 再次运行 flutter run --flavor dev

安全建议:

  • 永远不要 把密钥库密码、密钥密码或密钥别名硬编码在 build.gradle 或任何提交到版本控制的文件里。Positivo 的签名脚本 (positivo-signing-config.gradle) 应该从安全的地方(如环境变量、根目录下的 local.properties 文件,或专门的密钥管理服务)读取这些敏感信息。确保 local.properties 或包含密码的文件已加入 .gitignore

进阶使用技巧:

  • 使用 applicationIdSuffix 为不同 flavor 生成不同的应用 ID,这样可以在同一设备上安装来自不同 flavor 的应用。
  • 考虑使用 versionNameSuffix 或修改 versionCode 来区分不同 flavor 的构建版本。
  • 直接运行 Gradle 命令可以更精确地定位问题。例如,尝试运行 cd android && ./gradlew assembleDevDebug。这会只执行 dev flavor 的 debug 构建任务,输出的日志可能更集中。

方案二:深入检查 Positivo 签名脚本

如果简化 build.gradle 后问题依旧,那焦点就该放在 positivo-signing-config.gradle 这个脚本上了。

原理与作用:

这个外部脚本负责定义 signingConfigs.positivo。它内部需要正确引用 Positivo 提供的密钥库文件(keystore)、提供密钥库密码、密钥别名和密钥密码。任何一个环节出错,Gradle 就无法找到有效的签名信息,导致构建失败。

操作步骤:

  1. 找到脚本: 定位到项目根目录下的 positivo/positivo-signing-config.gradle 文件 (根据 apply from 的路径)。

  2. 检查内容: 打开脚本文件,仔细核对:

    • 密钥库路径: 脚本中指定的 Positivo keystore 文件路径是否正确?是相对路径还是绝对路径?确保该文件确实存在于指定位置。
    • 凭证读取: 脚本是如何获取密钥库密码 (storePassword)、密钥别名 (keyAlias) 和密钥密码 (keyPassword) 的?通常它会尝试从 local.properties、环境变量或其他配置文件读取。确认这些值已经按照 Positivo 的要求设置在正确的地方,并且脚本能成功读取到。
    • 签名配置定义: 脚本内部是否正确定义了 android.signingConfigs.positivo 块?类似这样:
    android {
        signingConfigs {
            positivo {
                storeFile file('<path_to_your_positivo_keystore>') // 路径要对
                storePassword System.getenv("POSITIVO_STORE_PASSWORD") ?: project.property("positivoStorePassword") // 示例:从环境变量或属性读取
                keyAlias System.getenv("POSITIVO_KEY_ALIAS") ?: project.property("positivoKeyAlias")
                keyPassword System.getenv("POSITIVO_KEY_PASSWORD") ?: project.property("positivoKeyPassword")
            }
        }
    }
    

    你需要根据你的实际脚本逻辑来检查。注意路径分隔符在不同操作系统上的兼容性。

    • 权限问题: 确保构建环境有读取 keystore 文件的权限。
  3. 确认凭证: 如果脚本是从 local.properties 读取凭证,请检查 android/local.properties 文件是否包含类似以下的行,并且值是正确的:

    positivoStorePassword=<你的Positivo密钥库密码>
    positivoKeyAlias=<你的Positivo密钥别名>
    positivoKeyPassword=<你的Positivo密钥密码>
    # 可能还需要 keystore 文件路径,取决于脚本写法
    # positivoStoreFile=../path/to/positivo.keystore
    

    再次强调: 包含敏感信息的 local.properties 文件必须 被添加到 .gitignore 中。

  4. Positivo 文档: 回头再仔细阅读一遍 Positivo 关于签名的官方文档,看看是否有遗漏的步骤或特定的配置要求。

安全建议:

  • 强烈建议使用环境变量或安全的密钥管理方案来存储签名凭证,而不是明文写在 local.properties 文件中。CI/CD 环境尤其要注意这一点。

方案三:诊断 Gradle 环境和依赖

有时候,问题并非出在配置本身,而是 Gradle 环境或其缓存出了问题。

原理与作用:

Gradle 会缓存依赖库和构建输出,以加快后续构建速度。但这些缓存有时会损坏或变得不一致,导致奇怪的构建错误。清理缓存并验证 Gradle 设置能排除这类因素。

操作步骤:

  1. 彻底清理: 再次执行深度清理:
    flutter clean
    cd android
    ./gradlew clean
    # (可选,但有时有效)删除用户主目录下的 .gradle/caches 文件夹
    # rm -rf ~/.gradle/caches
    # (可选,如果使用 Android Studio) File > Invalidate Caches / Restart...
    cd ..
    
  2. 检查 Gradle 版本: 确认 android/gradle/wrapper/gradle-wrapper.properties 文件中指定的 Gradle 版本 (distributionUrl) 是否与项目要求兼容,特别是与 Android Gradle 插件版本 (android/build.gradle 文件中的 com.android.tools.build:gradle:X.Y.Z) 兼容。查阅 Flutter 和 Android 官方文档获取推荐版本。
  3. 检查 JDK 版本: 确保你的系统安装并配置了与当前 Android Gradle 插件版本兼容的 JDK (Java Development Kit)。通常需要 JDK 11 或更高版本。运行 java -version 查看当前使用的 Java 版本。你可以在 Android Studio 的设置中 (File > Settings > Build, Execution, Deployment > Build Tools > Gradle) 指定 Gradle 使用的 JDK。
  4. 运行 Gradle 诊断命令:
    cd android
    # 查看项目依赖,检查是否有冲突或缺失
    ./gradlew :app:dependencies
    # 查看签名报告,确认 devDebug 变体的签名是否指向 Positivo 配置
    ./gradlew :app:signingReport
    
    仔细阅读这些命令的输出,它们可能会直接指出问题所在,比如找不到签名配置、依赖冲突等。

方案四:使用 flutter build apk 进行独立构建

flutter run 命令会尝试构建、安装并运行应用。有时,问题出在安装或运行环节,而非纯粹的构建环节。或者 flutter run 的复杂流程掩盖了真实的构建错误信息。

原理与作用:

flutter build apk 命令只负责构建 APK 文件,不涉及安装和运行。这能让你专注于构建过程本身,并可能获得更清晰的错误日志。

操作步骤:

  1. 执行构建命令:
    flutter build apk --flavor dev --verbose
    
    --verbose 参数会输出非常详细的构建日志,有助于定位具体是哪个 Gradle 任务失败了以及失败原因。
  2. 分析输出: 仔细查看命令行的输出,特别是错误 (ERROR) 或失败 (FAILURE) 的信息。它可能会明确指出是哪个环节出了问题,比如 "Task :app:packageDevDebug FAILED"。
  3. 检查生成的 APK: 如果构建成功,APK 文件通常会生成在 build/app/outputs/flutter-apk/app-dev-debug.apk(具体路径可能略有不同)。检查这个文件是否存在。

通过尝试以上这些方案,你应该能够定位到导致 flutter run --flavor dev 失败的具体原因,并解决 Positivo 终端集成的 APK 构建问题。核心在于理清 build.gradle 的逻辑,确保 Positivo 签名配置无误且被正确应用到 dev flavor 的 debug build 变体上。