In this article, we use Gradle to customize an application. We can customize an application for official server, test server, super server and other versions. In this article we are going to take a big step and let you build multiple applications with one set of code.

The scene is introduced

Requirement: “Change an application to a skin, third-party account, background server, change a name online, and the new functions in the future are updated synchronously”.

What do you do when you encounter such a need?

Should I make a copy of the project, modify it, and then manually copy it back when new features come out and modify the UI a little bit?

Or could you switch to a branch, change the relevant information on that branch, merge the code each time you develop a new feature, and change the UI of the new feature slightly?

Let me introduce you to using Gradle’s flavorDimensions to build multiple applications in one piece of code.

The specific implementation

Gradle configuration:

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 25
        versionCode gitVersionCode()
    }

    // Configure signature files for the two applications
    signingConfigs {
        app1 {
            storeFile file("app1.jks")
            storePassword "111111"
            keyAlias "app1"
            keyPassword "111111"
        }

        app2 {
            storeFile file("app2.jks")
            storePassword "111111"
            keyAlias "app2"
            keyPassword "111111"
        }
    }

    buildTypes {
        release {
            // Do not display Log
            buildConfigField "boolean"."LOG_DEBUG"."false"
        }

        debug {
            / / display the Log
            buildConfigField "boolean"."LOG_DEBUG"."true"
            versionNameSuffix "-debug"
            signingConfig null
            manifestPlaceholders.UMENG_CHANNEL_VALUE = "test"}}// Create a two-dimensional flavor
    flavorDimensions "APP"."SERVER"

    productFlavors {

        app1 {
            dimension "APP"
            applicationId 'com.imliujun.app1'

            versionName rootProject.ext.APP1_versionName

            / / application name
            resValue "string"."app_name"."APP1"

            buildConfigField("String"."versionNumber"."\"${rootProject.ext.APP1_versionName}\"")

            // Some configurations of the third-party SDK
            buildConfigField "int"."IM_APPID"."Tencent IM APPID for App1"
            buildConfigField "String"."IM_ACCOUNTTYPE"."\" Tencent IM Accountype \""
            manifestPlaceholders = [UMENG_APP_KEY      : "Umeng APP KEY for APP1",
                                    UMENG_CHANNEL_VALUE: "App1 default channel name",
                                    XG_ACCESS_ID       : App1 carrier pigeon push ACCESS_ID,
                                    XG_ACCESS_KEY      : App1 carrier pigeon push ACCESS_KEY,
                                    QQ_APP_ID          : "app1的QQ_APP_ID",
                                    AMAP_KEY           : "App1 Autonavi key",
                                    APPLICATIONID      : applicationId]
            // Signature file
            signingConfig signingConfigs.app1
        }

        app2 {
            dimension "APP"
            applicationId 'com.imliujun.app2'

            versionName rootProject.ext.APP2_versionName

            / / application name
            resValue "string"."app_name"."APP2"

            buildConfigField "String"."versionNumber"."\"${rootProject.ext.APP2_versionName}\""

            // Some configurations of the third-party SDK
            buildConfigField "int"."IM_APPID"."Tencent IM APPID for App2"
            buildConfigField "String"."IM_ACCOUNTTYPE"."\" Tencent IM Accountype \""
            manifestPlaceholders = [UMENG_APP_KEY      : "Friendship APP KEY for APP2",
                                    UMENG_CHANNEL_VALUE: "App2 default channel name",
                                    XG_ACCESS_ID       : "App2 carrier pigeon push ACCESS_ID",
                                    XG_ACCESS_KEY      : App2 carrier pigeon push ACCESS_KEY,
                                    QQ_APP_ID          : "app2的QQ_APP_ID",
                                    AMAP_KEY           : "App2 autonavi key",
                                    APPLICATIONID      : applicationId]
            // Signature file
            signingConfig signingConfigs.app2
        }

        offline {
            dimension "SERVER"

            versionName getTestVersionName()
        }

        online {
            dimension "SERVER"
        }

        admin {
            dimension "SERVER"

            versionName rootProject.ext.versionName + "- Administrator"
            manifestPlaceholders.UMENG_CHANNEL_VALUE = "admin"
        }
    }
}

