Android 多渠道打包总结

文章目录

Android 多渠道打包总结

概述

多渠道打包是指为同一个 Android 应用生成多个不同渠道的安装包,每个渠道包具有唯一的标识,用于区分不同的发布渠道(如应用商店、推广渠道等)。

定义多渠道

在 app/build.gralde 中配置:

groovy 复制代码
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace 'com.test.myapplication'
    compileSdk 35

    defaultConfig {
        applicationId "com.test.myapplication"
        minSdk 27
        targetSdk 35
        versionCode 1
        versionName "1.0"
    }

    // 签名配置
    signingConfigs {
        release {
            storeFile file("./myapp.jks")
            storePassword "123456"
            keyAlias "app"
            keyPassword "123456"
        }
        debug {
            storeFile file("./myapp.jks")
            storePassword "123456"
            keyAlias "app"
            keyPassword "123456"
        }
    }

    // 构建类型
    buildTypes {
        release {
            signingConfig signingConfigs.debug
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix "-debug"
            signingConfig signingConfigs.debug
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = '17'
    }

    // 定义2个维度
    flavorDimensions "channel", "seo"
    productFlavors {
        // 应用市场渠道
        huawei {
            dimension "channel" 
        }
        xiaomi {
            dimension "channel" 
        }
        // 推广渠道
        wechat {
            dimension "seo" 
        }
        douyin {
            dimension "seo"
        }
    }

    // 配置APK命名规则
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // 获取构建类型
            def buildType = variant.buildType.name
            // 获取渠道名称
            def flavorName = variant.flavorName
            // 获取版本信息
            def versionName = defaultConfig.versionName
            def versionCode = defaultConfig.versionCode
            // 获取当前日期
            def nowDate = new Date().format("yyyyMMddHHmm")
            // 创建文件名
            def fileName = "app_${flavorName}_${buildType}_v${versionName}_${versionCode}_${nowDate}.apk"
            // 兼容写法
            if (output.properties.containsKey('outputFileName')) {
                output.setProperty('outputFileName', fileName)
            } else {
                output.outputFileName = fileName
            }
        }
    }
}

dependencies {
    implementation libs.androidx.core.ktx
    implementation libs.androidx.appcompat
    implementation libs.material
    implementation libs.androidx.activity
    implementation libs.androidx.constraintlayout
    testImplementation libs.junit
    androidTestImplementation libs.androidx.junit
    androidTestImplementation libs.androidx.espresso.core
}

配置说明:

  • namespace:命名空间
  • signingConfigs:配置签名
  • buildTypes:配置构建类型
  • flavorDimensions:配置渠道维度
  • productFlavors:配置渠道
  • applicationVariants:配置apk命名规则
  • compileOptions:配置Java编译项
  • kotlinOptions:配置Kotlin编译项

buildConfigField

buildConfigField 是 Gradle 提供的一种机制,用于在构建过程中生成 BuildConfig.java 文件中的静态常量字段。这些字段可以直接在代码中访问。

语法

复制代码
buildConfigField "类型", "字段名", "值"
  • 类型:支持 Java 基本数据类型和 String 类型:boolean、int、long、float、double、short、byte、char、String、String\[\]
  • 字段名:生成静态常量字段名,使用驼峰命名法
  • 值:字段的具体值
groovy 复制代码
buildConfigField "int", "COUNT", "3"
buildConfigField "boolean", "MODE", "true"
buildConfigField "long", "time", "3000L"
buildConfigField "String", "URL", "www.baidu.com"
buildConfigField "String[]", "LANGUAGES", '{"中文","英文","日语"}'

优先级规则

buildTypes > productFlavors > defaultConfig

开启

在较新的 Android Gradle 插件版本(尤其是与 Gradle 8 配合使用时), BuildConfig 生成功能默认被禁用 。这是 Android Gradle 插件为了优化构建性能,避免生成不必要的文件而引入的变更。

