218
技術社區[雲棲]
大規模團隊協同開發利器:阿裏Atlas正式開源!
近日,手機淘寶安卓客戶端容器化框架Atlas正式宣布開源。Atlas由阿裏巴巴移動團隊自研,以容器化思路解決大規模團隊協作問題,實現並行開發、快速迭代和動態部署,適用於Android 4.x以上係統版本的大小型App開發。
Atlas特別適用於大規模團隊的協同開發。通過提供組件化、動態性、解耦化的支持,Atlas能夠實現每個業務在開發階段獨立編譯、獨立調試、獨立運行,最後再以一個組件的形式集成到客戶端中,每個業務之間並行開發互不影響。此外,還具備客戶端動態發版和快速修複的能力。
開源地址:https://github.com/alibaba/atlas
Atlas是古希臘神話中的天神,是波士頓動力公司的機器人,借助搜索引擎,得以發現這個名詞背後許許多多的含義。在手機淘寶,Atlas是一個紮根於Android客戶端的一個組件化容器框架,相比神話中用手和頭支撐起蒼天的泰坦神族,Atlas在手淘默默無聞地承載著手淘上豐富業務的運行,伴隨著數不清的功能在用戶手中經曆新老交替。
在阿裏,在手機淘寶,移動客戶端的迭代更新可以說是整個移動互聯網從新起到繁榮的見證。伴隨著越來越多業務的誕生,越來越多的代碼開始往小小的客戶端湧入,而無論是iOS還是Android,各種客戶端的體積也經曆著快速的增長。而由於客戶端的推廣成本較高,各個業務線都有很強的需求接入手機淘寶客戶端。所以在當時航母戰略的背景下,迫切需要一種技術能把客戶端化整為零,這些模塊可以自由組合,並且當部分功能變更時隻需要更新對應模塊。
我們在2012年底的時候就開始研究Android上的插件化技術,並與2013年初把Atlas作為插件化框架接入手機淘寶。基於插件化的能力,一個普通的Android應用可以低成本地轉為符合Atlas規範的插件,使Apk既可以以插件方式運行也可以獨立安裝運行。一個大的Android客戶端項目可以分割成數個插件,做到代碼隔離,降低了開發、維護、部署的成本。
MacDown Screenshot
當時插件化的方案可分為三類:Web、WebApp、Native。各種方案在手淘內都在有同學進行深入的研究。 Native形式的插件由於技術門檻較高,當時業界采用的並不多見。但是由於其比WebApp有良好的性能和體驗,同時對於許多已經成型的小客戶端推廣的迫切需求,尤其值得我們去探索。
MacDown Screenshot
上圖是當時插件化的技術原理的簡圖,其實跟現在業界的插件化方案主要的設計思路基本一致。Atlas作為一個模擬的Android運行環境,每個插件以一個獨立的進程運行在各自的沙箱環境中。同時由於接入成本較低,其他的業務應用基本可以算是零成本的方式接入到手淘環境,同時也可以隨時發布新的版本進行更新。
插件化的方案有其特有的優勢,獨立的進程保證了業務的絕對隔離,同時也便於隔離風險。聚劃算、天貓、彩票一個個獨立應用開始起降於手淘這架航母。 甜蜜的成長總是短暫,插件化發展很快,隨著手淘all in方案的深入,一些隱患開始展現。
- 性能:新進程的開辟極大影響每個業務的進入速度,同時由於手淘的插件並不是用完即走的場景,比如說從首頁->聚劃算->詳情->店鋪->下單各個環節是存在著一定的關聯,各個插件的進程無法退出。而多進程的機製又極大的占用了內存,同時也引起許多用戶的質疑
- 複用:插件的獨立限製了許多中間件的複用,aidl的方式並不適合中間件能力的輸出,獨立的apk直接進入的同時也攜帶了大量重複的二方庫,也一方麵極大增大的應用的體積
- 穩定:插件化需要一個模擬的Android運行環境,在Android多版本以及國產rom的兼容中需要做大量的工作,同時多插件運行過程中在低端設備上很容易遇到進程被回收或者三方應用強殺的情況,沒有良好的恢複機製會極大降低用戶的體驗。
伴隨著all in的進行,在手淘內部發起了對大型app進行重構的計劃。我們需要這樣一個框架:
1. 支持大量豐富業務的接入,同時業務之間能夠保持清晰的邊界,各自可以繼續靈活迭代;
2. 用一批統一的中間件去支撐起各種業務的底層功能,保持中間件代碼的全麵複用;
3. 能夠盡量保持對係統的低侵入,尊重原生運行機製以降低後期的維護成本;
4. 在用戶設備上盡量體現一個簡單客戶端的特性,同時特定的業務功能按需獲取,保持體積的可控;
↑新容器化結構設想↑
基於對插件化框架對Android運行機製的理解,參考了OSGI在服務端框架,在開發IDE等領域"高複用、低耦合、可插拔"的優勢,我們借鑒了OSGI規範開發了基於組件化的Atlas容器化框架。
• 最底層的tookit verifier全麵羅列了上層需要反射使用的注入和代理的Api,會在應用啟動時先進性全局性的校驗,以避免程序運行中遇到不兼容的情況;
• 往上Bundle Framework負責組件的安裝 更新 操作以及管理所有組件的生命周期,這裏組件的邊界隔離就遵循了OSGI的規範,每個組件分配獨立的classloader,同時組件有各自的資源,每個資源在構建期間由AAPT分配獨立的package ID;
• Runtime層 主要包括清單管理、版本管理、以及係統代理三大塊:
- 版本:每個組件在構建期間就由構建插件分配自己的版本號,同時安裝期間也有各自的版本目錄,每個bundle的啟動加載都需要經過版本的校驗,組件發生更新同時也下發最新的版本信息。依托版本管理機製組件的熱更新能力水到渠成。
- 清單:OSGI規範中每個組件通常通過OSGI.MF來暴露自身的component,這是與Atlas容器所不同的地方。在Android設備上,多文件的形式很容易受IO異常的影響幹擾bundle正常運行,所以我們采用了構建期間集中生成清單的方式,清單裏麵記錄bundle所有的component(Android四大組件),依賴、packagename等內容。
- 代理:各個係統關鍵點的注入使得bundle可以做到按需加載,避免了像原生multidex方案由於首次啟動時多dex同步安裝而造成UI卡頓的情況,代理層的核心DelegateClassLoader負責類的查找和路由,DelegateResource管理所有bundle的資源,它們在容器啟動時進行注入,並在運行過程中隨著bundle的不斷載入進行更新。 • 接入層 簡單是美-複雜的東西留給自己。為了方便,Atlas容器有自身獨立的Application負責啟動,同時在構建期間會由插件替換應用原有的Application。運行期間應用首先由AtlasBridgeApplication負責啟動,並在容器啟動完畢後全權代理給應用真正的Application;同時對需要自定義和由外部決策的功能,容器開放接口由接入方簡單設置。
依托classloader,組件的獨立性有了充分的保證,同時由於BundleClassloader保持了對PatchClassLoader的引用,使得宿主的中間件就如同普通APk開發一樣可以被組件簡單調用;同時組件之間除了基於AIDL的服務調用,組件之間提供了靜態依賴和運行時依賴,相互依賴的組件可以通過配置打成,這在一定程度上使某些偏功能的代碼或者UI的重用成為可能,同時配置的方式使組件之間的關係可以清晰追溯。
1.異步按需:由於每個組件是一個小型Apk的結構,每個組件安裝使其涉及到文件的拷貝、native lib的解壓以及校驗載入等過程。特別是service等後台component觸發組件安裝和前台Activity引發組件安裝並行時UI的流暢很受影響。為了降低對UI的影響,每個組件的安裝都在一個統一的異步安裝線程中進行。Activity、service、receiver等的發起都被進行了異步的處理。
2.解釋執行:組件的代碼在安裝後還需要dexopt才能使用,這一過程在art上的時間消耗尤為明顯,為了降低用戶等待時間,Atlas框架對dalivk係統上首次使用bundle時關閉了verify,在art係統上首次使用時關閉了dex2oat走解釋執行,保障bundle首次進入盡可能地高效,同時後台通過異步任務走原生的dexopt過程,為下次使用做好準備。
1.遠程組件:插件化時代的按需下載並沒有被廢棄,獨立的遠程組件可以滿足用戶的個性化需求。基於用戶的操作和清單機製遠程組件在用戶需要時隻需要簡單地等待或者授權就可以從遠端拉倒本地。同時當用戶設備因為空間緊張時容器也可以清理掉一些長期不用的組件以釋放擁擠的空間
2.動態部署: 動態部署是容器一個最重要的功能。基於此,業務可以靈活發布自己的需求;有故障的業務可以及時修複或者回滾;同時動態部署的快速覆蓋能力在灰度等場景下可以更快地收到所需的效果。動態部署的範圍也通過改造進行擴大,從最初的組件部署到現在支持除Atlas容器小部分核心代碼外的所有代碼,部署patch包的體積和性能的改進也在不斷的進行改進。
每次大版本的發版,集成平台會保留該次發版的AP(Ap中記錄所有參與構建的二方庫的版本,組件的依賴,混淆的mapping等各種信息),在手淘接下來每周一次的動態部署迭代中,動態部署的構建會與之前的dex進行字節碼級別的diff,生成tpatch包,最終下發到用戶手機的patch僅包含變化class組成的dex和更改或者新增的資源文件 #### Bundle的Merge patch收到後,客戶端的merge過程會根據patch信息,取到source 組件,source dex和patch dex進行dex合並,合成新的dex,同時變化的資源覆蓋老的資源最終形成新的組件。
動態部署能力的增強少不了對底層中間件的支持,部署的機製也與bundle截然不同。
1.中間件代碼部署
Dalvik和Art在merge過程中都會形成新patch zip,zip中dex以多dex的方式命名,patch dex為classes.dex,原始apk中的classes.dex以此根據序號+1放入zip中,不同的是dalvik由於preVerify的限製寫入zip中的源dex會先經過處理,剔除在patchdex中已有的class,確保被patch的dex無法被打上preVerify的標簽,安裝過程中則多個dex依次dexopt,同時插入到dexpatch的第一位。而art由於無preVerify校驗,但是Android到art後原有的dexopt會改為dex2oat,運行時代碼由原來的解釋執行改為直接運行native代碼,之前的使用過程中發現單純得往前麵追加patch的dex並不能完全解決動態部署的問題,dex2oat的過程優化了class的執行代碼,比如說內斂,虛函數的校驗等。就有可能在運行過程中直接在native層執行老的優化過的class代碼而不是從新patch的dex中load新的class去使用。所以在art上類似dalivk,放入源dex,隻是源dex不會做任何處理,然後合並在一起做一遍dex2oat,這樣能使新的class覆蓋老的class參與dex2oat並完成優化的過程。
2. 中間件資源部署
資源部署基於overlay機製達成,不過dalivk上這個機製並不支持新增資源,在overlay的包裏麵如果讀到了一個資源,dalvik係統會去校驗該資源ID在base中值,如果不存在則拋錯,所以動態部署為了利用這一特性同時支持新增資源的需求,在打基線包的時候就在每個不同的type裏麵預留了128個資源ID供後續動態部署使用;同時打動態部署的patch時會以之前的ID分配的內容作為輸入,保證已有的資源分配到的ID保持不變,同時如果資源沒有發生變更,則剔除該資源,所以aapt dump resource patch的時候我們看到的INVALID TYPE 的資源段就表示沒有發生變更的資源,如果有發生了變更,且之前有這個資源,就會在原有的資源ID分配過去,同時新的資源文件打入patch包或者新的資源文本寫入arsc,如果是新增的資源,則使用預留的資源段進行分配。整個替換過程如下圖所示:
MacDown Screenshot
Atlas動態部署的能力隨著手淘的需求不斷進行著演進,隨著整個框架的開源,自然需求會變得更加廣泛。比如在手淘內部一周一發版的節奏下,動態部署對新增component的某些需求並不強烈,而開源後此類功能可能會尤為重要,所以接下來容器的更新迭代中此類的功能也會及時上線。擁抱開源社區會是Atlas重新審視自己,繼續發展的關鍵一步。
相比於iOS,Android的推陳出新的速度是極快的。Google在Android係統的改進深入到方方麵麵,組件化的發展也需要緊跟Android步伐。
Atlas起步的時候multidex還未出來,解決方法數的問題就能帶來足夠的愉悅。現在Atlas基於組件化減少主dex方法數的同時,也支持主dex原生multidex的能力。目前Atlas內部集成了multidex的能力,外部隻需要打開multidex開關即可。這麼做的目的一方麵避免外部初始化實際的誤解,另一方麵我們也意識到原生的mulitdex在性能上還有很多優化的點,我們會在後麵及時進行優化。
在開發調試方麵,Atlas容器框架基於動態部署的能力支持單個組件的獨立編譯調試,但是相比instantrun的速度,目前還有很大提高。而基於instant run改造的單組件秒級編譯也已經在緊鑼密鼓的開發中,後續基於框架的獨立調試插件也將會在Android studio插件庫中上線。
Android係統機製的演進也鞭策著動態部署前行。比如16年Android 7.0的混合編譯當時也給Atlas造成了不小的挑戰,服務於億級的uv,動態部署的能力的覆蓋麵、到達率都還有進一步提升的空間,相比andfix等熱部署修複方案,動態部署需要重啟來打到更新的能力可能也需要有新的轉變。
市麵上許許多多的容器框架,基本思路就大致兩三種。Atlas本身也沒有特別新的技術,有的是基於已有的知識進行不斷的重組和優化。在手淘內部,追求更高的穩定性和性能,億級的UV需要及其穩定的容器來承載,同時Atlas也不斷嚐試新的方案,在創新與實用之間不斷權衡。Atlas是一個結構簡明的容器框架,通過盡量簡單的思路去解決移動應用從開發到運維中的的桎梏,讓開發中的複雜簡單化,讓維護中的複雜簡單化,讓開發從流水線上忙碌的工作回到專注於對最終目標的精耕細作,拋開嘈雜的幹擾,撥開虛無縹緲,到達雲和山的彼端!
最後更新:2017-06-21 14:01:47