android.applicationVariants.all { variant ->
    switch (variant.flavorName) {
        case "app1Admin":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://admin.app1domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getTestVersionName() + "- Administrator")}else {
                variant.mergedFlavor.setVersionName(rootProject.ext.APP1_VERSION_NAME + "- Administrator")}break
        case "app1Offline":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://offline.app1domain.com/\""
            variant.mergedFlavor.setVersionName(getTestVersionName())
            break
        case "app1Online":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://online.app1domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getTestVersionName())
            }
            break
        case "app2Admin":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://admin.app2domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getApp2TestVersionName() + "- Administrator")}else {
                variant.mergedFlavor.setVersionName(rootProject.ext.APP2_VERSION_NAME + "- Administrator")}break
        case "app2Offline":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://offline.app2domain.com/\""
            variant.mergedFlavor.setVersionName(getApp2TestVersionName())
            break
        case "app2Online":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://online.app2domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getApp2TestVersionName())
            }
            break}}Copy the code
ext {
    APP1_VERSION_NAME = "2.0.2"
    APP1_TEST_NUM = "0001"
    APP2_VERSION_NAME = "1.0.5."
    APP2_TEST_NUM = "0005"
}

def getTestVersionName() {
    return String.format("%s.%s", rootProject.ext.APP1_VERSION_NAME,
            rootProject.ext.APP1_TEST_NUM)
}

def getApp2TestVersionName() {
    return String.format("%s.%s", rootProject.ext.APP2_VERSION_NAME,
            rootProject.ext.APP2_TEST_NUM)
}

static int gitVersionCode() {
    def count = "git rev-list HEAD --count".execute().text.trim()
    return count.isInteger() ? count.toInteger() : 0
}Copy the code

Some changes have been made to the configuration from the previous article, while retaining all the functionality from the previous article.

Configuring Multiple Applications

Let’s start with the most important concept:

flavorDimensions "APP"."SERVER"Copy the code

This line of code configures the flavor with two dimensions, APP representing multiple applications and SERVER representing the SERVER version.

App1 and app2 have dimension “APP”, offline, Online and admin have dimension “SERVER”, and app2 have dimension “APP”, offline, online and admin have dimension “SERVER”.

Based on two dimensions of Product Flavors: APP [App1, APP2], SERVER [Offline, Online, Admin], Build Type [Debug, Release] Finally, the following Build Variant is generated:

  • app1AdminDebug
  • app1AdminRelease
  • app1OfflineDebug
  • app1OfflineRelease
  • app1OnlineDebug
  • app1OnlineRelease
  • app2AdminDebug
  • app2AdminRelease
  • app2OfflineDebug
  • app2OfflineRelease
  • app2OnlineDebug
  • app2OnlineRelease

Does every application have 3 server versions with debug and release packages for each version?

Configure different package names

For us to have multiple applications, we have to be able to install them on the same phone. So the package name has to be different between different applications.

You can change the application package name by setting different Applicationids in the FLAVOR of the APP dimension.

app1{
    applicationId 'com.imliujun.app1'
}

app2{
    applicationId 'com.imliujun.app2'
}Copy the code

App1 and App2 can be installed on the same phone and uploaded to the app Store at the same time.

One thing to remember is that the package in androidmanifest.xml does not need to be modified. The path of R file is generated according to this package. If you make changes to the package, the path of the R file will also change, and all classes that reference the R file will need to be modified.

Dynamically configure the URL and version number

Since each Build Variant is a combination of Product Flavors and Build types of different dimensions, we can’t configure the URL of the server in offline, Online, and admin as we did in the previous article. Because app1Offline and app2Offline are both test servers, but they are not the same app and the URL is different.

At this point, task operations are required to set different data for different combinations.

android.applicationVariants.all { variant ->
    // What version of flavorName is used
    switch (variant.flavorName) {
        case "app1Admin":
            // This is the supermanaged version of APP1, set the supermanaged server URL
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://admin.app1domain.com/\""
            // Determine whether the current package is' debug 'or' release 'and set the version number
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getTestVersionName() + "- Administrator")}else {
                variant.mergedFlavor.setVersionName(rootProject.ext.APP1_VERSION_NAME + "- Administrator")}break
        case "app1Offline":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://offline.app1domain.com/\""
            variant.mergedFlavor.setVersionName(getTestVersionName())
            break
        case "app1Online":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://online.app1domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getTestVersionName())
            }
            break
        case "app2Admin":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://admin.app2domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getApp2TestVersionName() + "- Administrator")}else {
                variant.mergedFlavor.setVersionName(rootProject.ext.APP2_VERSION_NAME + "- Administrator")}break
        case "app2Offline":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://offline.app2domain.com/\""
            variant.mergedFlavor.setVersionName(getApp2TestVersionName())
            break
        case "app2Online":
            variant.buildConfigField "String"."DOMAIN_NAME"."\"https://online.app2domain.com/\""
            if ("debug" == variant.buildType.getName()) {
                variant.mergedFlavor.setVersionName(getApp2TestVersionName())
            }
            break}}Copy the code

