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 assembleAppaDebug
gradlew 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