Introduction to the

As shown! This article tries to solve this problem. Easy packaging and running time can be done without manual replacement configuration, you can package the desired APK. When packing, just choose which apK configuration you want to use. (^o^)/~

First, there are requirements as follows:

  1. Same project
  2. Different APK ICONS
  3. Different server domain names
  4. Different package names
  5. Different names
  6. Different signatures
  7. Different third-party keys
  8. Different version names Version numbers

solution

  1. Of course, the most direct way is not to replace the corresponding configuration every time a different package is made, which is obviously troublesome.
  2. All configurations, resources, etc., are configured into the project. When packaging, different configurations of APK are packaged according to the selected channel. (This article explains how.)
  3. Believe there are others…

A couple of relevant points

  1. First of all, we need to know productFlavors to configure channels. Here, I use channels to represent what KIND of APK. I need to configure four applications as follows:

    productFlavors {

    userquhua {}

    quhua {}

    cuntuba {}

    xemh {}

    }

    Copy the code
  2. If we select a channel, we run the package to select the resource file based on the channel name (see point 6).

  3. Signatures can be configured in multiple signingConfigs (I put all signature files in the key folder of the project and directory) so that we can specify pre-configured signature configurations through signingConfigs.

    signingConfigs {

    userquhuaRelease {

    storeFile file(".. /key/xxx1.keystore")

    storePassword "xxxxxx"

    keyAlias "alias"

    keyPassword "xxxxxx"

    }



    quhuaRelease {

    storeFile file(".. /key/xxx2.keystore")

    storePassword "xxxxxx"

    keyAlias "alias"

    keyPassword "xxxxxx"

    }



    cuntubaRelease {

    storeFile file(".. /key/xxx3.keystore")

    storePassword "xxxxxx"

    keyAlias "alias"

    keyPassword "xxxxxx"

    }



    xemhRelease {

    storeFile file(".. /key/xxx4.keystore")

    storePassword "xxxxxx"

    keyAlias "alias"

    keyPassword "xxxxxx"

    }

    }

    Copy the code
  4. Build. gradle can be configured to dynamically configure constant data for Java code calls (e.g., third-party appids can be dynamically configured for different channels, or other data that needs to be changed for different channels)

    • For example, in defaultConfig {} we define:

      buildConfigField "String", "SERVER_URL", '"http://xx.xxxx.com/"'

      Copy the code
    • At this point, look at the value of package in the manifest tag of the manifest file, if:

      com.xxx.xx

      Copy the code
    • Then, you can import files in Your Java code:

      import com.xxx.xx.BuildConfig;

      Copy the code
    • And then call

      BuildConfig.SERVER_URL

      Copy the code

    Its value is the string configured above: http://xx.xxxx.com/.

    • You can enterBuildConfigTake a look, it also contains some current package name version number and other information.

  5. In channel configuration, you can configure the corresponding package name, version name, signature, and so on, as follows:

    // Omit other configurations...

    android {

    // Omit other configurations...

    productFlavors {

    userquhua {

    applicationId "com.xxx.xx"

    versionCode 1

    VersionName "1.0.0"

    SigningConfig signingConfigs userquhuaRelease / / configure the signature



    String QQ_id = '" XXXXXXXXX "' // Configure QQ AppID

    buildConfigField "String", "QQ_ID", qq_id

    BuildConfigField "String", "WX_ID", '" WXXXXXXXXXXXXXXXXX "' // Configure wechat AppID

    manifestPlaceholders = [

    qq_id: qq_id,

    JPUSH_PKGNAME : applicationId,

    JPUSH_APPKEY: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX", / / JPush registered on the package name of the corresponding Appkey.

    JPUSH_CHANNEL : "developer-default",

    ]

    }

    }



    buildTypes {

    release {

    // Omit other configurations...

    SigningConfig null // Null

    }



    debug {

    // Omit other configurations...

    SigningConfig null // Null

    }

    }

    }

    Copy the code
    • Thus, if we package the Userquhua channel, see point 2 for selecting userquhuaDebug.
    • Then, it’s best to clean up the project and run it.
    • The package name of the app iscom.xxx.xx, the version number is1, version name1.0.0.
    • throughBuildConfigcallQQ_IDStatic constants, which are the values configured in the channel,WX_IDIn the same way.
    • manifestPlaceholdersThe configuration can also be configured this way.
    • The signature problem was tried repeatedly by individuals (and then over half a day  ̄ translation  ̄), and finally the signature was configured as above.Need to pay attention tobuildTypesSignature configuration insigningConfigIf it is not set tonull, so is there a package or with the built-in signature package.
  6. Resource file replacement

    As described in point 2, resources under the corresponding channel will be matched by default after we select the operation channel. Below I willxemhThe resource catalogue of the channel is expanded.

    • As shown above, you only need the resource name to be the same as the file name corresponding to the app directory.
    • The name of the application in strings.xml, you just have to match itapp_nameModify the strings under appapp_nameI don’t need to write anything else.
  7. When selecting a channel, you can package apK with different configurations. Of course, you can also use the command mode.

