vivo官網App模塊化開發方案-ModularDevTool

語言: CN / TW / HK

作者: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。

四、ModularDevTool概覽

工具採用了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操作;第二類是項目構建,實現了動態配置子模塊依賴、子模塊發佈等功能。

5.1 代碼管理

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

5.2 項目構建

(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發佈子模塊的流程如下:

圖片

六、ModularDevTool接入

接入本方案的前提是項目採用多代碼倉的方式進行模塊化開發。具體接入步驟也比較簡單。

第一步,主倉依賴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%以上。