【編者按】Nicolas Frankel 是 hybris 的高級(jí)顧問, 在Java / J2EE 領(lǐng)域擁有超過10年的管理經(jīng)驗(yàn),本文闡述了他在使用自動(dòng)化工序去構(gòu)建 Android 應(yīng)用程序遇到的一些難題,大家不妨讀讀,希望能有所收獲。
以下系譯文:
在我目前的工作中,我必須使用一些自動(dòng)化工序去構(gòu)建 Android 應(yīng)用程序。這篇文章的目的就是描述我所遇到的難題,避免讀者在這個(gè)過程浪費(fèi)更多時(shí)間。目的就是分享我在這一過程中所遇到的困難,為讀者提供前車之鑒,從而節(jié)省寶貴的時(shí)間。
環(huán)境搭建如下:
- 使用 Puppet 搭建基礎(chǔ)設(shè)施
- 使用 Jenkins 搭建 CI 服務(wù)器
- 工程文件
- 來構(gòu)建主體
- 作為主要測試工具
Puppet and Jenkins

事實(shí)上,我的準(zhǔn)備工作已經(jīng)相當(dāng)完備。同事們已經(jīng)使 Jenkins 服務(wù)器可以自動(dòng)安裝,以及準(zhǔn)備好了所需的軟件包——包括 Java 和已提供的可復(fù)用的 Puppet 類。Jenkins 的工作完全依賴于一個(gè)單一 config.xm
文件,即不同部分的封裝。每部分都由一個(gè)專門的模板處理。因此,在我看來,創(chuàng)建一個(gè)簡單的 Gradle 任務(wù)就如同在公園里散步一般輕松,最多幾天時(shí)間便可以完成。
第一步非常容易:只需一個(gè)最新版 Puppet 清單,能幫助你添加 Gradle 插件到 Jenkins 服務(wù)器。
The Gradle wrapper
如果你是我博客的忠實(shí)讀者,那你大概知道我對(duì) Gradle 的看法。不過,我必須得承認(rèn),Maven 的確缺乏這種兼容性,即不論安裝哪個(gè)版本的工具都確保編譯成功——雖然它應(yīng)該具備該功能。為了實(shí)現(xiàn)這一目標(biāo),Gradle 通過提供一個(gè) JAR、一個(gè) shell 腳本和一個(gè)屬性文件,屬性文件還包含了從 URL 到 Gradle ZIP 的分發(fā),組裝成所謂的包裝機(jī)制。這三個(gè)需求都被存儲(chǔ)在 SCM 中。
然而這正是麻煩的開始。在一個(gè)企業(yè)環(huán)境中進(jìn)行下載,意味著要通過和驗(yàn)證代理。最簡單的選擇莫過于在工作配置下設(shè)置好一切,包括代理憑證。然而,從安全的角度來看,這樣的做法并不理想,因?yàn)槿魏稳嗽L問 Jenkins 接口或文件系統(tǒng),都能夠讀取這些憑據(jù)。顯然,我們需要一個(gè)更好的方式。
用戶已經(jīng)擁有了配置代理完備的 Nexus 庫。上傳所需的 Gradle 分布,并更新指向它的 gradle.properties,簡直易如反掌。
The Android SDK

Android SDK 只是一個(gè) ZIP 文件。我用同樣的方法:先下載文件然后將其上傳到 Nexus。這一步之后,一個(gè) Puppet 腳本會(huì)負(fù)責(zé)下載、提取,并為它設(shè)置正確的權(quán)限。
然而,事情并沒有想象的那么簡單。Android 開發(fā)者都知道,Android SDK 需要手動(dòng)操作:開發(fā)者必須手動(dòng)檢查所需平臺(tái)和工具,并將其下載在本地文件系統(tǒng)。這看上去很簡單不是嗎,但如果轉(zhuǎn)為自動(dòng)操作則會(huì)讓很多開發(fā)者頭疼,盡管有一個(gè)命令行相當(dāng)于可以通過 SDK「帶有 --no-ui
參數(shù)」來安裝/更新包。如果你想了解更多,請(qǐng)點(diǎn)擊這里。
谷歌工程師未能提供的兩個(gè)重要參數(shù):
- Proxy credentials – login/password
- Accepting license agreements
為了解決這一問題,網(wǎng)上有很多蹩腳的方案,最誘人的應(yīng)該要數(shù)配置文件了,但我卻發(fā)現(xiàn)它們沒多大用。然而,通過 expect
命令的使用,我反而發(fā)現(xiàn)了一種創(chuàng)造性的解決辦法。Expect 是一個(gè)漂亮的命令,用來讀取標(biāo)準(zhǔn)輸出,并用標(biāo)準(zhǔn)輸入進(jìn)行相應(yīng)的填寫。值得一提的是,它竟然還可以接受正則表達(dá)式。所以,在請(qǐng)求代理登錄時(shí),你鍵入登錄名、填寫密碼,當(dāng)它要求許可證接受時(shí),你鍵入「同意」就能輕松搞定。雖然我反復(fù)多次試驗(yàn),歷經(jīng)很多錯(cuò)誤,才達(dá)到預(yù)期結(jié)果。但這個(gè)方法非常簡單、直接。
我最初的設(shè)計(jì)是,使所有可能用到的裝有 Puppet 的 Android 包,成為服務(wù)器配置的一部分。在標(biāo)準(zhǔn)化操作中「如文件創(chuàng)建或系統(tǒng)包安裝」,Puppet 可以確定這項(xiàng)配置是否必要。例如:如果某文件已經(jīng)存在,那就沒有必要再重復(fù)創(chuàng)建了。在最后的 Log 中,Puppet 會(huì)報(bào)告它執(zhí)行的每一個(gè)操作。起初,我試圖通過在配置過程中,人為地告訴 Puppet 哪個(gè)包是已配置的,因?yàn)?Android SDK 為每個(gè)包都創(chuàng)建一個(gè)文件夾。但要命的是,Puppet 只接受單一文件夾來驗(yàn)證。對(duì)于某些包來說,并沒有任何版本信息「例如 Google 游戲服務(wù)」。
因此,一個(gè)同事提出將 Puppet 配置的更新,移動(dòng)到每個(gè)任務(wù)的預(yù)先步驟中。這樣能修復(fù)非冪等問題,同時(shí),還能在每個(gè)任務(wù)中更新所需配置。
Robolectric

說到這里,我本以為一切都搞定了。但非常不幸,并不是這么回事,就因?yàn)檫@個(gè)庫—— Robolectric。
此前,我對(duì) Robolectric 沒什么了解,只知道這是一個(gè)測試庫,能夠在 Android 上運(yùn)行測試,而無需任何物理設(shè)備的連接。在 Jenkins 上試圖編譯時(shí),我偶然發(fā)現(xiàn)了一個(gè)「有意思」的問題:盡管 Roboletric 提供了一個(gè)具有完整依賴性的 POM, 但 MavenDependencyResolver
類硬編碼庫應(yīng)該從哪里下載?
唯一的解決辦法是通過擴(kuò)展上面的類來實(shí)現(xiàn)。我用的就是上面提到的企業(yè) Nexus 庫。
上傳并發(fā)布任務(wù)
為了實(shí)現(xiàn)前面的任務(wù),我只需添加一個(gè)自定義 Gradle 任務(wù),從 settings.xml
得到 Nexus 設(shè)置「由 Puppet 調(diào)配」。基于此,我成功地上傳了任務(wù)。最后,對(duì)于每個(gè)任務(wù)執(zhí)行的型號(hào),我添加到所上傳的工件集的輸出文件中。因此,不管編譯文件是哪種型號(hào)配置,下面的命令都將只上傳 XXX 和 YYY:
./gradlew assembleXXX assembleYYY upload
上面的任務(wù)都搞定了,那發(fā)布豈不是更簡單:唯一需要的就是設(shè)置 Gradle 插件,它添加了一個(gè)發(fā)布任務(wù),類似于 Maven 的 deploy。
結(jié)束語
作為后端開發(fā)人員,我已經(jīng)習(xí)慣于持續(xù)集成設(shè)置,毫無疑問,我可以在幾天內(nèi)搞定 Android CI 的進(jìn)程。對(duì)于 Android 系統(tǒng)在 CI 上的欠缺,我覺得不可思議。每一步都苦不堪言,糟糕的記錄「如果有的話」和解決方案似乎更像黑客般具有破壞性。如果你想沿著這條路走下去。吶,別說我沒告訴你……Good Luck!
原文鏈接: Fully Automated Android Build
本文系 OneAPM 工程師編譯整理。OneAPM 是應(yīng)用性能管理領(lǐng)域的新興領(lǐng)軍企業(yè),能幫助企業(yè)用戶和開發(fā)者輕松實(shí)現(xiàn):緩慢的程序代碼和 SQL 語句的實(shí)時(shí)抓取。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問 OneAPM 官方博客。