Other Configuration Records

Get the current time

static def releaseTime() {

return new Date().format("yyyy-MM-dd-HH.mm", TimeZone.getTimeZone("GMT+8"))

}

Copy the code

When packaging, change the file name to facilitate channel and version packaging time

applicationVariants.all {

variant ->

variant.outputs.all {

outputFileName = "${variant.productFlavors[0].name}-v${variant.productFlavors[0].versionName}-${releaseTime()}.apk"

}

}

Copy the code
  • ${variant.productFlavors[0].name}Current Channel Name
  • ${variant.productFlavors[0].versionName}Current version name
  • ${releaseTime()}The current time

Other matters needing attention

If you have one in the manifest file androidmanifest.xml that starts with the package name. Because if the names of bags change, some of them need to change dynamically. You can use ${applicationId} instead. When packaging, it is automatically replaced with the current package name.

For example, the configuration is similar to the following:

<permission

android:name="com.xxx.xx.permission.JPUSH_MESSAGE"

android:protectionLevel="signature" />

<uses-permission android:name="com.xxx.xx.permission.JPUSH_MESSAGE" />

<receiver

android:name=".push.MyJPushMessageReceiver"

android:enabled="true"

android:exported="false" >

<intent-filter>

<action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />

<category android:name="com.xxx.xx" />

</intent-filter>

</receiver>

<provider

android:name="android.support.v4.content.FileProvider"

android:authorities="com.xxx.xx.provider"

android:exported="false"

tools:replace="android:authorities"

android:grantUriPermissions="true">

<meta-data

android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/file_paths" />

</provider>

Copy the code

Can be changed to:

<permission

android:name="${applicationId}.permission.JPUSH_MESSAGE"

android:protectionLevel="signature" />

<uses-permission android:name="${applicationId}.permission.JPUSH_MESSAGE" />

<receiver

android:name=".push.MyJPushMessageReceiver"

android:enabled="true"

android:exported="false" >

<intent-filter>

<action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />

<category android:name="${applicationId}" />

</intent-filter>

</receiver>

<provider

android:name="android.support.v4.content.FileProvider"

android:authorities="${applicationId}.provider"

android:exported="false"

tools:replace="android:authorities"

android:grantUriPermissions="true">

<meta-data

android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/file_paths" />

</provider>

Copy the code

Of course, it’s worth noting that we can’t write out the package name in code either, but we can get the current package name by BuildConfig

My complete configuration for reference

