android 多渠道打包

有这样的需求, 应用上架googlePlay权限审批没通过,需要分为两个版本,normal版本升级方案为原有的升级方案, 调用安装新版本apk需要 android.permission.REQUEST_INSTALL_PACKAGES 这个权限。 而Google Play升级不能有这个权限,需要去掉这个权限。 所以需要打两个渠道包,一个normal版本,一个Google Play版本。

app/build.gradle.kts中增加如下渠道配置:

另外, Google play升级的依赖:

app/build.gradle.kts完整代码如下:

Groovy 复制代码
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资源目录的一个包里面新增统一的升级接口:

Kotlin 复制代码
package com.example.androiddemos.update

import android.app.Activity

/**
 * 检查版本升级
 */
interface IUpdateChecker {
    fun check(activity: Activity)
}

新增Google play渠道资源目录,并在其中新增升级实现类以及AndroidManifest文件:

其中AndroidManifest内容如下:

XML 复制代码
<?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代码如下:

Kotlin 复制代码
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 复制代码
<?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代码如下:

Kotlin 复制代码
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渠道包:

运行,点击检查升级:

日志:

bash 复制代码
 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.

相关推荐
xiangxiongfly9158 个月前
Android 多渠道打包总结
android·多渠道·多渠道打包·defaultconfig·buildtypes·productflavors
宾有为2 年前
【Android】多渠道打包配置
android·java·kotlin·1024程序员节·多渠道打包