The server URLS and version numbers of the two apps are inconsistent, so task is used to dynamically set them.

Configuring the Application Name

Different applications have their own application names:

resValue "string"."app_name"."APP1"Copy the code

This line of code means the same thing as defining a String value in strings.xml. If app_name is configured using Gradle, it cannot be defined in strings. XML.

Configuring Application Signatures

If multiple applications use the same signature file, configure it in release and debug of buildTypes as described in the previous article. But what about different signature files for each application?

signingConfigs {

    app1 {
        storeFile file("app1.jks")
        storePassword "111111"
        keyAlias "app1"
        keyPassword "111111"
    }

    app2 {
        storeFile file("app2.jks")
        storePassword "111111"
        keyAlias "app2"
        keyPassword "111111"}}Copy the code

Configure multiple signature files and configure signature information in the flavor of the APP dimension.

app1{
    signingConfig signingConfigs.app1
}

app2{
    signingConfig signingConfigs.app2
}Copy the code

This allows you to set up different signature files for different applications. However, there is another place to pay attention to, this hole I did not fill, but a long way around the past, now I will fill it!

debug {
    signingConfig null
}Copy the code

Make sure to leave the signature file configuration blank in debug. Otherwise, the Build Type has higher permissions than Product Flavors, and debug Build Type automatically uses Debug SigningConfig. In this case, the signature information configured in the flavor is overwritten. The problem is that there is no problem compiling the Release package and compiling the Debug package will not be able to use some third-party SDKS that require verification signatures.

Configure code and resources for different applications

Here we go. All you need to do now is change the UI, copy, or some interface layout and logic code.

First, set up the sourceSets directory for each application, for example:

  • The app1sourceSetsLocation issrc/app1/
  • The app2sourceSetsLocation issrc/app2/

App1 is an app that has already been developed. It only needs to change the UI and copy to become App2. Create a new RES directory under SRC /app2/ and put the name of the cut graph to be replaced in the same directory as app1 into the corresponding directory of RES.

Text in the same way, will need to replace the string in the SRC/app2 / res/values/strings. The XML write a again, keep the same name, the contents literally replacement.

The same rules apply to replace layout files, style and color.

WeChat login, share, pay the callback is to return to the {application package name. Wxapi WXEntryActivity}, {application package name. Wxapi WXPayEntryActivity} these two activities.

We put these two callback activities in app1 and App2:

SourceSets File directory

Then dynamically configure the Activity package name in the Androidmanifest.xml file:

<! -- wechat share callback -->
<activity android:name="${APPLICATIONID}.wxapi.WXEntryActivity"/>
<! Wechat Pay callback -->
<activity android:name="${APPLICATIONID}.wxapi.WXPayEntryActivity"/>Copy the code

APPLICATIONID placeholders are set in Gradle:

manifestPlaceholders = [APPLICATIONID : applicationId]Copy the code

If you use ShareSDK for third-party sharing and login, you need to configure sharesdk. XML to the assets folder and copy the sharesdk. XML to the app2/ Assets/Sharesdk. XML folder. Just replace the third-party APP ID and APP KEY inside.

After installing the following authorities, you can configure the Activity package name in the same way as before:

 <! -- 【 must 】 【 attention 】 AUTH_XGPUSH, such as demo package name: com.qi.xgdemo -->
<provider
    android:name="com.tencent.android.tpush.XGPushProvider"
    android:authorities="${APPLICATIONID}.AUTH_XGPUSH"
    android:exported="true"/>Copy the code

conclusion

The above content basically involves all aspects, other details or special requirements or customization, using the above way to deal with all can be solved. I hope you don’t just learn to copy and paste, to master its principle, meet similar needs to be able to draw inferences.

Demo address: github.com/imliujun/Gr…

To summarize the technical points:

  • manifestPlaceholders -> AndroidManifest.xmlA placeholder
  • buildConfigField -> BuildConfigDynamically configure constant values
  • resValue -> String.xmlDynamic configuration string
  • signingConfigs-> Configure the signature file
  • productFlavors-> Product customization of multiple versions
  • flavorDimensions-> Set multiple dimensions for product customization
  • android.applicationVariants– > task operation

reading

  • Use Gradle to customize your application
  • Android Studio 3.0 Gradle changes

The brain so hungry


Public number: the brain is very hungry