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 小米")
    }
}
相关推荐
清蒸鳜鱼5 小时前
【Mobile Agent——Droidrun】MacOS+Android配置、使用指南
android·macos·mobileagent
2501_915918415 小时前
HTTPS 代理失效,启用双向认证(mTLS)的 iOS 应用网络怎么抓包调试
android·网络·ios·小程序·https·uni-app·iphone
峥嵘life6 小时前
Android EDLA CTS、GTS等各项测试命令汇总
android·学习·elasticsearch
Cobboo6 小时前
i单词上架鸿蒙应用市场之路:一次从 Android 到 HarmonyOS 的完整实战
android·华为·harmonyos
天下·第二6 小时前
达梦数据库适配
android·数据库·adb
定偶6 小时前
MySQL知识点
android·数据结构·数据库·mysql
iwanghang6 小时前
Android Studio 2023.2.1 新建项目 不能选择Java 解决方法
android·ide·android studio
似霰6 小时前
JNI 编程指南10——从内存角度看引用类型
android·jni
南墙上的石头6 小时前
Android端 人工智能模型平台开发实战:模型服务部署与运维平台
android·运维
我命由我123456 小时前
Android 控件 - 最简单的 Notification、Application Context 应用于 Notification
android·java·开发语言·junit·android studio·android jetpack·android-studio