由于公司产品的复杂性,连带着项目也跟着复杂起来,这时候也接触到了android的build 变体的使用。在这里记录一下这个过程,最初查看关于这一块的资料是在android的官网上看得,地址如下:配置 build 变体 | Android 开发者 | Android Developers
首先,我们公司是有多种产品的,每一种产品的类型有相同的也有不同的,而且种类很多,每一种产品下面还会继续细分不同的特性,例如有产品1、产品2、产品3....产品n,然后产品1可能会存在特性1、特性2......特性N,每个产品之间可能有相同部分,但是不相同的地方比较多,所以每一个产品可以作为一个单独的工程来实现,相同部分就抽成一个库进行依赖到项目中来,而每个产品下的特性则可以使用变体的方式来区分,当然不是说所有的特性都使用变体,如果特性增多,这个工程也会变得非常庞大的,还是要斟酌着使用。
针对上面所说的类型搭建项目,如下图所示:
接着,在公司的项目中,我主要使用到了以下几方面来划分产品与特性:
当创建新模块时,Android Studio 会自动为您创建“debug”build 类型和“release”build 类型。虽然“debug”build 类型没有出现在 build 配置文件中,但 Android Studio 会使用 debuggable true 配置它,在项目app目录下的build.gradle中进行配置,配置的例子如下:
- android {
- defaultConfig {
-
- }
- signingConfigs {
- release {
- storeFile file(appStoreFile)
- storePassword 'appStorePassword'
- keyAlias 'appKeyAlias'
- keyPassword 'appKeyPassword'
- }
-
- debug {
-
- storeFile file(appStoreFile)
- storePassword 'appStorePassword'
- keyAlias 'appKeyAlias'
- keyPassword 'appKeyPassword'
- }
- }
-
- buildTypes {
- release {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
-
- debug {
- // 配置debug版本的签名
- signingConfig signingConfigs.debug
-
- // 是否开启代码混淆,默认false
- minifyEnabled false
-
- // 是否应该生成可调试的apk
- debuggable true
-
- // 混淆规则配置文件
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- // 自定义buildType
- buildConfigField 'String', 'BASE_URL', '"http://api-debug.**/"'
- }
-
- /**
- * The `initWith` property allows you to copy configurations from other build types,
- * then configure only the settings you want to change. This one copies the debug build
- * type, and then changes the manifest placeholder and application ID.
- */
- staging {
- initWith debug
- applicationIdSuffix ".debugStaging"
- }
- }
- }
一般buildType中,我们会配置项目所需要使用到的环境变量,如测试环境地址和正式环境地址等,这个环境变量可以在代码中使用的,并且在编译打包的时候可以确定对应的值, 可以通过buildConfigField 关键字来进行配置,编译过后,我们可以通过BuildConfig.BASE_URL方式在代码中调用,如下图所示:
获取到的值,就是在build.gradle中对应build type下配置的值。
配置完成后,sync同步一下,在gradle模块中可以看到配置的变化,当然如果点击Gradle出现Task list not build,没有Task出现,如下图
这是由于新版本的android studio默认设置了不同步task,我们可以点击Task list not build,去掉不同步的勾选,如下图:
同步一下工程即可。我们可以在Gradle的Task-other看到这个变化,如下图:
也可以通过build variants切换当前工程的build type,切换到release下,那么buildConfig中的值也会跟着变化,如下图所示:
2.配置产品变种
如果我们的产品有多种特性类型,产品总体是一样,只有部分不一样,我们可以通过配置变体要区分特性与类型,例如,音箱项目分为蓝牙音箱、wifi音箱、蓝牙wifi音箱(只是举个例子),基本主体是一样的,只有连接方式不同,并且需要分别打不同的包,我们可以在app目录下的build.gradle配置type维度的变体,如下配置:
- flavorDimensions "type"
- productFlavors {
- bluetooth {
- // Assigns this product flavor to the "version" flavor dimension.
- // If you are using only one dimension, this property is optional,
- // and the plugin automatically assigns all the module's flavors to
- // that dimension.
- dimension "type"
- // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.bluetooth
- // 不需要修改包名直接不配置即可
- applicationIdSuffix ".bluetooth"
- versionNameSuffix "-bluetooth"
- }
- wifi {
- dimension "type"
- // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.wifi
- applicationIdSuffix ".wifi"
- versionNameSuffix "-wifi"
- }
- bluetoothandwifi{
- dimension "type"
- // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.bluetoothandwifi
- applicationIdSuffix ".bluetoothandwifi"
- versionNameSuffix "-bluetoothandwifi"
- }
- }
同步之后,可以在Gradle看到变体已经构建出来了:
同样也可以通过build variants切换和查看当前工程的变体,如下:
如果特性仅有打包的包名不一样,这样配置之后打出来的包就会带上对应变体设置的后缀名,但是如果代码也不一样的话,这就需要我们创建对应的变体模块来实现不一样的地方。
首先,我们可以右键项目,new一个java folder,选择需要创建的变体模块,如下:
创建之后,模块下面是什么都没有的,我们还需要创建目录,由于我当前项目是处于blueTooth变体模块,如下:
所以可以直接项目右键选择package,输入包名即可,如下:
如果当前工程不是处于想要创建变体的模式,则创建目录的时候,是没有package的选择的,只有Directory,如下:
注意:main模块是所有变体都能访问到,变体模块下的代码是只有当前工程处于该变体模式下才能访问。
针对变体,一般我们有可能需要由以下的配置:
每一个变体创建相同路径的MainActivity类,删除main目录下的MainActivity,统一通过main目录下的清单文件作为入口,这里创建demo和full两个变体,以这两个作为例子
app的build.gradle配置
- flavorDimensions "version"
- productFlavors {
- demo {
- // Assigns this product flavor to the "version" flavor dimension.
- // If you are using only one dimension, this property is optional,
- // and the plugin automatically assigns all the module's flavors to
- // that dimension.
- dimension "version"
- // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.bluetooth
- // 不需要修改包名直接不配置即可
- applicationIdSuffix ".demo"
- versionNameSuffix "-demo"
- }
- full {
- dimension "version"
- // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.wifi
- applicationIdSuffix ".full"
- versionNameSuffix "-full"
- }
- }
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.Project">
- <activity
- android:name=".MainActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
通过build variants切换当前工程的build type,直接运行项目的代码,就可以调用对应变体下的MainActivity,当前是什么变体模式,就会使用变体目录下相同路径的类。
如果您的源代码未按照 Gradle 要求的默认源代码集文件结构进行组织(如上文关于创建源代码集的部分中所述),您可以使用 sourceSets 代码块更改 Gradle 为源代码集的每个组件收集文件的位置。sourceSets
代码块必须位于 android
代码块中。您无需改变源代码文件的位置,只需向 Gradle 提供相对于模块级 build.gradle
文件的路径,Gradle 应该会在该路径下找到每个源代码集组件的文件 。
- sourceSets{
- main{
- java.srcDirs = ['src/main/java']
-
- // If you list multiple directories, Gradle uses all of them to collect
- // sources. Because Gradle gives these directories equal priority, if
- // you define the same resource in more than one directory, you get an
- // error when merging resources. The default directory is 'src/main/res'.
- res.srcDirs = ['src/main/res']
-
- // Note: You should avoid specifying a directory which is a parent to one
- // or more other directories you specify. For example, avoid the following:
- // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
- // You should specify either only the root 'other/res1' directory, or only the
- // nested 'other/res1/layouts' and 'other/res1/strings' directories.
-
- // For each source set, you can specify only one Android manifest.
- // By default, Android Studio creates a manifest for your main source
- // set in the src/main/ directory.
- manifest.srcFile 'src/main/AndroidManifest.xml'
- }
-
- full{
- java.srcDirs = ['src/full/java']
-
- // If you list multiple directories, Gradle uses all of them to collect
- // sources. Because Gradle gives these directories equal priority, if
- // you define the same resource in more than one directory, you get an
- // error when merging resources. The default directory is 'src/main/res'.
- res.srcDirs = ['src/full/res']
-
- // Note: You should avoid specifying a directory which is a parent to one
- // or more other directories you specify. For example, avoid the following:
- // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
- // You should specify either only the root 'other/res1' directory, or only the
- // nested 'other/res1/layouts' and 'other/res1/strings' directories.
-
- // For each source set, you can specify only one Android manifest.
- // By default, Android Studio creates a manifest for your main source
- // set in the src/main/ directory.
- manifest.srcFile 'src/full/AndroidManifest.xml'
- }
-
- demo{
- java.srcDirs = ['src/demo/java']
-
- // If you list multiple directories, Gradle uses all of them to collect
- // sources. Because Gradle gives these directories equal priority, if
- // you define the same resource in more than one directory, you get an
- // error when merging resources. The default directory is 'src/main/res'.
- res.srcDirs = ['src/demo/res']
-
- // Note: You should avoid specifying a directory which is a parent to one
- // or more other directories you specify. For example, avoid the following:
- // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
- // You should specify either only the root 'other/res1' directory, or only the
- // nested 'other/res1/layouts' and 'other/res1/strings' directories.
-
- // For each source set, you can specify only one Android manifest.
- // By default, Android Studio creates a manifest for your main source
- // set in the src/main/ directory.
- manifest.srcFile 'src/demo/AndroidManifest.xml'
- }
- }
有时候,我们项目主工程可能涉及到build type的新增,而主工程依赖了库工程,但是库工程并不需要新增这个build type类型,例如:主工程的build type如下:
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
-
- debug {
- minifyEnabled false
- debuggable true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-
- }
-
-
- staging {
- initWith debug
- applicationIdSuffix ".debugStaging"
- }
依赖的库工程的build type配置如下:
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
这时候如果当前工程为debug或release类型编译工程是不会有问题的,这是因为不管是工程还是库,android studio在创建这些工程和库的时候都会默认创建debug或release类型,但是一旦当前工程切换到staging 类型,此时编译运行工程,就会出现以下问题:
* What went wrong:
Could not determine the dependencies of task ':app-project-two:compileStagingJavaWithJavac'.
> Could not resolve all task dependencies for configuration ':app-project-two:stagingCompileClasspath'.
> Could not resolve project :variants-res.
Required by:
project :app-project-two
> No matching variant of project :variants-res was found. The consumer was configured to find an API of a component, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
- Variant 'debugApiElements' capability Project:variants-res:unspecified declares an API of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
- Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
- Other compatible attribute:
- Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'debugRuntimeElements' capability Project:variants-res:unspecified declares a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
- Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
- Other compatible attribute:
- Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'releaseApiElements' capability Project:variants-res:unspecified declares an API of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
- Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
- Other compatible attribute:
- Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'releaseRuntimeElements' capability Project:variants-res:unspecified declares a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
- Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
- Other compatible attribute:
- Doesn't say anything about its target Java environment (preferred optimized for Android)
这是由于依赖库和主工程的build type不一致的问题导致的,遇到这种问题,我们可以使用
matchingFallbacks 关键字来设置库的build type优先级,这也是android官方给出的解决方案,如下:
我们只需要在app的build.gradle的build type配置一下 matchingFallbacks即可,如下:
- buildTypes {
- release {
- .....
- }
-
- debug {
- .....
- }
-
-
- staging {
- ......
- // 关键的一行
- matchingFallbacks = ['debug','release']
- }
- }