有这样的需求, 应用上架googlePlay权限审批没通过,需要分为两个版本,normal版本升级方案为原有的升级方案, 调用安装新版本apk需要 android.permission.REQUEST_INSTALL_PACKAGES 这个权限。 而Google Play升级不能有这个权限,需要去掉这个权限。 所以需要打两个渠道包,一个normal版本,一个Google Play版本。
app/build.gradle.kts中增加如下渠道配置:
另外, Google play升级的依赖:
app/build.gradle.kts完整代码如下:
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) } android { // 1. 定义维度名称。 // flavorDimensions用于声明有哪些分类维度,它将所有的 productFlavors 划分为不同的组。每个组代表一个独立的维度。 flavorDimensions += "app_channel" // 2. 配置产品变种 productFlavors { create("normal") { // dimension 是在具体的 productFlavor 内部使用的属性,用于指定当前这个 Flavor 属于哪一个预先声明好的维度 dimension = "app_channel" // 注意:buildConfigField 的参数类型和值需要符合 Kotlin 字符串规范 // "false" 是字符串类型的值,传递给 Java/Kotlin 代码时会变成 boolean false buildConfigField("boolean", "IS_GOOGLE_PLAY", "false") } create("googlePlay") { dimension = "app_channel" buildConfigField("boolean", "IS_GOOGLE_PLAY", "true") } } // 在这里添加配置,开启 BuildConfig 自动生成 buildFeatures { buildConfig = true } namespace = "com.example.androiddemos" compileSdk { version = release(36) } ndkVersion = "28.2.13676358" externalNativeBuild { cmake { path = file("src/main/cpp/CMakeLists.txt") version = "3.22.1" } } defaultConfig { applicationId = "com.example.androiddemos" minSdk = 29 targetSdk = 36 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" ndk { abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a")) } } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } buildFeatures { compose = true } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.camera:camera-core:1.4.0") implementation("androidx.camera:camera-camera2:1.4.0") implementation("androidx.camera:camera-lifecycle:1.4.0") implementation("androidx.camera:camera-view:1.4.0") // implementation("androidx.camera:camera-view-ktx:1.4.0") // 仅 googlePlay 渠道编译时包含此依赖. 前面是渠道名,如果仅normal渠道包含某依赖,则写normalImplementation(...) "googlePlayImplementation"("com.google.android.play:app-update:2.1.0") // 如果用 Kotlin 扩展库也同理 "googlePlayImplementation"("com.google.android.play:app-update-ktx:2.1.0") }main资源目录的一个包里面新增统一的升级接口:
package com.example.androiddemos.update import android.app.Activity /** * 检查版本升级 */ interface IUpdateChecker { fun check(activity: Activity) }新增Google play渠道资源目录,并在其中新增升级实现类以及AndroidManifest文件:
其中AndroidManifest内容如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- 强制移除该权限,防止AAR/sdk依赖带入REQUEST_INSTALL_PACKAGES,GP上架保护 --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" /> </manifest>UpdaterImpl代码如下:
package com.example.androiddemos.update import android.app.Activity import android.app.AlertDialog import android.util.Log import com.example.androiddemos.MainActivity import com.google.android.play.core.appupdate.AppUpdateManagerFactory import com.google.android.play.core.appupdate.AppUpdateOptions import com.google.android.play.core.install.model.AppUpdateType import com.google.android.play.core.install.model.UpdateAvailability class UpdaterImpl(private val activity: Activity) : IUpdateChecker { override fun check(activity: Activity) { Log.d("Updater", "google play channel check update") val appUpdateManager = AppUpdateManagerFactory.create(activity) appUpdateManager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) ) { // 弹框 AlertDialog.Builder(activity) .setTitle("发现新版本") .setMessage("检测到新版本,是否立即更新?") .setPositiveButton("确定") { _, _ -> val options = AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build() appUpdateManager.startUpdateFlowForResult( info, activity, options, MainActivity.REQUEST_CODE_GP_UPDATE ) } .setNegativeButton("取消", null) .show() } }.addOnFailureListener { e -> Log.e("Update", "GP check failed", e) } } }MainActivity加上常量 REQUEST_CODE_GP_UPDATE,以及onActivityResult响应升级结果:
这个是按钮,点击就调用UpdaterImpl的check方法检查版本升级:
同样,normal渠道包也是新建以下同样结构(注意,UpdaterImpl的类名以及包名需要和Google play渠道的完全一致,否则编译不过 ):
其中AndroidManifest内容如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> </manifest>UpdaterImpl代码如下:
package com.example.androiddemos.update import android.app.Activity import android.util.Log class UpdaterImpl(private val activity: Activity) : IUpdateChecker { override fun check(activity: Activity) { // 使用非GP的更新逻辑 Log.d("Updater", "Normal channel does not support in-app updates") // 待实现 ... ... } }ok. 开始打Google play渠道包:
运行,点击检查升级:
日志:
D google play channel check update 2026-10-05 20:58:48.681 15900-15900 PlayCore com.example.androiddemos E UID: [10404] PID: [15900] AppUpdateService : onError(-9) 2026-10-05 20:58:48.695 15900-15900 Update com.example.androiddemos E GP check failed com.google.android.play.core.install.InstallException: -9: Install Error(-9): The Play Store app is either not installed or not the official version. (https://developer.android.com/reference/com/google/android/play/core/install/model/InstallErrorCode#ERROR_PLAY_STORE_NOT_FOUND) at com.google.android.play.core.appupdate.zzr.zzj(com.google.android.play:app-update@@2.1.0:2) at com.google.android.play.core.appupdate.zzr.zze(com.google.android.play:app-update@@2.1.0:1) at com.google.android.play.core.appupdate.zzg.getAppUpdateInfo(com.google.android.play:app-update@@2.1.0:1) at com.example.androiddemos.update.UpdaterImpl.check(UpdaterImpl.kt:16) at com.example.androiddemos.MainActivityKt.Greeting$lambda$6$lambda$5$lambda$4(MainActivity.kt:92) at com.example.androiddemos.MainActivityKt$$ExternalSyntheticLambda3.invoke(D8$$SyntheticClass:0) at androidx.compose.foundation.ClickableNode.onPointerEvent-H0pRuoY(Clickable.kt:1009) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:436) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:422) at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:275) at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:171) at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:118) at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:2428) at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:2378) at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:2249) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3343) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3004) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3343) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3004) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3343) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3004) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3343) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3004) at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:898) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:2077) at android.app.Activity.dispatchTouchEvent(Activity.java:4516) at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:817) at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:805) at android.view.View.dispatchPointerEvent(View.java:15273) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:8511) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:8245) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7594) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7651) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7617) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7799) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7625) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:7856) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7598) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7651) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7617) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7625) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7598) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:11132) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:11034) 2026-10-05 20:58:48.695 15900-15900 Update com.example.androiddemos E at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:10987) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:11385) at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:310) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:374) at android.os.Looper.loopOnce(Looper.java:163) at android.os.Looper.loop(Looper.java:293) at android.app.ActivityThread.loopProcess(ActivityThread.java:10170) at android.app.ActivityThread.main(ActivityThread.java:10159) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1247)异常意思是没安装Google应用商店。环境准备好才能正常检查升级。
再看下apk,其中没有 REQUEST_INSTALL_PACKAGES 权限:
同理,打下normal包,看下:
ok。有安装apk权限。 运行点击检查升级:
ok.