Everything about private information has been replaced with XXX

  1. Build. gradle in the project root directory

    // Top-level build file where you can add configuration options common to all sub-projects/modules.



    buildscript {



    repositories {

    google()

    jcenter()

    }

    dependencies {

    The classpath 'com. Android. Tools. Build: gradle: 3.0.0'

    The classpath "IO. Making. Prototypez: save - state: 0.1.7"



    // NOTE: Do not place your application dependencies here; they belong

    // in the individual module build.gradle files

    }

    }



    allprojects {

    repositories {

    google()

    jcenter()

    maven { url "https://jitpack.io" }

    maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }

    flatDir {

    dirs 'libs'

    }

    }

    }



    task clean(type: Delete) {

    delete rootProject.buildDir

    }



    ext{

    minSdkVersion = 16

    targetSdkVersion = 27

    compileSdkVersion = 27

    BuildToolsVersion = '27.1.1'



    SupportLibraryVersion = '27.1.1'

    1.2.2 xmvpVersion = ' '

    Retrofit2Version = '2.3.0'

    Okhttp3Version = '3.8.1'

    ButterknifeVersion = '8.6.0'

    Rx2Version = '2.0.2'

    CircleProgressDialogVersion = '1.0.2'

    SmarttabVersion = '1.6.1 @ aar'

    AdapterHelperVersion = '2.9.41'

    GlideVersion = '4.7.1'

    RoundedimageviewVersion = '2.3.0'

    EventbusVersion = '3.0.0'

    DispatcherVersion = '2.4.0'

    Picture_libraryVersion = 'v2.2.3'

    1.5.1 statusbarutilVersion = ' '

    OkhttpUtilsVersion = '3.8.0'

    1.1.3 constraintVersion = ' '

    FlexboxVersion = "1.0.0"

    }

    Copy the code
  2. Build. gradle in app directory

    apply plugin: 'com.android.application'

    apply plugin: 'save.state'



    static def releaseTime() {

    return new Date().format("yyyy-MM-dd-HH.mm", TimeZone.getTimeZone("GMT+8"))

    }



    android {

    compileSdkVersion rootProject.compileSdkVersion

    // buildToolsVersion rootProject.buildToolsVersion

    defaultConfig {

    minSdkVersion rootProject.minSdkVersion

    targetSdkVersion rootProject.targetSdkVersion



    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    multiDexEnabled true

    // config the JSON processing library

    javaCompileOptions {

    annotationProcessorOptions {

    arguments = [ serializer : "gson" ]

    }

    }



    ndk {

    abiFilters "armeabi-v7a"

    }

    renderscriptTargetApi 25

    renderscriptSupportModeEnabled true



    }

    signingConfigs {

    userquhuaRelease {

    storeFile file(".. /key/xxx.keystore")

    storePassword "xxxxxx"

    keyAlias "xxx"

    keyPassword "xxxxxx"

    }



    quhuaRelease {

    storeFile file(".. /key/xxx.keystore")

    storePassword "xxxxxxx"

    keyAlias "xxx"

    keyPassword "xxxxxxx"

    }



    cuntubaRelease {

    storeFile file(".. /key/xxx.keystore")

    storePassword "xxxxxxx"

    keyAlias "xxx"

    keyPassword "xxxxxxx"

    }



    xemhRelease {

    storeFile file(".. /key/xxx.keystore")

    storePassword "xxxxxxx"

    keyAlias "xxx"

    keyPassword "xxxxxxx"

    }

    }

    flavorDimensions "default"

    productFlavors {

    userquhua {

    applicationId "com.xxx.xx"

    versionCode 22

    VersionName 1.7.5 ""

    signingConfig = signingConfigs.userquhuaRelease



    String qq_id = '"xxxxxx"'

    buildConfigField "String", "QQ_ID", qq_id // qq appId

    BuildConfigField "String", "SINA_ID", '" XXXXXX "' // sina appId

    BuildConfigField "String", "WX_ID", '" XXXXXX "' // wechat appId

    BuildConfigField "String", "UM_ID", '" XXXXXX "' // UmMENG

    BuildConfigField "String", "WX_SECRET", '" XXXXXX "' // 微信 secret

    SINA_REDIRECT buildConfigField "String", ", "http://open.weibo.com/apps/xxxxxx/privilege/oauth" / / sina



    BuildConfigField "String", "ADHUB_INIT_ID", '" XXXXXX "' // AD SDK initialization ID

    BuildConfigField "String", "ADHUB_SPLASH_ID", '" XXXXXX "' // Open screen AD ID

    BuildConfigField "String", "ADHUB_BANNER_ID", '" XXXXXX "' // banner AD ID



    buildConfigField "String", "SERVER_URL", '"http://xxx.xxx.com/"'

    buildConfigField "String", "LOGO_URL", '"http://file.xxx.com/img/xxx.png"'



    manifestPlaceholders = [

    qq_id: qq_id,

    JPUSH_PKGNAME : applicationId,

    JPUSH_APPKEY: "XXXXXX ", // Appkey corresponding to the package name registered on JPush.

    JPUSH_CHANNEL: "developer-default", // Temporarily fill in the default value.

    ]

    }



    quhua {

    applicationId "com.xxx.xx"

    versionCode 1

    VersionName "1.0.0"

    signingConfig = signingConfigs.quhuaRelease



    String qq_id = '"xxxxxx"'

    buildConfigField "String", "QQ_ID", qq_id

    buildConfigField "String", "SINA_ID", '"xxxxxx"'

    buildConfigField "String", "WX_ID", '"xxxxxx"'

    buildConfigField "String", "UM_ID", '"xxxxxx"'

    buildConfigField "String", "WX_SECRET", '"xxxxxx"'

    buildConfigField "String", "SINA_REDIRECT", '"http://open.weibo.com/apps/xxxxxx/privilege/oauth"'



    BuildConfigField "String", "ADHUB_INIT_ID", '" XXXXXX "' // AD SDK initialization ID

    BuildConfigField "String", "ADHUB_SPLASH_ID", '" XXXXXX "' // Open screen AD ID

    BuildConfigField "String", "ADHUB_BANNER_ID", '" XXXXXX "' // banner AD ID



    buildConfigField "String", "SERVER_URL", '"http://xx.xxx.com/"'

    buildConfigField "String", "LOGO_URL", '"http://file.xxx.com/img/xxx.png"'



    manifestPlaceholders = [

    qq_id: qq_id,

    JPUSH_PKGNAME : applicationId,

    JPUSH_APPKEY: "XXXXXX ", // Appkey corresponding to the package name registered on JPush.

    JPUSH_CHANNEL: "developer-default", // Temporarily fill in the default value.

    ]

    }



    cuntuba {

    applicationId "com.xxx.xx"

    versionCode 1

    VersionName "1.0.0"

    signingConfig = signingConfigs.cuntubaRelease



    String qq_id = '"xxxxxx"'

    buildConfigField "String", "QQ_ID", qq_id

    buildConfigField "String", "SINA_ID", '"xxxxxx"'

    buildConfigField "String", "WX_ID", '"xxxxxx"'

    buildConfigField "String", "UM_ID", '"xxxxxx"'

    buildConfigField "String", "WX_SECRET", '"xxxxxx"'

    buildConfigField "String", "SINA_REDIRECT", '"http://open.weibo.com/apps/xxxxxx/privilege/oauth"'



    BuildConfigField "String", "ADHUB_INIT_ID", '" XXXXXX "' // AD SDK initialization ID

    BuildConfigField "String", "ADHUB_SPLASH_ID", '" XXXXXX "' // Open screen AD ID

    BuildConfigField "String", "ADHUB_BANNER_ID", '" XXXXXX "' // banner AD ID



    buildConfigField "String", "SERVER_URL", '"http://xxx.xxxx.com/"'

    buildConfigField "String", "LOGO_URL", '"http://file.xxx.com/img/xxx.png"'



    manifestPlaceholders = [

    qq_id: qq_id,

    JPUSH_PKGNAME : applicationId,

    JPUSH_APPKEY: "XXXXXX ", // Appkey corresponding to the package name registered on JPush.

    JPUSH_CHANNEL: "developer-default", // Temporarily fill in the default value.

    ]

    }



    xemh {

    applicationId "com.xxx.xx"

    versionCode 1

    VersionName "1.0.0"

    signingConfig = signingConfigs.xemhRelease



    String qq_id = '"xxxxxx"'

    buildConfigField "String", "QQ_ID", qq_id

    buildConfigField "String", "SINA_ID", '"xxxxxx"'

    buildConfigField "String", "WX_ID", '"xxxxxx"'

    buildConfigField "String", "UM_ID", '"xxxxxx"'

    buildConfigField "String", "WX_SECRET", '"xxxxxx"'

    buildConfigField "String", "SINA_REDIRECT", '"xxxxxx"'



    BuildConfigField "String", "ADHUB_INIT_ID", '" XXXXXX "' // AD SDK initialization ID

    BuildConfigField "String", "ADHUB_SPLASH_ID", '" XXXXXX "' // Open screen AD ID

    BuildConfigField "String", "ADHUB_BANNER_ID", '" XXXXXX "' // banner AD ID



    buildConfigField "String", "SERVER_URL", '"http://xx.xxx.com/"'

    buildConfigField "String", "LOGO_URL", '"http://file.xxxxxx.com/img/xxxxxx.png"'



    manifestPlaceholders = [

    qq_id: qq_id,

    JPUSH_PKGNAME : applicationId,

    JPUSH_APPKEY: "XXXXXX ", // Appkey corresponding to the package name registered on JPush.

    JPUSH_CHANNEL: "developer-default", // Temporarily fill in the default value.

    ]

    }

    }



    applicationVariants.all {

    variant ->

    variant.outputs.all {

    outputFileName = "${variant.productFlavors[0].name}-v${variant.productFlavors[0].versionName}-${releaseTime()}.apk"

    }

    }



    buildTypes {

    release {

    // Do not display Log

    buildConfigField "boolean", "LOG_DEBUG", "false"

    signingConfig null

    minifyEnabled true

    zipAlignEnabled true

    // Remove useless resource files

    shrinkResources true

    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

    }



    debug {

    / / display the Log

    buildConfigField "boolean", "LOG_DEBUG", "true"

    signingConfig null

    minifyEnabled false

    zipAlignEnabled false

    shrinkResources false

    }

    }

    packagingOptions {

    exclude 'META-INF/DEPENDENCIES.txt'

    exclude 'META-INF/NOTICE'

    exclude 'META-INF/NOTICE.txt'

    exclude 'META-INF/LICENSE'

    exclude 'META-INF/LICENSE.txt'

    }

    compileOptions {

    targetCompatibility JavaVersion.VERSION_1_8

    sourceCompatibility JavaVersion.VERSION_1_8

    }



    dexOptions {



    JavaMaxHeapSize "4g" // The larger the value, the faster it is



    preDexLibraries = false



    }

    }



    repositories {

    flatDir {

    dirs 'libs', '.. /adpoymer/libs'

    }

    }



    dependencies {

    implementation fileTree(include: ['*.jar'], dir: 'libs')

    implementation "com.android.support:appcompat-v7:$supportLibraryVersion"

    implementation "com.android.support:recyclerview-v7:$supportLibraryVersion"

    implementation "com.android.support:support-v4:$supportLibraryVersion"

    implementation "com.android.support:design:$supportLibraryVersion"

    implementation "com.android.support.constraint:constraint-layout:$constraintVersion"



    Adding this dependency adds an okHTTP dependency by default

    compile "com.squareup.retrofit2:retrofit:$retrofit2Version"

    compile "com.squareup.retrofit2:converter-gson:$retrofit2Version"

    compile "com.squareup.retrofit2:adapter-rxjava2:$retrofit2Version"

    compile "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"

    compile "com.jakewharton:butterknife:$butterknifeVersion"

    annotationProcessor "com.jakewharton:butterknife-compiler:$butterknifeVersion"

    compile "io.reactivex.rxjava2:rxandroid:$rx2Version"

    compile "com.github.xujiaji:xmvp:$xmvpVersion"

    implementation "com.github.autume:CircleProgressDialog:$CircleProgressDialogVersion"

    compile "com.ogaclejapan.smarttablayout:library:$smarttabVersion"

    compile "com.github.CymChad:BaseRecyclerViewAdapterHelper:$adapterHelperVersion"



    compile "com.github.bumptech.glide:glide:$glideVersion"

    annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"



    compile "com.makeramen:roundedimageview:$roundedimageviewVersion"

    compile "org.greenrobot:eventbus:$eventbusVersion"

    annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$dispatcherVersion"

    compile "com.jaeger.statusbarutil:library:$statusbarutilVersion"

    compile("com.github.hotchemi:permissionsdispatcher:$dispatcherVersion") {

    exclude module: "support-v13"

    }

    implementation "com.github.LuckSiege.PictureSelector:picture_library:$picture_libraryVersion"

    Implementation 'me. Drakeet. Library: crashwoodpecker: 2.1.1'

    Implementation 'com. Making. Chenupt. Android: springindicator: 1.0.2 @ aar'

    DebugImplementation 'com. Amitshekhar. Android debug - db: 1.0.4'

    Implementation 'com. Umeng. SDK: common: 1.5.3'

    Implementation 'com. Umeng. SDK: analytics: 7.5.3'



    Implementation 'com. Liulishuo. Filedownloader: library: 1.7.5'



    implementation project(':banner')

    implementation project(':xdialog')

    implementation project(':shareutil')

    implementation project(':update')

    implementation project(':pay')

    // implementation project(':adhub')

    implementation project(':imagewatcher')

    Implementation files (' libs/lite - orm - 1.9.2. Jar ')

    '2.1.1 implementation' jp. Wasabeef: blurry.

    implementation "com.google.android:flexbox:$flexboxVersion"



    Implementation 'cn. Jiguang. SDK: jpush: 3.1.6' / / here to jpush 3.1.6 version, for example.

    Implementation 'cn. Jiguang. SDK: jcore: 1.2.5' / / here to jcore 1.2.5 version, for example.



    compile(name: 'sdk-release', ext: 'aar')

    compile(name: 'open_ad_sdk', ext: 'aar')

    The compile (name: 'adpoymer - 3.4.35, ext:' the aar)

    Implementation 'pl. Droidsonroids. GIF: android - GIF - drawable: 1.0 +'

    }

    Copy the code

The end of the

This will free up a lot of labor! If some configuration is not available in other channels, you can also use BuildConfig in Java to determine if it is such and such channels then block. over