寫這篇文章主要是因?yàn)?公司的項(xiàng)目對(duì)打包有多重需求,有哪些需求呢?
1.多渠道,這是最基本的;
2.分為3個(gè)版本(內(nèi)地版,海外版,精簡版),每個(gè)版本包名不同(這就意味著第三方sdk的各種key都不一樣),且logo,部分圖標(biāo)也不一樣;
3.開發(fā)版本的區(qū)別,分為debug版,pre預(yù)覽版,release正式版;
4.開發(fā)版本的區(qū)別要通過app名稱直接體現(xiàn)在桌面上,即apk安裝后,app名稱直接是XXdebug版,XXrelease版本
現(xiàn)在我們就分別來實(shí)現(xiàn)以上的需求
一.多渠道打包
gradle的productFlavors用來配置渠道,這里主要介紹一下友盟的多渠道打包配置
- 在AndroidManifest.xml中做如下配置
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
${...}這個(gè)占位符很重要,下面我們動(dòng)態(tài)配置一些屬性的時(shí)候都會(huì)用到這個(gè)占位符
- 在項(xiàng)目app這個(gè)module下的gradle中配置渠道,注意這個(gè)productFlavors是配置在android下的
//配置多渠道
flavorDimensions "default"
productFlavors {
wandoujia {dimension "default"}
_360 {dimension "default"}
baidu {dimension "default"}
xiaomi {dimension "default"}
tencent {dimension "default"}
taobao {dimension "default"}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
-
如上配置后,在AndroidStudio的Build->Select Build Variants ,可以看到每個(gè)渠道都有debug和release兩個(gè)版本
多渠道.png
- 4 多版本apk安裝在同一手機(jī)上,也就是打多版本
這是看到了這位大神的文章,覺得講的比我寫的更詳細(xì),下面部分就直接使用大神文章,謝謝作者
這種情況只需要我們提供不同包名的apk即可完成。因?yàn)橹灰獞?yīng)用包名不一樣即使簽名信息一樣還是可以同時(shí)安裝在同一臺(tái)手機(jī)上的,因此我們應(yīng)該在打包成apk時(shí)修改應(yīng)用的包名就可以達(dá)到目的啦。接下來我們進(jìn)入實(shí)際操作過程。這里我們先介紹一個(gè)知識(shí)點(diǎn),請(qǐng)直接看下圖:
image.png
當(dāng)然從截圖也可以看出,配置多apk打包和上一篇文章配置多渠道打包是一樣的,都是在productFlavors中配置的。如上圖,我們在productFlavors中配置了兩種flavor的apk信息一種是Beta版,一種是Releases版,同時(shí)每個(gè)flavor中我們都重新配置applicationId這個(gè)屬性,通過這個(gè)屬性我們就可以使打包出來的apk包名產(chǎn)生對(duì)應(yīng)的變化啦。至于為什么重新配置了applicationId就行呢,原因圖已經(jīng)說明啦,就是因?yàn)閐efaultConfig是Beta版和Releases版flavor的基礎(chǔ)配置,只要我們重寫了applicationId這個(gè)屬性就會(huì)覆蓋defaultConfig中相對(duì)應(yīng)屬性的信息,從而使打包出來的兩種apk的包名不一樣,達(dá)到在同一臺(tái)手機(jī)上安裝的目的。那么applicationId又是什么呢?看下圖(因此我們更改其實(shí)就是package屬性)


比如說不同版本要使用不同的icon,這時(shí)該如何做呢?實(shí)際上還是在productFlavors的每個(gè)flavor中通過manifestPlaceholders屬性配置即可,manifestPlaceholders是一個(gè)類似HashMap的容器,因此在manifestPlaceholders可以配置多個(gè)屬性,以便在AndroidManifest.xm中使用,比如我們需要為每種版本的apk替換特定的icon和appName這時(shí)我們可以這樣如下配置:

然后在AndroidManifest.xm中這樣使用即可:

defaultConfig {
applicationId "com.zejian.multi_versionapk"
minSdkVersion 10
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug4zj
minifyEnabled true
zipAlignEnabled true
}
}
//配置多版本的apk
productFlavors{
Beta{
applicationId "com.zejian.multi_versionapk.beta"
manifestPlaceholders = [app_name:"multi_versionapk.beta" ,icon: "@mipmap/ic_launcher_beta"]
//在java代碼中具體的使用方式為:context.getResources().getString(R.string.strKey);
resValue("string" , "strKey","beta版本")
}
Releases{
applicationId "com.zejian.multi_versionapk.release"
manifestPlaceholders = [app_name:"multi_versionapk.release",icon: "@mipmap/ic_launcher_releases"]
resValue("string" , "strKey","release版本")
}
}
}

同時(shí),我們Generate signed APK就可以打出release和debug的每個(gè)版本和每個(gè)渠道的包