groovy 复制代码
buildFeatures {
    buildConfig true
}

用法

在defaultConfig中定义

适用于所有渠道:

groovy 复制代码
defaultConfig {
    buildConfigField "String", "USER_NAME", '"小明"'
    buildConfigField "int", "USER_AGE", "18"
    buildConfigField "boolean", "USER_SEX", "true"
    buildConfigField "String[]", "USER_ADDRESS", '{"北京市","朝阳区"}'
}
在productFlavors中定义

适用于特定渠道:

groovy 复制代码
productFlavors {
    huawei {
        dimension "channel"
        buildConfigField "String", "USER_NAME", '"我是华为"'
        buildConfigField "int", "USER_AGE", "28"
        buildConfigField "boolean", "USER_SEX", "true"
        buildConfigField "String[]", "USER_ADDRESS", '{"广东省","深圳市"}'
    }
    xiaomi {
        dimension "channel"
        buildConfigField "String", "USER_NAME", '"我是小米"'
        buildConfigField "int", "USER_AGE", "38"
        buildConfigField "boolean", "USER_SEX", "false"
        buildConfigField "String[]", "USER_ADDRESS", '{"湖北省","武汉市"}'
    } 
}
在buildTypes中定义

适用于指定构建类型:

groovy 复制代码
buildTypes {
    release {
        buildConfigField "String", "USER_NAME", '"我是release"'
    }
    debug {
        buildConfigField "String", "USER_NAME", '"我是debug"'
    }
}
编译后生成BuildConfig.java
java 复制代码
/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.test.myapplication;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.test.myapplication.debug";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "huaweiDouyin";
  public static final String FLAVOR_channel = "huawei";
  public static final String FLAVOR_seo = "douyin";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0-debug";
  // Field from product flavor: huawei
  public static final String[] USER_ADDRESS = {"广东省","深圳市"};
  // Field from product flavor: huawei
  public static final int USER_AGE = 28;
  // Field from build type: debug
  public static final String USER_NAME = "我是debug";
  // Field from product flavor: huawei
  public static final boolean USER_SEX = true;
}
在代码中使用
kotlin 复制代码
val userName = BuildConfig.USER_NAME
val userAge = BuildConfig.USER_AGE
val userSex = BuildConfig.USER_SEX
val userAddress = BuildConfig.USER_ADDRESS

Log.e("TAG", "userName: $userName")
Log.e("TAG", "userAge: $userAge")
Log.e("TAG", "userSex: $userSex")
for (address in userAddress) {
    Log.e("TAG", "address: $address")
}

resValue

resValue 是 Gradle 提供的一种机制,用于在构建过程中生成字符串资源。这些资源会被添加到应用的 strings.xml 文件中,可以在布局文件和代码中直接使用,支持国际化。

语法

复制代码
resValue "类型", "资源名", "值"
  • 类型:支持 string、integer、bool、color、dimen

  • 资源名:生成资源的名称

  • 值:资源的具体值

    resValue "string", "name", "小明"
    resValue "integer", "age", "18"
    resValue "bool", "sex", "true"
    resValue "color", "primary_color", "#ff0000"
    resValue "dimen", "height", "48dp"

用法

