作者:vivo 網際網路使用者端團隊- Wang Zhenyu
本文主要講述了Android使用者端模組化開發的痛點及解決方案,詳細講解了方案的實現思路和具體實現方法。
說明:本工具基於vivo網際網路使用者端團隊內部開源的編譯管理工具開發。
現在使用者端的業務越來越多,大部分使用者端工程都採用模組化的開發模式,也就是根據業務分成多個模組進行開發,提高團隊效率。例如我們vivo官網現在的整體架構如下圖,分為13個模組,每個模組是一個獨立程式碼倉。
(注:為什麼這麼分,可以參考之前的一篇文章《Android模組化開發實踐》)
完全隔離的程式碼倉,使每個模組更獨立,更易於程式碼管理,但也帶來了一些問題。
1、開發階段,子倉開發以及整合開發偵錯,操作麻煩、易出錯、難跟蹤回溯
1.1、當開發時涉及的模組較多時,需要手動一個一個拉程式碼,多個子倉的程式碼操作非常麻煩,並且需要開啟多個AndroidStudio進行開發;
1.2、子倉整合到主倉開發偵錯,有兩種方式,但是都有比較大的缺點:
(1)方式1,子倉通過maven依賴,這種方式需要不斷的釋出子倉的snapshot,主倉再更新snapshot,效率較低;
(2)方式2,子倉通過程式碼依賴,也就是需要在主倉的settings.gradle中,手動include拉到原生的子倉程式碼,然後在build.gradle中設定dependencies,設定繁瑣,容易出錯;
1.3、主倉對子倉的依賴,如果是部分maven依賴、部分程式碼依賴,容易出現程式碼衝突;
1.4、apk整合的子模組aar和程式碼,沒有對應關係,排查問題時很難回溯。
2、版本釋出階段,流程繁瑣,過多重複勞動,流程如下:
2.1、逐個修改子倉的版本,指定snapshot或release;
2.2、每個子倉需要提交修改版本號的程式碼到git;
2.3、每個子倉都要手動觸發釋出maven倉;
2.4、更新主倉對子倉依賴的版本;
2.5、構建Apk;
2.6、如果用持續整合系統CI,則每個子倉都需要設定一個專案,再逐個啟動子倉的編譯,等子倉全部編譯完再啟動主倉編譯。
針對上述問題,我們優化的思路也很明確了,就是以自動化的方式解決繁瑣和重複的操作。最終開發了ModularDevTool,實現以下功能:
1、開發階段
1.1、在主倉中,管理所有子倉程式碼(拉程式碼、切分支及其他git操作),管理子倉相關資訊(程式碼倉路徑、分支、版本等);
1.2、只需要開啟一個AS工程,即可進行所有倉的程式碼開發;
1.3、對子倉的兩種依賴方式(程式碼依賴和maven依賴)一鍵切換,支援混合依賴(即部分倉程式碼依賴,部分倉maven依賴);
1.4、編譯時輸出子模組的版本及對應commitid,便於回溯跟蹤程式碼。
2、版本釋出階段
2.1、只需要在主倉修改子倉版本號,子倉無需修改,省去子倉程式碼修改和提交程式碼過程;
2.2、CI上只要配一個主倉專案,實現一鍵編譯,包括子倉編譯aar(按依賴關係順序編譯)、上傳maven、編apk;
2.3、CI上支援3種編譯模式:
OnlyApp:即只編譯主倉程式碼生成apk(前提是子模組已釋出maven);
publishSnapshot:即子倉編譯上傳snapshot版本,然後編譯主倉生成apk;
publishRelease:即子倉編譯上傳release版本,然後編譯主倉生成apk。
工具採用了shell指令碼+gradle外掛的方式實現的。
首先看下工程目錄概覽
1、submodules目錄是用來存放子倉程式碼的,子倉程式碼就是正常的工程結構,submodules目錄如下圖:
2、repositories.xml檔案是用來設定子倉資訊的,包括模組名、程式碼倉、分支、版本等,具體內容如下:
<?xml version="1.0" encoding="utf-8" ?> <repositories> <!-- 一個repository表示一個倉庫,一個倉庫下可能會有多個module --> <repository> <!-- 倉庫名稱,可以隨意定義,主要用於本地快速識別 --> <name>lib模組</name> <!-- 上傳至maven時的groupid --> <group>com.vivo.space.lib</group> <!-- 設定倉庫中的所有子模組,如果多個module就新增多個module標籤 --> <modules> <module> <!-- 上傳至maven時的artifactid --> <artifactid>vivospace_lib</artifactid> <!-- 上傳至maven時的版本號 --> <version>5.9.8.0-SNAPSHOT</version> <!-- 編譯順序優先順序,越小優先順序越高 --> <priority>0</priority> </module> </modules> <!-- 注意倉庫地址中的個人ssh名稱要使用$user預留位置代替 --> <repo>ssh://$user@smartgit:xxxx/VivoCode/xxxx_lib</repo> <!-- 開發分支,指令碼用來自動切換到該分支 --> <devbranch>feature_5.9.0.0_xxx_dev</devbranch> <!-- 打release包時必須強制指定commitId,保證取到指定程式碼 --> <commitid>cbd4xxxxxx69d1</commitid> </repository> <!-- 多個倉庫就新增多個repository --> ... </repositories>
3、vsub.sh指令碼是工具各種功能的入口,比如:
./vsub.sh sync:拉取所有子模組程式碼,程式碼存放在主工程下的submodules目錄中
./vsub.sh publish:一鍵編譯所有子倉,並行布aar到maven
4、subbuild目錄用來輸出子倉的git提交記錄,subError目錄用來輸出子倉編譯異常時的log。
ModularDevTool主要功能分為兩類,一類是程式碼管理,用於批次處理git操作;第二類是專案構建,實現了動態設定子模組依賴、子模組釋出等功能。
vsub.sh指令碼中封裝了常用的git命令,用於批次處理子倉的git操作,實現邏輯相對簡單,利用shell指令碼將git命令封裝起來。
比如 ./vsub.sh -pull的實現邏輯,首先是cd進入submodules目錄(submodules目錄存放了所有子倉程式碼),然後遍歷進入子倉目錄執行git pull --rebase命令,從而實現一個命令完成對所有子倉的相同git操作,實現邏輯如下:
<!-- ./vsub.sh -pull程式碼邏輯 --> cd submodules path=$currPath files=$(ls $path) for fileName in $files do if [ ! -d $fileName ] then continue fi cd $fileName echo -e "\033[33mEntering $fileName\033[0m" git pull --rebase cd .. done
(1)Sync 功能
通過執行./vsub.sh sync命令將所有子模組的程式碼拉取到主工程的submodules目錄中。
Sync命令有3個功能:
1)如果子倉程式碼未拉取,則拉取程式碼,並切換到repositories.xml中設定的devbranch;
2)如果子倉程式碼已拉取,則切換到repositories.xml中設定的devbranch;
3)考慮到在一些場景(比如jenkins構建),使用分支檢出程式碼可能會存在異常,在sync命令後面加 -c 引數,則會使用repositories.xml中設定的commitid檢出指定分支程式碼。
Sync流程如下:
(2)子模組依賴處理
在之前我們依賴不同子倉的程式碼時,需要手動修改settings.gradle匯入子模組,然後修改build.gradle中的dependencies,如下圖。
<!-- settings.gradle --> include ':app',':module_name_1',':module_name_2',':module_name_3'... project(':module_name_1').projectDir = new File('E:/AndroidCode/module_name_1/code/') project(':module_name_2').projectDir = new File('E:/AndroidCode/module_name_2/code/') project(':module_name_3').projectDir = new File('E:/AndroidCode/module_name_3/code/') ...
<!-- build.gradle --> dependencies { api fileTree(dir: 'libs', include: ['*.jar']) // 業務子模組 begin api project (':module_name_1') api project (':module_name_2') api project (':module_name_3') // 業務子模組 end } ...
團隊中每個人程式碼的存放位置不同,在新版本拉完程式碼後都需要手動設定一番,比較繁瑣。
基於sync功能已經把所有的子倉程式碼都拉到了submodules目錄中,現在我們專案在構建時只需簡單設定local.properties即可(local.properties設定如下圖),確定哪些子模組是程式碼依賴,哪些子模組是maven依賴。
<!-- 其中key module_name_x表示子模組名,value 0表示maven依賴,1表示程式碼依賴,預設是maven依賴,也就是,如果不設定某些子模組則預設maven依賴 --> module_name_1=0 module_name_2=0 module_name_3=1 module_name_4=1 module_name_5=1 module_name_6=1
子模組依賴處理的流程如下:
(3)publish功能
通過執行./vsub.sh publish命令實現一鍵編譯所有子模組aar並上傳maven。
publish命令主要有4個功能:
1)如果子倉程式碼未拉取,則自動拉取子倉程式碼;
2)如果是釋出snapshot版本,則切換到devbranch分支最新程式碼,version中包含snapshot字串的子模組,編譯生成aar並上傳maven;否則,則直接跳過,不會編譯;
3)如果是釋出release版本(即指定-a引數),則切換到commitid對應的程式碼,編譯生成release版本的aar,並上傳maven;
4)子倉的編譯上傳順序根據設定的priority優先順序來執行。
注:上述的devbranch、version、commitid、priority等都是repositories.xml中的設定項。
publish釋出子模組的流程如下:
接入本方案的前提是專案採用多程式碼倉的方式進行模組化開發。具體接入步驟也比較簡單。
第一步,主倉依賴gradle外掛modular_dev_plugin;
(該外掛包含settings、tools、base、publish四個子外掛,其中settings、tools和base外掛配合實現子倉程式碼管理、動態依賴處理,publish外掛實現子倉的aar釋出)
第二步,主倉的settings.gradle應用settings外掛,主倉的app build.gradle中應用tools和base外掛;
第三步,主倉根目錄新增repositories.xml組態檔和vsub指令碼;
第四步,子倉依賴modular_dev_plugin,並應用publish外掛;
第五步,中間層的子倉(比如App→Shop→Lib,那Shop就是中間層子倉)對下一層子倉的依賴版本號改成預留位置,專案構建時會自動替換成repositories.xml中的版本號。如下圖:
dependencies { // 對lib倉的依賴,原來是依賴具體的版本號,現在改成「unified」預留位置,專案構建時會自動替換成repositories.xml中的版本號 api "com.vivo.space.lib:vivospace_lib:unified" }
至此,ModularDevTool就接入完成了。
基於這個工具,現在我們官網的開發流程如下:
第一步是clone主App倉程式碼,checkout對應開發分支,並在AndroidStudio開啟工程;
第二步是修改repositories.xml設定,需要進行開發的子倉,修改devbranch為對應開發分支,修改version為對應版本號;
第三步,通過./vsub.sh sync命令,檢出所有子模組程式碼;
第四步,修改local.properties中子倉依賴的模式(maven依賴or程式碼依賴),修改完成後點選Sync一下,然後就可以正常進行程式碼開發了,開發體驗與單工程多module模式完全一樣。
這個工具已經很成熟,在vivo錢包、vivo官網等專案已經使用多年,通過該工具,開發階段,實現多業務模組整合式開發,解決程式碼倉分散管理和手動設定依賴等繁瑣操作,釋出階段,實現多種編譯模式以及一鍵編包能力,對於團隊的開發效率有很大提升,支撐官網app專案3+業務線並行迭代,並且程式碼衝突降低50%以上。