Android APP基於Android Studio多版本構建實戰
關於多版本構建,我們可以通過buildTypes來新增構建型別,一般而言這裡也不需要自行定義,預設會生成debug和release兩種型別。
重點在於使用productFlavors生成不同“風味”的版本,我們可以構建標準版和中性版APP,這在企業應用中非常普遍,中性版本不含有logo資訊,可再次貼牌等。同時應該在release版本中關閉log輸出。
我這裡的區別為中性版assets/defaultLogo/logo_default.png和標準版的資源是不一樣的,所以需要建立對應的productFlavor資料夾,放入需要替換的logo_default.png資源。同時不論是中性版還是標準版還有四種機型需要適配(F600P、F600、F200P、F200),也就是說我們需要生成8個版本的app。
1.目標
生成多個不同“風味”的版本很容易,只要定義productFlavors即可。但目標應該更進一步,通過只敲一次命令列生成所有的標準版和中性版release app,並且按照我們的命令格式重新命名apk檔案,最後只需要到build/outputs/apk拿出對應的安裝包。
2.使用Gradle命令列
(1).檢視gradle命令是否可用
預設情況下開啟Android Studio的Terminal面板,鍵入gradle -version會提示找不到這個命令,無法使用,這需要配置環境變數。
(2).檢視Android Studio自帶Gradle安裝位置
在File-Settings中檢視Gradle home(Android Studio安裝後會自帶一個Gradle,就在其安裝目錄gradle下)所在位置。
(3).配置Gradle環境變數
建議使用使用者變數,只有當前使用者可用。新增一個變數名為GRADLE_HOME、變數值為E:/as/gradle/gradle-2.14.1的環境變數。然後編輯path環境變數,新建一行,並填入%GRADLE_HOME%/bin/即可。
(4).驗證
再次在Terminal中輸入gradle –version命令後回車。出現下面的gradle版本資訊,就說明gradle構建工具在命令列方式下已經正常工作。
3.關閉log開關
當然前提是log可以全域性通過一個boolean值控制。
使用以下函式可以判斷是否debug版本,非debug版本也就是正式版關閉log即可。
/**
* 判斷當前應用是否debug狀態
*
* @param context
* @return
*/
public static boolean isApkDebug(Context context) {
ApplicationInfo info = context.getApplicationInfo();
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
我在自定義的Application(MainApp.java)中根據判斷是否開啟log開關。
// Log開關
Logger.DEBUG = AppUtils.isApkDebug(this);
4.配置正式簽名
android{
…
signingConfigs {
releaseSign {
storeFile file("E:\\xx\\xx\\xx.keystore")
storePassword "xxxxx"
keyAlias "xx"
keyPassword "xxxxx"
}
}
buildTypes {
release {
signingConfig signingConfigs.releaseSign
…
}
}
…
}
這段程式碼很好理解,通過在android閉包下配置signingConfigs,並在buildTypes的release下引用即可。
5.配置productFlavors
productFlavors {
// 中性版本F600P
neutralF600P {
// 這裡替換了assets內logo_default.png資源
manifestPlaceholders = getVersionNameMap('F600P')
}
// 標準版本F600P
standardF600P {
manifestPlaceholders = getVersionNameMap("F600P")
}
// 中性版本F600
neutralF600 {
manifestPlaceholders = getVersionNameMap("F600")
}
// 標準版本F600
standardF600 {
manifestPlaceholders = getVersionNameMap("F600")
}
// 中性版本F200P
neutralF200P {
manifestPlaceholders = getVersionNameMap("F200P")
}
// 標準版本F200P
standardF200P {
manifestPlaceholders = getVersionNameMap("F200P")
}
// 中性版本F200
neutralF200 {
manifestPlaceholders = getVersionNameMap("F200")
}
// 標準版本F200
standardF200 {
manifestPlaceholders = getVersionNameMap("F200")
}
}
通過在android閉包下配置8個版本的flavor,並給中性版建立對應的資料夾,放入需要替換的logo_default.png資源。Flavor對應的資料夾位於src下,和main平級。
src->
main
neutralF600P
neutralF600
neutralF200P
neutralF200
再來看getVersionNameMap(key)函式。
/**
* 根據key獲取version Map
* @param key
* @return
*/
def getVersionNameMap(key) {
def versionNameMap = [:]
if (key == "F600P") {
versionNameMap.D_VERSION_NAME = "F600P"
} else if (key == "F600") {
versionNameMap.D_VERSION_NAME = "F600"
} else if (key == "F200P") {
versionNameMap.D_VERSION_NAME = "F200P"
} else if (key == "F200") {
versionNameMap.D_VERSION_NAME = "F200"
} else {
versionNameMap.D_VERSION_NAME = "F600P"
}
return versionNameMap
}
解釋一下:manifestPlaceholders是android gradle dsl中定義的屬性,用來替換其中的一些欄位,使用groovy GString在AndroidMainifest中佔位,通過給manifestPlaceholders賦值Map,將Map中的值替換到AndroidMainifest中。
AndroidMainfest.xml中定義如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx" >
<application
android:name="xx.MainApp"
android:allowBackup="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
…
<meta-data
android:name="D_VERSION"
android:value="${D_VERSION_NAME}" />
…
</application>
</manifest>
Java程式碼中通過meta-data的對應value去配置為不同的版本,如F600P或F600,當然這部分程式碼需要在Java中實現對應的邏輯。
/**
* 通過key獲取Application標籤下meta-data的值
*
* @param context
* @param key
* @return
*/
public static String getApplicationMetaDataVal(Context context, String key) {
ApplicationInfo appInfo = null;
String msg = null;
try {
appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
msg = appInfo.metaData.getString(key);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return msg;
}
接下來再看轉化為版本Version的程式碼,則部分程式碼並非必須的,相信看到這裡已經明白了,我們是間接的用AndroidMainfest中的meta-data標籤傳遞在build.gradle配置的值,從而達到讓java程式碼載入不同版本的目的。
// 獲取不同版本的對應配置引數
Version version = Version.F600P;
String versionName = AppUtils.getApplicationMetaDataVal(this, "D_VERSION");
if (versionName.equals(Version.F600P.getName())) {
version = Version.F600P;
} else if (versionName.equals(Version.F600.getName())) {
version = Version.F600;
} else if (versionName.equals(Version.F200P.getName())) {
version = Version.F200P;
} else if (versionName.equals(Version.F200.getName())) {
version = Version.F200;
}
Log.d("version", "v:" + version.getName());
versionConfig = VersionFactory.getVersionConfig(version);
6.定製重新命名邏輯
android {
…
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
String flavorName = variant.productFlavors[0].name
//修改apk檔名
def fileName = "xxx-${flavorName}-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk"
def fileParentDir
if (flavorName.startsWith("standard")) {
fileParentDir = flavorName.substring("standard".length(), flavorName.length());
} else if (flavorName.startsWith("neutral")) {
fileParentDir = flavorName.substring("neutral".length(), flavorName.length());
}
File dir = new File(outputFile.parent + File.separator + fileParentDir + File.separator)
if (!dir.exists()) {
dir.mkdirs()
}
output.outputFile = new File(dir, fileName)
}
}
}
…
}
xxx-${flavorName}-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk
這一大段就是重新命名後的apk檔名,最終生成的檔名為:
xxx-neutralF600P-v1.0.5-2455-signed-20170502.apk
很好懂,用GString的寫法佔位。其中後面還有一段的邏輯為把同一型號下標準版和中性版的app放到同一目錄下,如F600P資料夾下,放入neutralF600P 和standardF600P app。
最後來看一下releaseTime()函式,作用為按照yyyyMMdd格式化當前時間,即格式化為年月日的形式。
def releaseTime() {
return new Date().format("yyyyMMdd",TimeZone.getTimeZone("UTC"))
}
7.最後一步
以上步驟配置好了生成簽名版程式的所有要素,最後在android studio命令列中敲入:
gradle aR
回車後任務開始進行,全部finish後到xx工程\xx\build\outputs\apk下取出apk即可。
詳解:
之所以可以使用gradle aR命令是因為gradle支援這種簡短的駝峰式寫法,實際上和gradle assembleRelease命令是等價的。
另外,我們使用這個命令生成的apk全部為release版本,雖然通過gradle build和gradle assemble都可以生成我們想要的release版本,但gradle build同時生成了debug版本,這對於我們來說是多餘的。