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:
- Same project
- Different APK ICONS
- Different server domain names
- Different package names
- Different names
- Different signatures
- Different third-party keys
- Different version names Version numbers
solution
- Of course, the most direct way is not to replace the corresponding configuration every time a different package is made, which is obviously troublesome.
- 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.)
- Believe there are others…
A couple of relevant points
-
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 -
If we select a channel, we run the package to select the resource file based on the channel name (see point 6).
-
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 -
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 enter
BuildConfig
Take a look, it also contains some current package name version number and other information.
-
-
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 is
com.xxx.xx
, the version number is1
, version name1.0.0
. - through
BuildConfig
callQQ_ID
Static constants, which are the values configured in the channel,WX_ID
In the same way. manifestPlaceholders
The 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 to
buildTypes
Signature configuration insigningConfig
If it is not set tonull
, so is there a package or with the built-in signature package.
- 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 willxemh
The 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 it
app_name
Modify the strings under appapp_name
I don’t need to write anything else.
- 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
-
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 -
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