android的多变体构建是指根据不同配置,基于一份代码根据不同需求构建出不同的apk。
目前花椒面临的不同构建需求主要有
目前花椒Android客户端主要使用的gradle编译属性和product flavor来满足这些需求,目前已经有了较为灵活的构建机制,能够快速响应各方的构建需求。本文就详细介绍这两种技术的原理及在花椒的实践。
product flavor是Android Gradle Plugin提供的一种强大的构建配置。它通过配置不同的flavorDimensions,然后组合这些dimension可以构建出不同功能的APK。典型的配置如下:
groovy android { ... buildTypes { debug {...} release {...} }
// 配置dimensions,注意配置的顺序决定了变体中flavor顺序,
flavorDimensions "api", "mode"
productFlavors {
demo {
// 每个flavor必须配置dimension
dimension "mode"
...
}
full {
dimension "mode"
...
}
// 每个flavor中都可以配置defaultConfig下的所有配置项目,如minSdkVersion,buildConfigField等
minApi24 {
dimension "api"
minSdkVersion 24
versionCode 30000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi24"
...
}
minApi23 {
dimension "api"
minSdkVersion 23
versionCode 20000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi23"
...
}
}
}
...
这里要注意的是productflavors中配置的顺序决定了配置的优先级,如上代码,如果在demo和minApi24中同时配置了minSdkVersion,因为api在flavordimensions中靠前,所以minApi24中的配置会胜出。
那么flavor是如何起作用的呢?我们知道通常的构建都会使用位于src/main目录下的代码,但配置了flavor后我们可以在src下创建以flavor名称命名的文件夹,当构建此flavor时候就会使用此文件夹下的代码。
gradle对代码和配置文件有不同的处理方式:
在花椒中以上两种配置均有使用,如在配置CTA预装包时,需要对Application代码进行hook跳转特定Activity,就需要分别在ctaEnable目录下创建BaseapplicationHook,在ctaDisable创建BaseapplicationHook,CtaActivity以及AndroidMainfest.xml.目录结构如下图:
此处需要注意的是,某些情况下希望在flavor中删除src/main/AndroidMainfest.xml中的某些配置可以使用tools:node="remove"来实现。
在使用flavor后,应用代码就有了多个variant,同时就会有多个sourceset。在android studio中一次只能显示一个variant代码,我们可以通过Build Variants选择需要的variant。需要注意的是对于module依赖,需要注意variant的传递,比如如果主module 的variant是ctaEnableDebug,其依赖的子module如果会优先选择ctaEnable的variant。但是通常情况下子module没有对应的variant,此时会fallback到debug。
product flavor虽然很强大,但依然存在一些问题:
针对以上问题,花椒在对某些简单的构建需求使用gradle构建属性来实现。这些需求一般有如下特点:
gradle构建属性的主要原理是:
以下代码展示了如何通过构建属性配置apk的appname
groovy String appNameX = "花椒" String appNameX*P = "appNameX" def applicationName = "huajiao" if (project.hasProperty(appNameX*P)) { appNameX = project.property(appNameX\_P) }
defaultConfig{ resValue("string", "appNameX", appNameX) }
xml <application android:name="com.huajiao.base.BaseApplication" android:allowBackup="false" android:icon="@drawable/logo180" android:label="@string/appNameX" android:largeHeap="true" android:persistent="true" android:theme="@style/AppBaseTheme" android:networkSecurityConfig="@xml/network_security_config" tools:replace="android:allowBackup,android:label">
通过以上配置我们使用gradlew -PappNameX=花椒直播 assemble打包时候,配置文件中的appNameX的值就会变成花椒直播并通过resValue配置到string.xml中,这样AndroidMainfest.xml中的label名称在最终编译中就会是花椒直播,从而实现了替换应用名称的目的。
除了在xml中使用构建属性外,我们还可以通过buildconfigField将属性传递到Buildconfig类上,这样在java代码中通过引用buildConfig就可以获取构建属性了。
例如在开发中我们可以对某些模块的日志进行开关,下面代码展示了如何通过构建属性打开或关闭统计日志。
groo boolean showStatisticLog = false
if (project.hasProperty("showStatisticLog")) { showStatisticLog = true } defaultConfig { versionCode hostVCode versionName "${hostVName}" buildConfigField "boolean", "SHOW*STATISTIC*LOG", String.valueOf(showStatisticLog) }
java if (BuildConfig.SHOW_STATISTIC_LOG) { QHStatAgent.setLoggingEnabled(true); QHConfig.setDebugMode(context, true); QosSdk.enableLog(true); }
这里需要注意gradle的构建属性的配置,如果使用命令行可以直接加上-Pkey=value,如果使用android stuido可以在Settins>Compiler中设置。
在实际使用中,某些flavor组合出来的variant可能并不是我们需要的。我们可以在variantFilter配置中过滤掉不需要的variant。
variantFilter { variant -> def name = variant.flavors*.name def buildTypeName = variant.buildType.name if (name.contains("friendsChannel")) { setIgnore(!(name.contains("smEnable") && name.contains("loginForceFullScreenEnable") && name.contains("googlePlayDisable") && name.contains("ctaDisable"))) } }
当我们综合使用flavor和构建属性时候,最终可能产生有不同特性的apk,如果不加配置很容易产生重名的apk导致互相覆盖,并且在交付的时候容易出现混乱。在花椒的构建实践中我们尽可能的把本次构建的特性写入apk的文件名称中。在gradle中可以使用applicationVariants的outputFilename来配置。
groo applicationVariants.all { variant -> def buildId = "${applicationName}" + "${buildName.length() > 0?"_"+buildName:""}" + "_${variant.buildType.name}" + "_${defaultConfig.versionName}" + "${loginRequestX?"_loginRequest":""}" + flavorNames + "_targetSDK${targetSdk}" + "${huaweiPPS?"_huaweipps":""}" + "${huaweiChannel?"_huaweiChannel":""}" + "${quickStart?"_quickStart":""}" + "${usePhone?"_usePhone":""}" + "${oppoDeepLink?"_oppoDeepLink":""}" + "${desc.length() > 0?"_"+desc:""}" + "${buildNum.length() > 0?"_"+buildNum:""}" variant.outputs.all { outputFileName = "${buildId}.apk" } }
Copyright© 2013-2019