在defaultConfig中定义
groovy 复制代码
defaultConfig {
    resValue "string", "name", "小明"
    resValue "integer", "age", "18"
    resValue "bool", "sex", "true"
    resValue "color", "primary_color", "#ff0000"
    resValue "dimen", "height", "48dp"
}
在productFlavors中定义
groovy 复制代码
productFlavors {
    huawei {
        dimension "channel"

        resValue "string", "name", "小华"
        resValue "integer", "age", "28"
        resValue "bool", "sex", "false"
        resValue "color", "primary_color", "#00ff00"
        resValue "dimen", "height", "58dp"
    }
    xiaomi {
        dimension "channel"

        resValue "string", "name", "小米"
        resValue "integer", "age", "38"
        resValue "bool", "sex", "true"
        resValue "color", "primary_color", "#0000ff"
        resValue "dimen", "height", "68dp"
    }
}
在buildType中定义
groovy 复制代码
buildTypes {
    release {
        resValue "string", "name", "release"
        resValue "integer", "age", "48"
        resValue "bool", "sex", "true"
        resValue "color", "primary_color", "#ff00ff"
        resValue "dimen", "height", "78dp"
    }
    debug {
        resValue "string", "name", "debug"
        resValue "integer", "age", "58"
        resValue "bool", "sex", "true"
        resValue "color", "primary_color", "#ff00ff"
        resValue "dimen", "height", "88dp"
    }
}
在代码中使用
kotlin 复制代码
val name = resources.getString(R.string.name)
val age = resources.getInteger(R.integer.age)
val sex = resources.getBoolean(R.bool.sex)
val primaryColor = resources.getColor(R.color.primary_color, theme)
val height = resources.getDimension(R.dimen.height)
在布局中使用
xml 复制代码
<TextView
          android:layout_width="wrap_content"
          android:layout_height="@dimen/height"
          android:text="@string/name"
          android:textColor="@color/primary_color" />

manifestPlaceholders

manifestPlaceholders 是 Gradle 提供的一种机制,用于在构建过程中替换 AndroidManifest.xml 文件中的占位符。它允许开发者在 build.gradle 中定义键值对,在 AndroidManifest.xml 中使用 ${} 语法使用这些值。

语法

groovy 复制代码
manifestPlaceholders = [
    键名1: "值1",
    键名2: "值2",
    ...
]
  • 键名:在 AndroidManifest.xml 中占位符的名称
  • 值:占位符的具体值

用法

在defaultConfig中定义
groovy 复制代码
defaultConfig {
    manifestPlaceholders = [
            APP_NAME   : "我的APP",
            APP_VERSION: "1.1.1",
            APP_URL    : "http://www.baidu.com"
    ]
}
在productFlavors中定义
groovy 复制代码
productFlavors {
    // 应用市场渠道
    huawei {
        dimension "channel"
        manifestPlaceholders = [
                APP_NAME   : "我的华为",
                APP_VERSION: "2.2.2",
                APP_URL    : "http://www.huawei.com"
        ]
    }
    xiaomi {
        dimension "channel"
        manifestPlaceholders = [
                APP_NAME   : "我的小米",
                APP_VERSION: "3.3.3",
                APP_URL    : "http://www.xiaomi.com"
        ]
    }
}
在buildTypes中定义
groovy 复制代码
buildTypes {
    release {
        manifestPlaceholders = [
                APP_NAME   : "我的release",
                APP_VERSION: "4.4.4",
                APP_URL    : "http://www.release.com"
        ]
    }
    debug {
        manifestPlaceholders = [
                APP_NAME   : "我的debug",
                APP_VERSION: "5.5.5",
                APP_URL    : "http://www.debug.com"
        ]
    }
}
在AndroidManifest.xml中使用
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">
    
    <application
        android:label="${APP_NAME}">

        <meta-data
            android:name="APP_VERSION"
            android:value="${APP_VERSION}" />
        <meta-data
            android:name="APP_URL"
            android:value="${APP_URL}" />
    </application>
</manifest>
在代码中使用
kotlin 复制代码
fun getMetaDataValue(key: String): String? {
    return try {
        val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
        appInfo.metaData.getString(key)
    } catch (e: Exception) {
        Log.e("TAG", "获取 meta-data 失败")
        null
    }
}

val appVersion = getMetaDataValue("APP_VERSION")
val appUrl = getMetaDataValue("APP_URL")
Log.e("TAG", "appVersion: $appVersion")
Log.e("TAG", "appUrl: $appUrl")

定制资源

