多渠道
defaultConfig {
....
// All flavors must now belong to a named flavor dimension.
//flavorDimensions 是不能少的,如果有flavor 使用的情况下 否则会出现 kotlin not figured
flavorDimensions "env"
// dex突破65535的限制
multiDexEnabled true
// 默认是umeng的渠道
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
...
}
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="${APP_NAME}"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
...
</application>
manifestPlaceholders = [UMENG_CHANNEL_VALUE: “umeng”] 可以设置 meta-data 中的value 数据,这个数据一般是用在渠道分发使用。
sourceSets {
main {
//分模块存放资源
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player'
]
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/aidl']
renderscript.srcDirs = ['src/maom']
//res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
jniLibs.srcDir 'src/main/jniLibs'
//对默认的so存放地址改成 app/libs目录
//jniLibs.srcDirs =['libs']
}
//不同flavor的res目录
xiaomi.res.srcDirs = ['src/main/res-xiaomi']
huawei.res.srcDirs = ['src/main/res-huawei']
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('test')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
//debug.setRoot('build-types/debug')
//release.setRoot('build-types/release')
}
//根据不同的Flavor和不同的module对App的资源进行替换和逻辑变换
//渠道Flavors,配置不同风格的app
productFlavors {
xiaomi {
}
huawei{ //右键->Mark Directory as -> Resources Root
}
}
//根据Flavor设置在清单文件中进行设置
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name, APP_NAME: name]
}
如果我们想在build的时候,就按照某种 flavor 进行打包,那么就可以在 Build Variants 中选择对应的 flavor 版本,然后正常编译即可编译出对应的apk。
按照Flavor打包成不同的apk
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
android {
...
android.applicationVariants.all { variant ->
variant.outputs.all {
//这里的逻辑是在配置阶段结束之后进行的
outputFileName = "luckyboy-${variant.buildType.name}-${releaseTime()}-${defaultConfig.versionName}-${variant.productFlavors[0].name}.apk"
}
}
...
}
执行不同的命令得到不同的apk
./gradlew assemble
./gradlew assembleDebug
./gradlew assembleRelease
./gradlew assembleXiaomi
./gradlew assembleXiaomiRelease
将打包好的release包改名并拷贝到指定的地方,如下是将app/build/outputs目录下的编译好的apk文件拷贝到用户的桌面的release文件目录下,且按照 variant 归类。
android {
...
afterEvaluate {
applicationVariants.all { variant ->
def name = variant.name // xiaomiRelease
def baseName = variant.baseName //xiaomi-release
println "name : $name" //
println "baseName: $baseName"
Map<String, String> map = System.getenv()
String userName = map.get("USER")
def des = '/Users/'+userName + '/Desktop/release'
//根据apk类型打包(release 和 debug 两个版本,一般供单个应用打包使用)
if (variant.buildType.name == 'release') {
println "release打包类型"
def output = variant.outputs.first()
def apkName = "app-${variant.baseName}"+"-${variant.versionName}.apk"
//修改打包的apk文件名称
output.outputFileName = apkName
println "outputFileName "+output.outputFile.name
println "outputFile----->"+output.outputFile.path
def destFilePath = des+"/"+"${variant.baseName}"
println "destFile----"+destFilePath
//拷贝apk 文件到指定的目录
copy {
from output.outputFile.path
into destFilePath
}
}
if (variant.buildType.name == 'debug'){ //Debug打包类型 构建的是debug包
println "debug打包类型"
}
}
}
...
}
签名,在项目的app目录下创建keystore目录,然后 build->Generated Signed Bundle/Apk,然后按照提示创建进签名文件,并进行相应配置,注意在 Create New的时候填写的是 keystore 的全路径【目录地址是keystore路径,包含文件的名称和后缀名,后缀名可以是jks】。
android {
...
//signingConfigs的前后顺序不能乱
signingConfigs {
release {
storeFile file("keystore/release.jks")
storePassword "admin123"
keyAlias "luckyjoy"
keyPassword "admin123"
}
debug {
storeFile file("keystore/debug.jks")
storePassword "admin123"
keyAlias "luckyjoy"
keyPassword "admin123"
}
}
buildTypes {
release {
zipAlignEnabled true
// 移除无用的resource文件 如果要使用shrinkResources 必须使minifyEnabled为true
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.release
buildConfigField "boolean", "DEBUG_ENABLE", 'false'
buildConfigField("String", "BASE_URL", '"http://one.luckyjoy.com"')
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.debug
buildConfigField "boolean", "DEBUG_ENABLE", 'true'
buildConfigField("String", "BASE_URL", '"http://one.luckyjoy.com"')
}
}
}
其他
android {
...
lintOptions {
所有正式版构建执行规则生成崩溃的lint检查,如果有崩溃问题将停止构建
//checkReleaseBuilds false
//错误发生后停止gradle构建
abortOnError false
}
dexOptions {
//最大堆内存
javaMaxHeapSize '2048M'
//预编译
preDexLibraries = true
//线程数
threadCount = 16
dexInProcess = true
}
...
}
//以下的两个def方法可以放在总工程路径下的build.gradle里使用ext置成全局多个
//Module调用(通过rootProject.ext.xxx调用获取桌面上的keystore路径)
def getKeyStorePath(){
Map<String, String> map = System.getenv()
String userName = map.get("USER")
def des = '/Users/'+userName+'/'+'keystore.jks'
return des
}
//获取AndroidManifest中的versionName
def getVersionName(Project project){
def xmlFile = project.file("src/main/AndroidManifest.xml")
def rootManifest = new XmlSlurper().parse(xmlFile)
def versionName = rootManifest['@android:versionName']
return versionName
}
通过代码获取设置的渠道名称
val channel:String?
val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
channel = appInfo.metaData.getString("UMENG_CHANNEL")
print("channel $channel")
tv_brand.text = channel
遇到的问题:
在项目执行 ./gradlew assemble 的时候,控制台报错 编译器 (1.8.0_144) 中出现异常错误。在查看其他的项目并进行对比之后,发现修改 compileSdkVersion 和 buildToolsVersion 以及 targetSdkVersion 之后【都修改成29,对应的buildToolsVersion 改成 29.0.2】,竟然神奇的好了,不清楚原因是什么,可能构建工具需要和对应的JDK版本要相匹配,否则也会出现问题。
以下是build文件的全部内容
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.luckyboy.productflavor"
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
// All flavors must now belong to a named flavor dimension.
//flavorDimensions 是不能少的,如果有flavor 使用的情况下 否则会出现 kotlin not figured
//flavorDimensions "env", "kind"
flavorDimensions "env"
// dex突破65535的限制
multiDexEnabled true
// 默认是umeng的渠道
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
main {
//分模块存放资源
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player'
]
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/aidl']
renderscript.srcDirs = ['src/maom']
//res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
jniLibs.srcDir 'src/main/jniLibs'
//对默认的so存放地址改成 app/libs目录
//jniLibs.srcDirs =['libs']
}
xiaomi.res.srcDirs = ['src/main/res-xiaomi']
huawei.res.srcDirs = ['src/main/res-huawei']
// Move the tests to tests/java, tests/res, etc...
//instrumentTest.setRoot('test')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
//debug.setRoot('build-types/debug')
//release.setRoot('build-types/release')
}
//signingConfigs的前后顺序不能乱
signingConfigs {
release {
storeFile file("keystore/release.jks")
storePassword "admin123"
keyAlias "luckyjoy"
keyPassword "admin123"
}
debug {
storeFile file("keystore/debug.jks")
storePassword "admin123"
keyAlias "luckyjoy"
keyPassword "admin123"
}
}
buildTypes {
release {
zipAlignEnabled true
// 移除无用的resource文件 如果要使用shrinkResources 必须使minifyEnabled为true
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.release
buildConfigField "boolean", "DEBUG_ENABLE", 'false'
buildConfigField("String", "BASE_URL", '"http://one.luckyjoy.com"')
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.debug
buildConfigField "boolean", "DEBUG_ENABLE", 'true'
buildConfigField("String", "BASE_URL", '"http://one.luckyjoy.com"')
}
}
//根据不同的Flavor和不同的module对App的资源进行替换和逻辑变换
//渠道Flavors,配置不同风格的app
productFlavors {
xiaomi {
dimension 'env'
}
huawei{ //右键->Mark Directory as -> Resources Root
dimension 'env'
}
// charge{
// dimension 'kind'
// }
// free {
// dimension 'kind'
// }
}
//根据Flavor设置在清单文件中进行设置
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name, APP_NAME: name]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// kotlinOptions {
// jvmTarget = '1.8'
// }
// applicationVariants.all { variant ->
// variant.outputs.all {
// //这里的逻辑是在配置阶段结束之后进行的
// outputFileName = "luckyboy-${variant.buildType.name}-${releaseTime()}-${defaultConfig.versionName}-${variant.productFlavors[0].name}.apk"
// }
// }
afterEvaluate {
applicationVariants.all { variant ->
def name = variant.name // xiaomiRelease
def baseName = variant.baseName //xiaomi-release
println "name : $name" //
println "baseName: $baseName"
Map<String, String> map = System.getenv()
String userName = map.get("USER")
def des = '/Users/'+userName + '/Desktop/release'
//根据apk类型打包(release 和 debug 两个版本,一般供单个应用打包使用)
if (variant.buildType.name == 'release') {
println "release打包类型"
def output = variant.outputs.first()
def apkName = "app-${variant.baseName}"+"-${variant.versionName}.apk"
//修改打包的apk文件名称
output.outputFileName = apkName
println "outputFileName "+output.outputFile.name
println "outputFile----->"+output.outputFile.path
def destFilePath = des+"/"+"${variant.baseName}"
println "destFile----"+destFilePath
//拷贝apk 文件到指定的目录
copy {
from output.outputFile.path
into destFilePath
}
}
if (variant.buildType.name == 'debug'){ //Debug打包类型 构建的是debug包
println "debug打包类型"
}
}
}
lintOptions {
所有正式版构建执行规则生成崩溃的lint检查,如果有崩溃问题将停止构建
//checkReleaseBuilds false
//错误发生后停止gradle构建
abortOnError false
}
dexOptions {
//最大堆内存
javaMaxHeapSize '2048M'
//预编译
preDexLibraries = true
//线程数
threadCount = 16
dexInProcess = true
}
}
//以下的两个def方法可以放在总工程路径下的build.gradle里使用ext置成全局多个
//Module调用(通过rootProject.ext.xxx调用获取桌面上的keystore路径)
def getKeyStorePath(){
Map<String, String> map = System.getenv()
String userName = map.get("USER")
def des = '/Users/'+userName+'/'+'keystore.jks'
return des
}
//获取AndroidManifest中的versionName
def getVersionName(Project project){
def xmlFile = project.file("src/main/AndroidManifest.xml")
def rootManifest = new XmlSlurper().parse(xmlFile)
def versionName = rootManifest['@android:versionName']
return versionName
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
}
参考: