文檔概述
關(guān)于Android開發(fā),除了技術(shù)方面需要掌握,還有發(fā)布流程需要了解。本文檔就包括以上兩個方面,主要介紹:
- 使用配置文件配置不同功能的apk
- 使用gradle為Android構(gòu)建簽名包
- Jenkins集成Android自動化打包
- 使用gradle為Android生成不同配置的簽名包
一、場景描述
在項目開發(fā)中,我們可能有多個功能有區(qū)別但是整體框架一致的工程。
情景:我們有一app,基礎(chǔ)功能包括a,b,c,擴(kuò)張功能包括d,e,f,其中客戶1需要擴(kuò)展功能d,f,客戶2需要e,f。
二、配置文件簡介
鑒于以上場景,開發(fā)app過程應(yīng)該怎么做?如果客戶1創(chuàng)建一份工程代碼,客戶2創(chuàng)建一份工程代碼效率就太低了。因此我們需要另辟思路,采用配置文件的方式進(jìn)行開發(fā)。
配置文件分類
關(guān)于配置文件目前有兩種方式:
- apk文件寫注釋
- apk源碼屬性文件
配置方式的區(qū)別
方式1主要適用于輕量的注釋,例如書寫渠道名等一些簡單的注釋,不用修改源碼。方式2就比較適合當(dāng)前的場景,但是缺點在于配置文件在源碼部分,所以修改配置文件就必須修改源碼。
配置文件的使用
關(guān)于方式1的使用,可以: 美團(tuán)批量打包
基本原理就是在apk文件生成之后,修改apk文件的部分字段,而不影響apk本身簽名驗證,在源碼中根據(jù)apk安裝的位置獲取安裝包文件再讀取其中的字段。
方式2就是使用屬性文件。實現(xiàn)方式就是使用java.util.Properties類進(jìn)行文件加載。
預(yù)先在Android工程的main文件夾下創(chuàng)建assets文件夾,里面存放配置文件,客戶1的配置文件命名為:a.properties,b.properties,里面的內(nèi)容如下:
fun_a=true
fun_b=true
fun_c=true
fun_d=true
fun_e=false
fun_f=true
讀取配置屬性代碼:
public String funX(Context context, String x){
Properties props = new Properties();
try {
props.load(context.getAssets().open("a.properties"));
// props.load(context.getAssets().open("b.properties"));
} catch (IOException e) {
e.printStackTrace();
}
return props.getProperties("fun_" + x);
}
在源碼中根據(jù)對應(yīng)函數(shù)的配置信息決定是否執(zhí)行對應(yīng)操作。
對于客戶1和客戶2的不同需求可以選擇性的加載配置文件進(jìn)行打包操作。
以上就是不同需求的具體操作。但是我們可以發(fā)現(xiàn),這個操作效率太低,每次打包都要修改源碼。下面介紹自動化過程。
三、 自動化之gradle打包
配置操作
為了之后可以執(zhí)行自動化操作,簽名必須也要能進(jìn)行自動化執(zhí)行。
首先放置簽名文件到:項目的根目錄。
在module的build.gradle文件中添加以下內(nèi)容:
apply plugin: 'com.android.application'
def keystorePSW = ''
def keystoreAlias = ''
def keystoreAliasPSW = ''
// default keystore file, PLZ config file path in local.properties
Properties properties = new Properties()
// local.properties file in the root director
properties.load(project.rootProject.file('gradle.properties').newDataInputStream())
keystorePSW = properties.getProperty("keystore.password")
keystoreAlias = properties.getProperty("keystore.alias")
keystoreAliasPSW = properties.getProperty("keystore.alias_password")
android {
...
signingConfigs {
release {
keyAlias keystoreAlias
keyPassword keystoreAliasPSW
storePassword keystorePSW
storeFile file('../xxx.jks') // 簽名文件的位置
}
}
buildTypes {
release {
minifyEnabled false
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
dependencies {
...
}
為了能夠在Jenkins環(huán)境也能使用自動打包操作,在項目的根目錄gradle.properties文件中添加如下內(nèi)容:
keystore.password = xxx // 密鑰的密碼
keystore.alias = xxx // 密鑰的別稱
keystore.alias_password = xxx // 別稱的密碼
隨后在項目的根目錄下使用gradle即可進(jìn)行打包。
執(zhí)行命令
打包命令:
gradlew clean // 清除build文件夾
// 二選一
gradlew build // 檢查依賴并編譯打包,會生成debug和release兩個包
gradlew assesmRelease // 生成release包
執(zhí)行以上命令之后,會在項目的app/build/output/apk/*.apk生成對應(yīng)的apk。
注:Windows操作系統(tǒng)使用gradlew,Linux系統(tǒng)使用./gradlew。
以上,使用gradle自動打包就以完成。下面就是集成Jenkins持續(xù)集成環(huán)境了。
四、Jenkins集成
創(chuàng)建項目
關(guān)于構(gòu)建介紹可以參考:Jenkins+Gradle實現(xiàn)android開發(fā)持續(xù)集成、打包
配置信息沒什么特別的。主要是輸出文件路徑:直接填寫app/build/output/app/*.apk即可。

可能遇到的問題
-
由于Jenkins自動構(gòu)建,所以對語法要求比較嚴(yán)格。如果出現(xiàn)以下錯誤:
FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:lint'. > Lint found errors in the project; aborting build. Fix the issues identified by lint, or add the following to your build script to proceed with errors: ... android { lintOptions { abortOnError false } } ... * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.這個是因為代碼不符合規(guī)范,lint檢查時報錯,因此中斷了整個編譯過程。
只要在當(dāng)前app的
app/build.gradle文件內(nèi)增加如下代碼:android{ ... lintOptions{ abortOnError false } ... } -
安裝Jenkins配置局域網(wǎng)訪問
在mac安裝Jenkins之后,局域網(wǎng)無法訪問,原因在于使用brew安裝jenkins會避免很多其他安裝方式產(chǎn)生的用戶權(quán)限問題,但是會將httpListenAddress默認(rèn)設(shè)置為127.0.0.1,這樣我們雖然可以在本地用localhost:8080訪問,但是本機(jī)和局域網(wǎng)均無法用ip訪問。解決辦法為修改兩個路徑下的plist配置。
~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist /usr/local/opt/jenkins/homebrew.mxcl.jenkins.plist修改之后重啟Jenkins即可訪問。
brew services start jenkins // 開啟服務(wù) brew services stop jenkins // 關(guān)閉服務(wù) brew services restart jenkins // 重啟服務(wù) -
更多錯誤查看:
通過以上操作,Android項目就可以使用Jenkins自動集成了。
但是我們第一部分的場景問題還是沒有解決,如何自動化區(qū)分客戶的版本呢?
五、自動化版本區(qū)分
Android官網(wǎng)介紹了 構(gòu)建變體
通過配置不同的 productFlavors我們可以獲取不同版本的apk。
因此第一部分的需求通過以下操作實現(xiàn)。
更新buidl.gradle文件
android {
...
buildTypes {
...
}
productFlavors {
fun_a {
buildConfigField "String", "CONF_NAME", "\"a.properties\""
}
fun_b {
buildConfigField "String", "CONF_NAME", "\"b.properties\""
}
}
}
- 注1:字段解釋參考 GRADLE自定義你的BUILDCONFIG
- 注2:使用
String屬性時候,值需要使用\"進(jìn)行轉(zhuǎn)義
修改讀取配置文件代碼
配置屬性文件不變,讀取的代碼進(jìn)行如下修改:
public String funX(Context contex, tString x){
Properties props = new Properties();
try {
props.load(context.getAssets().open(BuildConfig.CONF_NAME));
} catch (IOException e) {
e.printStackTrace();
}
return props.getProperties("fun_" + x);
}
之后使用打包命令就會生成兩個apk安裝包,一個是客戶1定制的功能,一個是客戶2定制的功能。
輸出路徑不變,生成包名:
- app-fun_a-releas.apk
- app-fun_b-releas.apk
這樣,Jenkins一次編譯也就可以獲取到正確的版本。
修改輸出文件的包名
以上操作之后已經(jīng)可以正確獲取目標(biāo)apk,但是并不直觀。如果可以在輸出文件名中添加輸出版本號和打包時間就完美了。
在項目的build.gradle文件中,最后節(jié)點添加:
def releaseTime() {
return new Date().format("yyyyMMdd-HHmm", TimeZone.getTimeZone("GMT+08:00"))
}
android{
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (variant.buildType.name.equals('release')) {
def fileName = outputFile.name.replace("app-","").replace("release", "v${defaultConfig.versionName}-${releaseTime()}")
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
修改之后輸出文件名:
fun_a-v2.0.5-20171115-1655.apk
fun_b-v2.0.5-20171115-1655.apk
以上。
六、小結(jié)
通過使用gradle+Jenkins,可以讓程序員從繁復(fù)的打包任務(wù)中解放出來,更多時間去做核心開發(fā)的相關(guān)業(yè)務(wù)。
gradle的功能強(qiáng)大到我沒法想象,好好學(xué)習(xí),好好鉆研。
技術(shù),可以解放你。