背景
在組件管理和發(fā)布流程中,開發(fā)工具鏈之間處于信息孤島狀態(tài)赠制,各個節(jié)點較為分散配置文件有很多,繁瑣且易出錯挟憔。正式包發(fā)布流程復雜钟些,需要和各個業(yè)務線來回確認很多次信息,溝通成本高绊谭、容易出錯政恍。AirForce在這一背景下提上日程,大目標是承載組件化相關業(yè)務达传、提高組件管理和發(fā)布的穩(wěn)定性和效率篙耗。原文鏈接https://mp.weixin.qq.com/s/6RnUP-Hv1g6dpwBosmSRQw
組件化架構
首先介紹下得物Android工程的組件化架構,以便理解下文中的內容宪赶,我們App包含的組件概念為:APP殼工程宗弯、常規(guī)業(yè)務組件層、基礎業(yè)務組件層逊朽、基礎功能組件層罕伯,其他輔助模塊和腳本。
- APP殼工程 App殼工程可理解為一個main組件叽讳,依賴了很多的子組件追他,它只有一些配置文件。沒有任何代碼邏輯岛蚤,根據(jù)需要選擇不同的業(yè)務組件邑狸,build打包組成不同的App
- 常規(guī)業(yè)務組件 常規(guī)業(yè)務組件就是不同的業(yè)務模塊組件,按照功能化劃分的業(yè)務組件涤妒,例如用戶中心模塊单雾,社區(qū)模塊, 首頁模塊等。每個業(yè)務組件都是一個小的APP硅堆,理想狀態(tài)下可以單獨編譯屿储,單獨打包成APK在手機上運行。
- 基礎業(yè)務組件 基礎業(yè)務組件是對常規(guī)業(yè)務組件對抽象和封裝渐逃,提供給所有的業(yè)務方使用够掠。例如公共組件中封裝的BaseActivity,分享組件茄菊,業(yè)務埋點組件等疯潭。可以供上層的業(yè)務組件訪問數(shù)據(jù)面殖,Manager信息共享流轉竖哩。
- 基礎功能組件 基礎功能組件都是最基礎的組件功能,不包含任何業(yè)務邏輯脊僚,可以說這些組件都是一些通用的工具類相叁。例如日志記錄組件,文件上傳組件辽幌,網(wǎng)絡服務組件钝荡,數(shù)據(jù)處理組件等;
- 其他輔助模塊和腳本 其他輔助模塊可以理解為通用模塊舶衬,即所有模塊都需要依賴的功能埠通,例如route組件,bridge組件逛犹。 輔助腳本可以定義各模塊間的依賴關系端辱,包含對組件庫版本的管理以及第三方庫的版本管理。
組件之間必須遵循以下規(guī)則:
- 只有上層的組件才能依賴下層組件虽画,不能反向依賴舞蔽,否則可能會出現(xiàn)循環(huán)依賴的情況;
- 同一層之間的組件不能相互依賴码撰,這也是為了組件之間的徹底解耦渗柿;
AirForce平臺承載組件化相關業(yè)務,主要包括基線管理脖岛、組件管理朵栖、業(yè)務線管理、發(fā)布管理這四大核心模塊柴梆。組件管理模塊是整個組件化進程中的基石陨溅;基線管理是組件化工程支持多版本并行開發(fā)的組件管理解決方案,可以認為是一組業(yè)務組件和基礎組件的集合绍在;發(fā)布管理承載APP最終出包的流程管理门扇,包括灰度包雹有、正式包。下面讓我們來一一了解這幾個核心模塊吧臼寄。
基線管理
開發(fā)中經常會出現(xiàn)多個版本并行開發(fā)的情況霸奕,為了有效的隔離工作區(qū),抽象出基線的概念吉拳,基線持有一個組件版本集合铅祸,切換基線相當于切換開發(fā)版本。根據(jù)可見范圍可以把基線分為公共和私有兩種類型合武,顧名思義公共基線是協(xié)作開發(fā)時大家一起用的版本,如果是臨時做測試可以自己fork出來私有基線以防污染公共開發(fā)區(qū)涡扼。
狀態(tài)變更
根據(jù)開發(fā)階段不同的場景稼跳,把基線劃分為三個狀態(tài)開發(fā)中、預發(fā)布吃沪、已發(fā)布
- 開發(fā)中
這個階段沒有限制汤善,開發(fā)人員可以自由的添加、刪除票彪、修改組件版本等操作
- 預發(fā)布
到這個階段红淡,不允許添加SNAPSHOT的組件,如果當前有SNAPSHOT類型組件一個小時通知一次降铸,直到清零
- 已發(fā)布
只有SNAPSHOT類型組件清零才允許流轉到此狀態(tài)在旱,到這個階段不允許修改任何組件信息
沖突檢測
開發(fā)階段組件變更單提交的較為頻繁,很容易出現(xiàn)在未感知組件列表發(fā)生變化的情況下又提交了一個組件變更單推掸,為了解決這個問題引入了traceId的機制桶蝎,每次合入一個組件變更單時使用。UUID.randomUUID()隨機生成一個字符串作為所屬基線的traceId谅畅,前端頁面每次提交組件變更單時把拉取基線組件列表時返回的基線traceId帶上來登渣,這樣就能判斷出來本次變更是否有沖突。
經過一段時間的觀察毡泻,發(fā)現(xiàn)這個機制雖然可以避免數(shù)據(jù)一致性問題胜茧,但是沖突概率很高,原因是第一次進入基線詳情頁面時才會拉取一次基線組件列表和traceId仇味,大家都習慣一直開著這個頁面等有需要時直接在當前頁面上操作呻顽,這個時候離第一次打開頁面已經過去很久了,組件列表可能已經被其它開發(fā)人員修改過了丹墨。經過和業(yè)務方的溝通其實每個開發(fā)人員經常改的組件就四五個左右芬位,基線組件列表有幾百個組件,顯然traceId方案力度太大带到,需要尋找一個更細化的比對方案昧碉。
這個時候想起了mysql的機制英染,traceId方案可以想象成數(shù)據(jù)庫的表鎖,更為細化的有行鎖被饿,模仿這個思想為每條基線組件數(shù)據(jù)加一個修改編號mod_number四康,每次這條記錄有變動時+1,前端頁面修改組件配置時把組件mod_number一并帶過來狭握,后臺服務根據(jù)傳過來的mod_number和當前組件的mod_number做對比就可以得知這個組件信息是否被修改過闪金、添加組件時只判斷這個組件不能存在、刪除組件時判斷這個組件要存在论颅,新的機制在保證穩(wěn)定的前提下大大減低了提交時的沖突概率哎垦。
基線對比
之前對比兩條基線組件變化情況使用手工對比JSON的方式,不太方便且容易遺漏恃疯,所以提供了此功能漏设。兩條基線對比結果分為新增組件、組件變更今妄、刪除組件三種類型郑口,對比的過程就是兩個列表取差集。首先把基線1的組件列表和基線2的組件列表分別轉換為以ID為key的HashMap盾鳞,然后分別把兩個HashMap的KeySet拿出來創(chuàng)建新的HashSet犬性。
- 新增組件 = KeySet2 - KeySet1
- 刪除組件 = KeySet1- KeySet2
- 組件的變更內容是兩個Set取交集,然后在做進一步比對
組件管理
發(fā)布插件執(zhí)行完成publishMavenPublicationToMavenRepository任務后腾仅,把此次發(fā)布組件相關信息上報到AirForce后端服務乒裆,上傳的信息大致如下:
- git倉庫地址
- 發(fā)布人郵箱
- Maven groupId
- Maven artifactId
- 版本號
- gradle工程文件夾名
- 插件類型
- git分支
- pom url
- 發(fā)布插件版本
- pom文件sha1
- Source jar的sha1
- 包sha1
- Git comitId
- ...... 后端拿到組件發(fā)布信息后,保存git倉庫推励、組件缸兔、組件版本信息,從gitlab同步本次發(fā)布commit信息吹艇, 最后發(fā)送飛書通知惰蜜。
發(fā)布管理
目前客戶端的發(fā)版階段,依賴發(fā)版人與社區(qū)受神、交易抛猖、直播、增長鼻听、基礎等業(yè)務線核對版本再統(tǒng)一打包财著,這一模式隨著業(yè)務線的增加出現(xiàn)了發(fā)版時間長、溝通成本高撑碴、容易出錯的問題撑教。提測階段,打包產物可用性不高醉拓,偶爾出現(xiàn)代碼錯誤導致的運行時異常伟姐,打包阻塞的問題也時有發(fā)生收苏。為了解決上述問題將發(fā)版模式改為班車發(fā)版。
由各業(yè)務線決定是否跟車愤兵,以及跟車的可發(fā)布版本鹿霸,如果此次發(fā)布不跟車平臺自動選擇業(yè)務線上一個可用版本;發(fā)版人只維護App版本號秆乳、發(fā)版日期等基本信息懦鼠。開發(fā)和測試階段,增加集成組件的卡口檢查屹堰,避免代碼問題影響持續(xù)集成的穩(wěn)定性肛冶,后期還將完善測試、審核流程扯键。
整體流程
業(yè)務線發(fā)布
業(yè)務線Owner從候選版本中選擇一個作為發(fā)布版本, 平臺會做必要檢查. 如果不發(fā)布, 默認使用之前發(fā)布版本.
發(fā)版人打包
發(fā)版人預先創(chuàng)建發(fā)布計劃睦袖,明確App版本信息,基線和計劃發(fā)版日期忧陪,平臺通知業(yè)務線Owner。打包構建的時候平臺自動收集業(yè)務組件和三方組件近范,業(yè)務組件的版本來自業(yè)務線Owner的發(fā)布歷史嘶摊,三方組件來自基線。
打包流程
在沒有AirForce平臺之前评矩,打包都是在jenkins上打包的叶堆,業(yè)務線的Owner都要手動在jenkins上選擇要打包的業(yè)務線的組件,而每個打包的產物和上傳的產物也是不能動態(tài)配置的斥杜,打正式包和打測試包也沒有區(qū)分開虱颗,每個打包的產物也要去oss后臺上去看,可以說對于開發(fā)人員來說無疑增加了開發(fā)的時間蔗喂,和對于文檔需求的成本忘渔。
因此AirForce平臺在把打包這任務構建這塊變得更為的簡潔,開發(fā)人員甚至不用關心oss后臺和jenkins缰儿,直接在AirForce平臺上操作就可以了畦粮。
開發(fā)時的構建流程
在編譯插件中,實現(xiàn)了通過參數(shù)配置的下發(fā)來判斷是AirForce發(fā)布還是本地打包乖阵,從而實現(xiàn)無縫的可切換的過程宣赔,在此之外,在AirForce平臺中瞪浸,我們在開發(fā)時基于每個基線構建的時候也有動態(tài)的配置classpath的組件和動態(tài)的配置implementation的組件儒将,從而實現(xiàn)完全的動態(tài)可配的特性。
發(fā)布計劃構建流程
客戶端發(fā)布計劃对蒲,主要兼容了AirForce發(fā)布和本地打包钩蚊,原本的Baseline的參數(shù)不變贡翘,在AirForce發(fā)布計劃的情況下新增了 AirForce.Baseline,所以我們在build.gradle加載Classpath前統(tǒng)一做了攔截,保持了其他插件讀取Baseline參數(shù)的統(tǒng)一两疚。
而在構建詳情中床估,原本我們從讀取本地配置的渠道包,上傳固定的產物到oss后臺诱渤,而現(xiàn)在丐巫,我們把所有的邏輯統(tǒng)一收斂到了AirForce的平臺,包括了動態(tài)的獲取上傳的產物,動態(tài)的配置渠道包的配置勺美,從而讓AirForce串起了整個流程递胧,包括了獲取和配置app的組件,獲取和配置app的渠道包赡茸,獲取和配置app上傳的產物等缎脾,大大減少了各個業(yè)務線的發(fā)版和配置難度。
打包時卡口檢測流程
由于得物app采用的是殼工程+aar的形式占卧,故修改或者重構底層的模塊就顯得要異常的小心遗菠,尤其是重構底層的模塊。 舉個例子华蜒,如果重構du_common 那就需要把其關聯(lián)修改的類從aar的依賴變成源碼依賴的模式辙纬,此舉會導致編譯過慢,而且如果此module被上層依賴的module過多叭喜,就很有可能會導致開發(fā)人員漏了修改某一個上層的aar贺拣,從而導致此重構的底層module發(fā)上去,有可能造成運行期的異常捂蕴。 解決方案: 因此譬涡,為了解決此痛點,我們在打整包時執(zhí)行了檢測的task啥辨,通過在每個aar中生成每個info信息涡匀,然后調用compare.jar,此jar包能讓下列aar中的異常情況都能被檢測到,從而避免運行期異常溉知,可以檢測的異常情況如下:
- 某一個模塊的里的接口或者抽象類里的方法名字變更了或者是參數(shù)有變更渊跋,調用此task能檢測出被影響的組件列表。
- 某一個模塊里的類重命名或者被刪除了,調用此task能檢測出被影響的組件着倾。
- 某一個模塊里的方法名字或者方法參數(shù)發(fā)生變化了拾酝,調用此task能檢測出被影響的組件。 實現(xiàn)圖如下:
可以看到這里主要是獲取被檢測的業(yè)務組件的信息: 自己的類信息(shelfClassInfo)
- 包括了類的簽名
- 類的訪問權限
- 方法的訪問權限
- 其父類信息
- 父類所在的module名字
- 接口信息
- 接口所在的module名字
- 方法的描述符 繼承類的信息(inheritClassInfo)
- 類名
- 類的訪問權限 實現(xiàn)接口的信息(inheritClassInfo)
- 類名
- 類的訪問權限 調用其他module的類的信息(libsJarInfo)
- 方法名字
- 調用者的方法
- 方法描述符
- 調用者的類 然后把這些信息寫到一個json文件中卡者,通過封裝的compare的jar包做統(tǒng)一的調用蒿囤,從而分析出哪些類調用其他組件出現(xiàn)方法名缺失或者方法參數(shù)發(fā)生改變,要重寫的方法未重寫等,最后把比對的結果上傳上去崇决,然后去做統(tǒng)一的通知 所以我們編譯插件中材诽,也添加了關于整包的runtime的crash的預檢測的task,從而在包打出來之前提前發(fā)現(xiàn)此包會出現(xiàn)的runtime的異常底挫,從而提前通知開發(fā)人員僻孝,從而減少運行時異常給開發(fā)人員帶來的block,當然璧坟,此開關也是在AirForce平臺上可配置的柳沙。
總結
AirForce管理平臺暖侨,串起了從開發(fā)到最終投放過程中組件管理和打包的主要流程。逐步收斂組件管理和打包相關的各個分散的點威根,把之前需要手工操作的點程序化徘六、自動化锄贼,規(guī)范開發(fā)流程外遇,降低手工操作出錯概率注簿,以及管理成本,通過關鍵節(jié)點的信息入庫跳仿,做到行為可溯源诡渴,為提高研發(fā)效率助力。