至此,我們就可以通過Build Variants來控制當(dāng)前的運(yùn)行環(huán)境,來決定當(dāng)前的包是屬于哪個(gè)版本和哪個(gè)渠道
二. 多渠道包跟多版本包不同的內(nèi)容,如何來區(qū)分,也就是文章開頭第二條中的icon,logo,包名等信息不同時(shí)候如何來區(qū)別對(duì)待,那就要用到manifestPlaceholders了,文章上面說過,我們使用${...}占位符能夠動(dòng)態(tài)替換AndroidManitest里面的很多東西,如何操作請(qǐng)往下看
wandoujia {
dimension "default"
manifestPlaceholders = [
applicationId :"com.app.test",
app_name : "@string/AppName",
icon : "@string/ic_launcher",
app_key : "1234567",
umeng_channel: "豌豆莢"
]
}
wandoujia_oas {
dimension "default"
manifestPlaceholders = [
applicationId :"com.app.oas",
app_name : "@string/AppNameOas",
icon : "@string/ic_launcher",
app_key : "910JQKA",
umeng_channel: "豌豆莢海外版"
]
}
<application
android:allowBackup="false"
android:icon="${icon}"
android:label="${app_name}"
android:persistent="true"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup,android:label, android:icon, android:theme">
這樣第三方SDK的不同key都可以采用占位符來動(dòng)態(tài)替換,就像上面,當(dāng)我們打海外版包時(shí),就會(huì)將app_key和logo替換成另一套
三. 針對(duì)debug版本和release版本一些控制,就要用到 buildConfigField 了,buildConfigField 能夠在debug和release中生成不同的BuildConfig,我們在開發(fā)中的是否需要打印日志的控制就可以使用它
buildTypes {
release {
minifyEnabled false
debuggable false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("boolean","show_log","false")
}
//預(yù)覽版
pre {
minifyEnabled false
debuggable false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("boolean","show_log","true")
}
debug {
minifyEnabled false
debuggable true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("boolean","show_log","true")
}
}
配置之后我們得到的BuildConfig如下,在代碼中使用直接BuildConfig.xxx就可以
public final class BuildConfig {
public static final boolean DEBUG = false;
public static final String APPLICATION_ID = "kotlin.app.com.gradledemo";
public static final String BUILD_TYPE = "release";
public static final String FLAVOR = "baidu";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
// Fields from build type: release
public static final boolean show_log = false;
}
除了日志開關(guān),其他用的最多的地方還有url環(huán)境的切換,比如debug版本一般我們會(huì)用內(nèi)網(wǎng)的url,那么release版本就需要用生產(chǎn)環(huán)境的url
四. 要實(shí)現(xiàn)開篇的不同版本的apk名稱不一樣,就需要用到resValue了,它能夠定義資源,并且可以在debug和release等不同版本中來分別定義,例如resValue "string" 就是字符串資源,可以用R.String 來引用對(duì)應(yīng)的字符串資源
//在debug下設(shè)置如下
resValue "string","AppName","測試DEV"
resValue "string","AppNameOas","測試海外版DEV"
resValue "string","AppNameSimple","測試1精簡版DEV"
這時(shí)候一編譯,就會(huì)自動(dòng)在R.String下生成AppName,AppNameOas,AppNameSimple三個(gè)字符串,通過R.String.xxx就可以引用到;
注意:需要將之前values下string下的appName刪除,否則會(huì)報(bào)資源重復(fù)異常
五. 全局配置文件
project的build.gradle中的ext可以為各位module進(jìn)行全局配置參數(shù),防止各個(gè)module之間的不統(tǒng)一,不可控。而且當(dāng)我們升級(jí)sdk、build tool、target sdk等,幾個(gè)module都要更改,非常的麻煩。
在項(xiàng)目根目錄建立gradleConfig,然后
ext {
//統(tǒng)一管理依賴版本
android = [
compileSdkVersion: 25,
buildToolsVersion: '25.0.2',
applicationId : "com.test.xxx",
minSdkVersion : 17,
targetSdkVersion : 24,
versionCode : 2
versionName : "0.1"
]
def dependVersion = [
support: "25.1.0"
]
dependencies = [
"appcompat-v7" : "com.android.support:appcompat-v7:${dependVersion.support}",
"cardview-v7" : "com.android.support:cardview-v7:${dependVersion.support}",
"recyclerview-v7": "com.android.support:recyclerview-v7:${dependVersion.support}",
"design" : "com.android.support:design:${dependVersion.support}",
"support-v4" : "com.android.support:support-v4:${dependVersion.support}"
]
}
然后在每個(gè)module中引用
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.xxx.xxx"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
}
}
六. module 調(diào)整目錄結(jié)構(gòu)sourceSets
默認(rèn)情況下,java文件和resource文件分別在src/main/java和src/main/res目錄下,在build.gradle文件,andorid{}里面添加下面的代碼,便可以將java文件和resource文件放到src/java和src/resources目錄下。
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
簡便寫法
sourceSets {
min.java.srcDirs = ['src/java']
min.resources.srcDirs = ['src/resources']
}

