返回

修复 Play Console versionCode 已使用的上传错误 (分步指南)

Android

搞定 Play Console 上传错误:“Version code 1 has already been used”

往 Google Play Console 上传新的 App Bundle 时,有时会碰到一个拦路虎般的错误提示:“Version code 1 has already been used. Try another version code.” 就算你记得明明在 pubspec.yaml 里把 version1.0.0+1 改成了 2.0.0+1,这错误还是阴魂不散。别急,咱们来捋一捋这到底是怎么回事,以及怎么解决它。

一、为啥会出现这个报错?

Google Play Console 要求你上传的每一个 App Bundle(或旧版的 APK)都必须有一个独一无二的 versionCode。这个 versionCode 是一个内部版本号,纯数字,主要供系统和 Play Store 识别更新。它跟用户看到的版本名 versionName (比如 1.0.0, 2.1.5) 是两码事。

关键点在于:

  1. versionCode 必须是唯一的: 一旦某个 versionCode (比如 1) 被用于某个上传过的版本(无论是发布到哪个渠道,甚至是内部测试),它就不能再被用于任何新的上传。
  2. versionCode 必须递增: 新上传的版本,其 versionCode 必须大于等于当前 Play Console 中记录的所有版本(包括活跃和非活跃的)的最大 versionCode。实际上,为了避免混淆,最佳实践是严格递增

当你看到“Version code 1 has already been used”的错误时,意思就是 Google Play 的系统里已经记录了一个 versionCode1 的 App Bundle。你新上传的这个包,系统识别出来其 versionCode 依然1,所以拒绝了。

即便你修改了 pubspec.yaml 里的 version: 1.0.0+1version: 2.0.0+1,这里你主要修改的是 versionName (1.0.0 变成 2.0.0),而 versionCode 依然是 + 后面的那个数字 1,所以问题依旧。

二、怎么解决这个麻烦?

碰到这问题,一般有以下几种检查和解决思路:

1. 正确地更新 versionCode

这是最常见的原因,特别是在 Flutter 项目中。

原理和作用:

在 Flutter 项目中,pubspec.yaml 文件里的 version 字段同时定义了 versionNameversionCode。格式是 version: <versionName>+<versionCode>

  • + 号前面的是 versionName(给用户看的,例如 1.0.1, 2.0.0)。
  • + 号后面的是 versionCode(给系统看的,必须是正整数,例如 1, 2, 10)。

当你需要发布新版本时,你必须增加 + 号后面的数字。

操作步骤 (Flutter 项目):

  1. 打开 pubspec.yaml 文件。

  2. 找到 version: 这一行。

  3. + 号后面的数字增加。 例如,如果之前是 version: 1.0.0+1,你需要改成 version: 1.0.0+2 或者,如果你同时更新了版本名,改成 version: 2.0.0+2。关键是 + 后面的数字 2 比之前的 1 大。

    name: my_awesome_app
    description: A new Flutter project.
    
    # The following line prevents the package from being accidentally published to
    # pub.dev using `flutter pub publish`. This is preferred for private packages.
    publish_to: 'none' # Remove this line if you wish to publish to pub.dev
    
    # The following defines the version and build number for your application.
    # A version number is three numbers separated by dots, like 1.2.43
    # followed by an optional build number separated by a +.
    # Both the version and the builder number may be overridden in flutter
    # build by specifying --build-name and --build-number, respectively.
    # In Android, build-name is used as versionName while build-number used as versionCode.
    # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
    # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
    # Read more about iOS versioning at
    # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
    # In Windows, build-name is used as the major, minor, and patch parts
    # of the product and file versions while build-number is used as the build suffix.
    #version: 1.0.0+1  <-- 旧的版本
    version: 1.0.0+2  <-- 新的版本,versionCode 增加到了 2
    
    environment:
      sdk: '>=3.0.0 <4.0.0' # 根据你的项目调整
    
    dependencies:
      flutter:
        sdk: flutter
      # ... 其他依赖
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      # ... 其他开发依赖
    
    # ... 其他配置
    
  4. 保存 pubspec.yaml 文件。

  5. (建议) 在终端里运行 flutter clean 清理一下旧的构建产物。

  6. 重新构建你的 App Bundle:

    flutter build appbundle --release # 或者你使用的其他构建参数
    
  7. 上传新生成的 App Bundle 文件 (通常在 build/app/outputs/bundle/release/app-release.aab)。

2. 检查 Android 原生配置 (build.gradle)

有时候,versionCode 可能不是直接从 pubspec.yaml 读取的,或者在原生 Android 配置中被覆盖了。

原理和作用:

最终决定 Android 应用 versionCode 的地方是 android/app/build.gradle 文件。虽然 Flutter 默认会从 pubspec.yaml 读取并写入这里,但可能存在手动修改、不同 buildTypesproductFlavors 的配置覆盖了默认行为。