优先级规则

  • 资源文件 :构建类型资源(release、debug) > 渠道资源(a渠道、b渠道) > 主包资源(main目录) > 依赖库资源
  • 代码文件 :构建系统会将所有源集(main、渠道包)的代码合并到同一个编译环境中,当存在 包名+类名完全相同 的类时,编译器会报"重复声明"(Redeclaration)错误,而不是覆盖。

渠道包目录结构

复制代码
project/
├── app/
│   ├── build.gradle             # 应用级构建配置
│   ├── src/
│   │   ├── main/                # 主包(默认资源)
│   │   │   ├── java/com/example/multichanneldemo/  # 默认 Java/Kotlin 代码
│   │   │   │   ├── MainActivity.kt                  # 主活动
│   │   │   │   ├── AppConfig.kt                     # 应用配置
│   │   │   │   └── MetaDataUtil.kt                  # 元数据工具类
│   │   │   ├── res/             # 默认资源文件
│   │   │   │   ├── drawable/    # 默认图标
│   │   │   │   ├── layout/      # 默认布局
│   │   │   │   │   └── activity_main.xml           # 主布局
│   │   │   │   └── values/      # 默认字符串
│   │   │   │       ├── strings.xml                 # 字符串资源
│   │   │   │       └── styles.xml                  # 样式资源
│   │   │   ├── assets/          # 默认资产文件
│   │   │   │   └── channels.txt                    # 渠道列表文件
│   │   │   └── AndroidManifest.xml                 # 默认清单文件
│   │   ├── huawei/              # 华为渠道资源
│   │   │   ├── java/com/example/multichanneldemo/  # 华为特定代码
│   │   │   │   └── HuaweiChannelUtil.kt            # 华为渠道工具类
│   │   │   ├── res/             # 华为特定资源
│   │   │   │   ├── drawable/    # 华为特定图标
│   │   │   │   └── values/      # 华为特定字符串
│   │   │   │       └── strings.xml                 # 华为特定字符串
│   │   │   └── AndroidManifest.xml                 # 华为特定清单文件
│   └── build/                   # 构建输出目录
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar   # Gradle 包装器
│       └── gradle-wrapper.properties  # Gradle 包装器配置
├── build.gradle                 # 项目级构建配置
├── settings.gradle              # 项目设置
├── gradle.properties            # Gradle 属性配置
└── local.properties             # 本地配置(不提交到版本控制)
  • 每个渠道都可以有自己的资源目录,结构与 main 目录相同。
  • 渠道特定代码会与 main 目录中的代码合并,会覆盖 main 目录中的同名资源。

定制字符串资源

小米特定渠道:

在 app/src/xiaomi/res/values/string.xml:

xml 复制代码
<resources>
    <string name="app_name">小米APP</string>
    <string name="hello">hello小米</string>
</resources>

定制图片资源

小米特定渠道:

在 app/src/xiaomi/res/drawable 添加同名的图片。

定制代码

buildConfigField+条件判断

配置 build.gradle:

groovy 复制代码
productFlavors {
    huawei {
        dimension "channel"
        buildConfigField "String", "CHANNEL_ID", '"huawei"'
    }
    xiaomi {
        dimension "channel"
        buildConfigField "String", "CHANNEL_ID", '"xiaomi"'
    }
}

主包中定义工具类:

kotlin 复制代码
object Utils {
    fun sayHello() {
        Log.e("TAG", "hello APP")
        when (BuildConfig.CHANNEL_ID) {
            "xiaomi" -> Log.e("TAG", "hello 小米")
            "huawei" -> Log.e("TAG", "hello 华为")
        }
    }
}
接口+渠道特定代码

在主包中定义接口:

kotlin 复制代码
interface BaseUtils {
    fun sayHello()
}

在渠道包中实现特定代码:

kotlin 复制代码
object ImplUtils : BaseUtils {
    override fun sayHello() {
        Log.e("TAG", "hello 小米")
    }
}
相关推荐
zh_xuan2 小时前
android 多渠道打包
多渠道打包·google play 升级
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓5 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1235 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)6 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白1 天前
自回归和上下文
android