Requirements
Well, we've been asked to create a project which can generate different apps with different package names, and each of them has a different icon/png set and string values.
We want the user can handle all the APK generating and signing jobs by himself ( with minimum knowledge in Android programming).
I've seen some good example in StackOverflow but most of them doesn't really meet what I want so I'm creating one for myself.
Solution
This gradle script demonstrates how to handle different resources for each flavor:
- 3 apps defined in the project with 3 different package name (com.demo.appa, com.demo.appb, com.demo.appc) ;
- 2 sets of image *.png files and strings.xml resources (pack1, pack2) being applied to different app. pack1, pack2 are under the root directory of Android project.
In app/src/main/AndroidManifest.xml we have:
${appIcon} refers to manifestPlaceholders for each flavor in build.gradle:
manifestPlaceholders = [
appIcon: "@drawable/app_icon_appa"
]
Since app icon files are pretty small so we're not going to hassle with the copy to replace job, we simply put all app icons in res/drawable though there should only be one for each flavor.
In java sources, we use R.string.app_name and R.string.WebServer to refer to resValue for each flavor in gradle so we don't have to define values in strings.xml thus the user can maintain it thru build.gradle easily :
resValue "string", "app_name", "APP_A" resValue "string", "WebServer", "http://192.168.0.1/appA"
Resource copy
In afterEvaulate event, we use regular expression to scrap current running flavor and buildType so we can inject resource copy tasks into generate[Flavor][Build]Resources, e.g.: generateAppaDebugResources or generateAppbReleaseResources depends on current building flavor and build.
The code below demonstrates how to read current flavor (appa/appb/appc) and build type (debug/release):
def regex = "([^A-Z][a-z]+)([A-Z][^A-Z]+)([A-Z][^A-Z]+).*"
def resTask = (gradle.startParameter.taskNames[0] =~ /$regex/)
if (resTask.count==1) {
def flavor = resTask[0][2]
def type = resTask[0][3]
...
Then we inject tasks:
tasks."generate${flavor}${type}Resources".dependsOn copyPack1Png
tasks."generate${flavor}${type}Resources".dependsOn copyPack1String
Result: IDE build
Now the user can select any flavor from "Build Variants" panel in Android Studio (3.0) - all appa, appb, appc have their Debug/Release options available. And the APK will be signed if the user choose to generate Release APK.
Result: Command line build
The user can simply issue command line to create an APK:
gradlew assembleAppaDebuggradlew assembleAppaRelease- ... etc
Bonus
The user can create new apps easily (of course he can only change image and string resources, not the program logic). Simply create another resource pack in root directory, then copy/paste those related blocks in build.gradle and it's all done.
Full build.gradle
apply plugin: 'com.android.application'
android {
flavorDimensions "versionCode"
signingConfigs {
release {
storeFile file("keystore")
storePassword "password"
keyAlias "keyname"
keyPassword "password"
}
}
compileSdkVersion 24
buildToolsVersion '26.0.2'
defaultConfig {
minSdkVersion 21
targetSdkVersion 24
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles.add(file('proguard-gvr.txt'))
signingConfig signingConfigs.release
}
}
task ("copyPack1Png", type: Copy) {
println 'copyPack1Png'
from('../pack1')
into('src/main/res/mipmap-hdpi')
include('*.png')
}
task ("copyPack1String", type: Copy) {
println 'copyPack1String'
from('../pack1')
into('src/main/res/values')
include('strings.xml')
}
task ("copyPack2Png", type: Copy) {
println 'copyPack2Png'
from('../pack2')
into('src/main/res/mipmap-hdpi')
include('*.png')
}
task ("copyPack2String", type: Copy) {
println 'copyPack2String'
from('../pack2')
into('src/main/res/values')
include('strings.xml')
}
productFlavors {
appa {
applicationId "com.demo.appa"
versionCode 1
versionName "2017.12.10"
resValue "string", "app_name", "APP_A"
resValue "string", "WebServer", "http://192.168.0.1/appA"
manifestPlaceholders = [
appIcon: "@drawable/app_logo_appa"
]
}
appb {
applicationId "com.demo.appb"
versionCode 1
versionName "2017.12.10"
resValue "string", "app_name", "APP_B"
resValue "string", "WebServer", "http://192.168.0.1/appB"
manifestPlaceholders = [
appIcon: "@drawable/app_logo_appb"
]
}
appc {
applicationId "com.demo.appc"
versionCode 1
versionName "2017.12.10"
resValue "string", "app_name", "APP_C"
resValue "string", "WebServer", "http://192.168.0.1/appC"
manifestPlaceholders = [
appIcon: "@drawable/app_logo_appc"
]
}
}
}
dependencies {
// ....
// omitted
}
afterEvaluate {
def regex = "([^A-Z][a-z]+)([A-Z][^A-Z]+)([A-Z][^A-Z]+).*"
def resTask = (gradle.startParameter.taskNames[0] =~ /$regex/)
if (resTask.count==1) {
def flavor = resTask[0][2]
def type = resTask[0][3]
println resTask[0][2]
println resTask[0][3]
switch (flavor.toString().toLowerCase()) {
case "appa":
println "Applying package resources..."
android.productFlavors.appa {
tasks."generate${flavor}${type}Resources".dependsOn copyPack1Png
tasks."generate${flavor}${type}Resources".dependsOn copyPack1String
}
break
case "appb":
println "Applying package resources..."
android.productFlavors.appb {
tasks."generate${flavor}${type}Resources".dependsOn copyPack2Png
tasks."generate${flavor}${type}Resources".dependsOn copyPack2String
}
break
case "appc":
println "Applying package resources..."
android.productFlavors.appc {
tasks."generate${flavor}${type}Resources".dependsOn copyPack2Png
tasks."generate${flavor}${type}Resources".dependsOn copyPack2String
}
break
}
}
}
No comments:
Post a Comment