操作步骤:

  1. 打开项目中的 android/app/build.gradle 文件。

  2. 找到 android { ... } 代码块,特别是里面的 defaultConfig { ... } 部分。

  3. 检查 versionCode 的值。 它可能是直接写死的数字,或者是从某个地方(比如 gradle.properties 文件或者环境变量)读取的。

    • Groovy (通常的文件名 build.gradle):

      android {
          compileSdkVersion flutter.compileSdkVersion
          ndkVersion flutter.ndkVersion
      
          defaultConfig {
              applicationId "com.example.my_app"
              minSdkVersion flutter.minSdkVersion
              targetSdkVersion flutter.targetSdkVersion
              // 确保这里的 versionCode 是你想要的新值
              versionCode 2 // 或者 localProperties.getProperty('flutter.versionCode').toInteger() 等形式
              versionName "1.0.0" // 这个通常也会从 pubspec 读取,但也可能被覆盖
          }
      
          // ... 其他配置 (buildTypes, signingConfigs 等)
      }
      
    • Kotlin DSL (通常的文件名 build.gradle.kts):

      android {
          compileSdk = flutter.compileSdkVersion
          ndkVersion = flutter.ndkVersion
      
          defaultConfig {
              applicationId = "com.example.my_app"
              minSdk = flutter.minSdkVersion
              targetSdk = flutter.targetSdkVersion
              // 确保这里的 versionCode 是你想要的新值
              versionCode = 2 // 或者 rootProject.extra.get("flutterVersionCode") as? Int ?: 1
              versionName = "1.0.0" // 同上,可能被覆盖
          }
      
           // ... 其他配置
      }
      
  4. 如果你发现 versionCode 被写死或者配置不正确,请修改它。 确保这个值大于你之前所有上传过的版本的 versionCode。如果你希望它跟随 pubspec.yaml,确保读取逻辑是正确的(Flutter 默认的模板通常包含类似 localProperties.getProperty('flutter.versionCode').toInteger() 或从 rootProject.extra 读取的逻辑)。

  5. 保存 build.gradle 文件。

  6. (建议) 同样,执行 flutter clean 或直接在 android 目录下执行 ./gradlew clean

  7. 重新构建 App Bundle。

进阶使用技巧:

  • 你可以在 gradle.properties 文件中定义 versionCode,然后在 build.gradle 中读取,方便管理。
    # gradle.properties
    MYAPP_VERSION_CODE=2
    
    // build.gradle
    versionCode project.properties['MYAPP_VERSION_CODE'].toInteger()
    
  • 在 CI/CD 流程中,可以根据构建号(Build Number)或其他逻辑动态生成 versionCode,并通过命令行参数传递给构建脚本。例如:flutter build appbundle --build-number=$CI_PIPELINE_IID$CI_PIPELINE_IID 是 CI 系统提供的唯一递增 ID)。

3. 检查上传目标和历史记录

错误也可能是因为你尝试将这个 versionCode1 的版本上传到一个已经存在相同 versionCode 的轨道(Track),或者这个 versionCode 在过去某个时候已经被使用过了。

原理和作用:

Google Play Console 有多个发布轨道,如内部测试、封闭式测试、开放式测试和生产环境。versionCode 的唯一性是针对你整个 App 的所有版本历史记录而言的,横跨所有轨道。

操作步骤:

  1. 登录 Google Play Console。
  2. 选择你的应用。
  3. 导航到“App bundle explorer”(应用包浏览器)或类似的菜单项。 这里会列出所有你曾经上传过的 App Bundle 及其 versionCodeversionName
  4. 仔细检查列表。 确认 versionCode 1 是否真的已经被某个历史版本(即使那个版本现在不活跃或未发布)占用了。
  5. 检查各个发布轨道(内部、封闭、开放、生产)。 确保你不是在尝试上传一个 versionCode 等于某个轨道上已存在或已暂存的版本。有时候一个版本可能只在内部测试轨道,但其 versionCode 依然是全局唯一的。
  6. 如果你确认 versionCode 1 确实已被使用 ,那么解决方案就是回到前面的步骤,使用一个新的、更大的 versionCode(例如 2, 3, ...)。

4. 彻底清理和重新构建

极少数情况下,可能是本地构建缓存或流程出了问题,导致生成的 App Bundle 并没有包含你更新后的 versionCode

原理和作用:

构建工具(如 Flutter 的构建系统或 Gradle)会使用缓存来加速构建过程。有时缓存可能没有正确更新,导致即使你修改了配置文件,最终的产物还是旧的。

操作步骤:

  1. 确保你已经保存了对 pubspec.yamlbuild.gradle 的修改。

  2. 执行彻底清理命令:

    • Flutter 项目:在项目根目录运行 flutter clean
    • 原生 Android 项目或想更彻底:进入 android 目录,运行 ./gradlew clean (Mac/Linux) 或 gradlew clean (Windows)。
  3. 再次运行构建命令,生成 App Bundle:

    flutter build appbundle --release
    

    或者 Gradle 命令:

    ./gradlew bundleRelease # 或其他对应的 task
    
  4. 上传最新生成的 AAB 文件。 你可以通过检查文件生成时间戳来确认你上传的是刚刚构建的文件。

三、版本号管理建议

为了避免未来再次遇到类似问题,养成良好的版本号管理习惯很重要:

  1. versionCode 必须每次上传都增加: 无论是发布到哪个轨道,哪怕只是一个小小的修复或测试,只要生成新的 App Bundle 准备上传,就应该增加 versionCode。将其视为一个内部构建计数器。
  2. versionName 按需更新: versionName (如 1.0.1, 1.1.0) 通常根据功能更新或修复的重要程度来更新,可以遵循语义化版本(Semantic Versioning)等规范,不要求每次构建都变。
  3. 记录 versionCode 在团队协作或长期项目中,最好有个地方记录下哪个 versionCode 对应哪个发布版本或重要里程碑,避免混淆。
  4. 考虑自动化: 在 CI/CD 流程中,利用构建号、时间戳或者 Git commit hash 等信息自动生成并递增 versionCode,可以有效减少人为错误。

遵循这些步骤和建议,应该能帮你解决“Version code 1 has already been used”的错误,并让后续的应用发布过程更顺畅。