ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android buildTypes和productFlavors实现差异化打包

Android buildTypes和productFlavors实现差异化打包 什么是buildTypes和productFlavorshttps://developer.android.com/build/build-variants?hlzh-cn1.buildTypesbuildTypes主要用于定义不同的构建变体通常用于区分不同的构建模式如开发版、发布版等。每个buildType都会有一组配置项通常是与构建过程和构建输出相关的配置。常见的buildTypes包括debug用于调试构建通常包含日志和调试功能。release用于发布构建通常会启用混淆、签名、优化等。一共这些方法https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/dsl/BuildTypebuildTypes定义的是构建过程的行为它影响的是最终的 APK 构建方式例如签名方式、混淆规则等。android {buildTypes {debug {minifyEnabled false // 不启用混淆debuggable true}release {minifyEnabled true // 启用混淆shrinkResources true // 去除未使用的资源signingConfig signingConfigs.release}}}2.productFlavorsproductFlavors用于定义不同的产品变体通常是用来支持不同的产品版本或配置比如免费版和付费版、不同地区的版本等。通过productFlavors你可以为不同的版本提供不同的代码、资源或配置。productFlavors允许你在同一个项目中创建多个产品变体每个变体可以有不同的资源、应用 ID、版本号等。android {flavorDimensions versionproductFlavors {free {applicationId com.example.app.freeversionName 1.0-free}paid {applicationId com.example.app.paidversionName 1.0-paid}}}区别总结buildTypes控制构建过程的行为如是否启用混淆、签名、调试信息等默认有debug和release两种常见类型。productFlavors控制应用的版本和特性如免费版、付费版、不同地区版本等让你可以为不同的目标配置不同的资源和代码。在安卓开发过程中难免会遇到像以下这样的一些需求1.需要打不同市场的包像opp vivo等用于友盟统计各个市场的下载量2.需要打测试包生产包等要求每个报名下的app名称应用图标appid url不同甚至代码像以上的一些需求我们称之为Android的差异化打包现在我们一起来配置,下面的图是我配置好的productFlavors{ //todo a b,c,d等是打包提供的一些标志名称打包的时候可以选择 a{ applicationId com.example.myapplication1 resValue string, app_name, 测试a buildConfigField String, BASE_URL, 我时a 的路由 manifestPlaceholders[ APPICON : mipmap/ic_launcher, ] } b{ applicationId com.example.myapplication2 resValue string, app_name, 测试b buildConfigField String, BASE_URL, 我时b 的路由 manifestPlaceholders[ APPICON : mipmap/down, ] } c{ applicationId com.example.myapplication3 resValue string, app_name, 测试c buildConfigField String, BASE_URL, 我时c 的路由 manifestPlaceholders[ APPICON : mipmap/up, ] } d{ applicationId com.example.myapplication4 //修改application resValue String, app_name, 测试d //todo 修改res中的资源 buildConfigField String, BASE_URL, 我时d 的路由 //todo 用来根据不同的包更换不同的utl manifestPlaceholders[ APPICON : mipmap/app_icon_mail, //todo 清单文件中映射的值 ] } }然后运行的时候运行和打包的时候可以选择这里以运行为列点击如下按钮可以选择当前运行包的环境介绍完productFlavors 后我们现在带着问题来解释下他的一些属性和用法问题1manifestPlaceholders 占位符的使用,其实很好理解,你可以认为它可以在build.gradle文件中定义字符串并将值映射到AndroidManifest清单文件的指定位置.如何使用呢首先在AndroidManifest中设置需要替代的字段然后在build.gradle中设置清单文件中替代文字需要映射的值d{ manifestPlaceholders[ APPICON : mipmap/app_icon_mail, //todo 清单文件中映射的值 ] }问题2resValue 这个指定后会在build完成后会在build文件里面生成值例如resValue String, app_name, 测试a //他会在build文件夹生成如下string nameapp_name translatablefalse测试b/string如果你本里的values文件夹下存在了该string nameapp_nameExpandedRecycleViewDemo/string那么程序则会报错因为build时会存在两个app_name的namebuildConfigField这个属性很好用这个属性在build后会生成一个buildConfig.java的文件里面有许多可以使用的信息因此我们可以把BASEURL设置在此我的是如下配置d{ applicationId com.example.myapplication4 //修改application resValue String, app_name, 测试d //todo 修改res中的资源 buildConfigField String, BASE_URL, 我时d 的路由 //todo 用来根据不同的包更换不同的utl manifestPlaceholders[ APPICON : mipmap/app_icon_mail, //todo 清单文件中映射的值 ] }接下来看下buildConfig的文件public final class BuildConfig { public static final boolean DEBUG Boolean.parseBoolean(true); public static final String APPLICATION_ID com.example.myapplication2; public static final String BUILD_TYPE debug; public static final String FLAVOR b; public static final int VERSION_CODE 1; public static final String VERSION_NAME 1.0; // Fields from product flavor: b public static final String BASE_URL 我时b 的路由; }变生成了我设置的baseURL ,以后就可以根据不同的打包环境生成不同的BASEURL使用变体感知型依赖项管理机制 这里指的是module ,不是线上的依赖库Android Gradle 插件 3.0.0 及更高版本包含一种新的依赖项机制该机制可在使用库时自动匹配变体。这意味着应用的debug变体会自动使用库的debug变体依此类推。这种机制在使用变种时也同样适用应用的freeDebug变体将使用库的freeDebug变体。为了让插件准确匹配变体您需要在无法进行直接匹配的情况下按照以下部分中所述提供匹配回退机制。例如假设您的应用配置了一个名为“staging”的 build 类型但该应用的一个库依赖项没有进行相应配置。当插件尝试构建“staging”版本的应用时它不知道要使用哪个版本的库因此您将看到一条与以下内容类似的错误消息Error:Failed to resolve: Could not resolve project :mylibrary. Required by: project :app解决与变体匹配相关的构建错误 :您的应用包含库依赖项不包含的 build 类型。例如您的应用包含“staging”build 类型但依赖项仅包含“debug”和“release”build 类型。请注意如果库依赖项包含您的应用不包含的 build 类型这不会引发问题。这是因为插件在任何时候都不会从依赖项请求该 build 类型。使用matchingFallbacks为给定的 build 类型指定替代匹配项如下所示app模块buildTypes { register(benchmark) { isMinifyEnabled true signingConfig signingConfigs.getByName(release) proguardFiles( getDefaultProguardFile(proguard-android.txt), proguard-rules.pro ) multiDexKeepProguard file(multidex-rules.pro) matchingFallbacks listOf(debug, release) } release { isMinifyEnabled true isShrinkResources true // isDebuggable true signingConfig signingConfigs.getByName(release) proguardFiles( getDefaultProguardFile(proguard-android.txt), proguard-rules.pro ) multiDexKeepProguard file(multidex-rules.pro) } debug { // buildConfigField(boolean,name,2) isMinifyEnabled true applicationIdSuffix .debug // 调试版应用 ID 后缀 signingConfig signingConfigs.getByName(release) proguardFiles( getDefaultProguardFile(proguard-android.txt), proguard-rules.pro ) multiDexKeepProguard file(multidex-rules.pro) } create(pre) { // buildConfigField(boolean,name,1) isDebuggable true } }module模块buildTypes { release { isMinifyEnabled false proguardFiles( getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro ) } }module模块的buildTyp比 app少了benchmark所以选择benchmark编译的时候会报错这个时候在app下添加就行matchingFallbacks listOf(debug, release)。// 选择benchmark模块的时候其他module没有benchmark则从listOf(debug, release)集合中取优先debug# Android Product Flavors 与 Source Sets 本文说明 Android Gradle 中 productFlavors、sourceSets、main 和 buildTypes 的关系并结合 Majlis 项目的渠道结构给出示例。 ## 1. 核心概念 可以把它们简单理解为 text productFlavors声明有哪些产品渠道 sourceSets指定各个渠道的代码和资源存放位置 main所有渠道共用的代码和资源 buildTypes区分 debug、release 等构建类型 productFlavors 不会根据已有目录自动生成。必须先在 Gradle 中声明 flavorAndroid Gradle 插件才会为它创建同名 Source Set并按照约定查找目录。 ## 2. 当前项目的 Flavor 结构 libmajlis/build.gradle 中定义了三个 flavor 维度 gradle flavorDimensions [log, style, buy] productFlavors { localLog { dimension log } buglyLog { dimension log } yellow { dimension style buildConfigField int, SKIN_STYLE, 103 } blue { dimension style buildConfigField int, SKIN_STYLE, 104 } google { dimension pay } huawei { dimension pay } } 每个完整构建变体都会从每个维度中选择一个 flavor再选择一个 build type。 例如 text localLog yellow google release ↓ localLogYellowGoogleRelease text buglyLog blue huawei debug ↓ buglyLogBlueHuaweiDebug ## 3. Flavor 与 Source Set 的关系 声明一个 flavor gradle productFlavors { google { dimension pay } } Gradle 会自动创建逻辑上的 sourceSets.google默认读取 text src/google/java/ src/google/kotlin/ src/google/res/ src/google/assets/ src/google/jniLibs/ src/google/AndroidManifest.xml 使用标准目录时不需要再显式配置 gradle sourceSets { google { ... } } 如果需要使用自定义目录才需要配置 sourceSets gradle sourceSets { google { java.srcDirs [channel/google/java] res.srcDirs [channel/google/res] assets.srcDirs [channel/google/assets] jniLibs.srcDirs [channel/google/jniLibs] manifest.srcFile channel/google/AndroidManifest.xml } } 因此目录本身不会创建 flavor。例如新建 text src/AAA/BBB/ BBB 不会自动成为 product flavor。如果希望它成为 flavor必须先声明 gradle flavorDimensions [channel] productFlavors { bbb { dimension channel } } 推荐使用标准目录 text src/bbb/java/ src/bbb/res/ 如果必须保留 src/AAA/BBB可以手动映射 gradle sourceSets { bbb { java.srcDirs [src/AAA/BBB/java] res.srcDirs [src/AAA/BBB/res] } } ## 4. main 是什么 main 不是 product flavor而是 Android Gradle 插件自动创建的公共 Source Set。 每个 Android 模块都默认拥有 text src/main/java/ src/main/res/ src/main/assets/ src/main/jniLibs/ src/main/AndroidManifest.xml main 会参与所有构建变体。 当前项目对 main 做了扩展 gradle sourceSets { main { java.srcDirs [ src/main/java, src/main/java-config, src/main/java-core, src/main/java-db, src/main/java-uikit, src/main/java-utilkit, src/main/java-effect, src/main/java-webview, src/main/java-selectpicture, src/main/java-svga, src/main/java-utils, src/main/java-imagepreview, ] res.srcDirs [ src/main/res, src/main/res-uikit, src/main/res-effect, src/main/res-webview, src/main/res-selectpicture, src/main/res-svga, src/main/res-utils, src/main/res-imagepreview, ] } } 这些目录全部属于公共 main因此会进入 Google、Huawei、Debug 和 Release 等所有变体。 可以类比为 text defaultConfig 所有变体共用的构建配置 sourceSets.main 所有变体共用的代码和资源 ## 5. debug 和 release 也有 Source Set debug、release 属于 build type。Gradle 会自动创建 text src/debug/java/ src/debug/res/ src/debug/AndroidManifest.xml src/release/java/ src/release/res/ src/release/AndroidManifest.xml 构建 localLogOliveGoogleDebug 时主要合并 text src/main/ src/localLog/ src/olive/ src/google/ src/debug/ 构建 localLogOliveGoogleRelease 时主要合并 text src/main/ src/localLog/ src/olive/ src/google/ src/release/ Debug 和 Release 可以分别提供相同类的不同实现 text src/debug/java/com/example/DebugTool.kt src/release/java/com/example/DebugTool.kt 因为二者不会同时参与编译。这种结构适合让 Debug 使用真正的调试工具而 Release 使用空实现。 不要同时在 main 和某个参与构建的 flavor/build type 中声明包名、类名完全相同的 Java/Kotlin 类否则会出现重复类错误。 ## 6. Google 与 Huawei 代码如何隔离 项目分别提供 text src/google/java/... src/huawei/java/... 构建 Google 变体时 text 公共代码 src/main/ 等 main 目录 渠道代码 src/google/ 排除代码 src/huawei/ 构建 Huawei 变体时 text 公共代码 src/main/ 等 main 目录 渠道代码 src/huawei/ 排除代码 src/google/ Google 和 Huawei 可以分别提供相同包名、相同类名的实现例如 text src/google/java/com/common/support/buy/PayProcessor.kt src/huawei/java/com/common/support/buy/PayProcessor.kt 因为两个 pay flavor 属于同一个维度不会同时编译。公共代码可以调用统一的 PayProcessor实际实现由构建渠道决定。 ## 7. 资源同名时的处理 ### 7.1 Flavor 资源与 main 资源同名 例如 text src/main/res/drawable/logo.png src/google/res/drawable/logo.png Google 变体使用 src/google 中的资源其他没有定义同名资源的渠道继续使用 src/main 中的资源。 字符串、布局、颜色等资源也遵循相同的覆盖规则。 资源合并优先级大致为 text 具体构建变体 buildTypedebug/release productFlavor main 第三方依赖 多个 flavor 维度之间的优先级由 flavorDimensions 的声明顺序决定。当前项目是 text log style pay 不建议依赖跨维度同名资源覆盖因为资源来源会变得难以判断。 ### 7.2 同一个 Source Set 的多个资源目录中出现同名资源 例如 sourceSets.main.res.srcDirs 同时包含 text src/main/res/drawable/logo.png src/main/res-uikit/drawable/logo.png 两个目录都属于 main优先级相同一般会产生 Duplicate resources 编译错误。 ### 7.3 Java/Kotlin 类同名 Java/Kotlin 类不会像 Android 资源一样覆盖。 下面的结构在 Google 构建中会报重复类错误 text src/main/java/com/example/Pay.kt src/google/java/com/example/Pay.kt 下面的结构是允许的 text src/google/java/com/example/Pay.kt src/huawei/java/com/example/Pay.kt 因为 Google 和 Huawei 源集不会同时编译。 ### 7.4 Manifest src/main/AndroidManifest.xml 和渠道、build type 中的 Manifest 会进行合并而不是整个文件替换。 高优先级 Manifest 可以覆盖低优先级属性。无法自动解决的冲突需要使用 tools:replace、tools:remove 等 Manifest Merger 标记处理。 ## 8. 渠道依赖 Source Set 决定编译哪些代码依赖配置决定打包哪些第三方库两者是独立的。 普通依赖会进入所有渠道 gradle implementation group:name:version 仅 Google 渠道引入 gradle googleImplementation com.android.billingclient:billing-ktx:8.3.0 仅 Huawei 渠道引入 gradle huaweiImplementation com.huawei.hms:iap:6.13.0.300 huaweiImplementation com.huawei.hms:hmscoreinstaller:6.11.0.302 仅 Debug 引入 gradle debugImplementation io.github.didi.dokit:dokitx:3.7.11 仅 Release 引入 gradle releaseImplementation group:name:version 当前项目的 Google/Huawei 支付代码已经通过 src/google、src/huawei 隔离但支付依赖如果继续使用普通 implementation两套 SDK 仍会进入所有渠道。因此支付依赖也应按照 flavor 分开配置。 ## 9. 常用目录与配置对应关系 | Gradle 配置 | 默认 Source Set/目录 | 用途 | |---|---|---| | Android 插件内置 | src/main/ | 所有变体的公共代码和资源 | | buildTypes.debug | src/debug/ | Debug 专属内容 | | buildTypes.release | src/release/ | Release 专属内容 | | productFlavors.google | src/google/ | Google 渠道内容 | | productFlavors.huawei | src/huawei/ | Huawei 渠道内容 | | sourceSets.google | 自定义 | 修改 Google 源集的物理路径 | | googleImplementation | 无目录 | Google 渠道专属依赖 | | debugImplementation | 无目录 | Debug 专属依赖 | ## 10. 总结 text productFlavors ├── 声明渠道名称和所属维度 ├── 与 buildTypes 组合生成构建变体 ├── 自动创建同名 Source Set ├── 自动创建 googleImplementation 等依赖配置 └── 可以注入 BuildConfig、资源值、Manifest 参数等配置 sourceSets ├── main 是所有变体的公共源集 ├── flavor 和 build type 也有同名源集 └── 用于指定每个源集的实际代码、资源和 Manifest 路径 使用标准的 src/sourceSet名称/ 目录时不需要额外配置 sourceSets只有目录不符合标准约定时才需要手动配置路径。 ## 参考资料 - [Android Developers配置构建变体](https://developer.android.com/build/build-variants) - [Android Developers添加构建依赖项](https://developer.android.com/build/dependencies)
返回列表