在剛剛過(guò)去的云棲大會(huì)上,手淘宣布其移動(dòng)容器化框架Atlas將于2017年年初開(kāi)源,對(duì)這個(gè)框架,在過(guò)去團(tuán)隊(duì)對(duì)外部做過(guò)一些分享,外界也一直對(duì)其十分關(guān)注,到現(xiàn)在它終于即將開(kāi)源了。
本文將介紹Atlas的設(shè)計(jì)思路和手淘對(duì)容器化、組件化和動(dòng)態(tài)化上的思考,主要內(nèi)容來(lái)自阿里巴巴資深技術(shù)專(zhuān)家倪生華(玄黎)在云棲大會(huì)上的分享。
Atlas是什么
2013年,手淘航母戰(zhàn)略的制定,帶來(lái)了業(yè)務(wù)和開(kāi)發(fā)人員的翻倍膨脹。從不到100人猛增四五倍,同時(shí)業(yè)務(wù)數(shù)量大增,整個(gè)客戶(hù)端的架構(gòu)和發(fā)版節(jié)奏受到極大挑戰(zhàn),Atlas作為之前手淘客戶(hù)端的基礎(chǔ)框架,進(jìn)行了一次大的重構(gòu),形成了今天的Atlas。
Atlas是一個(gè)Android客戶(hù)端容器化框架,主要提供了組件化、動(dòng)態(tài)性、解耦化的支持。支持工程師在工程編碼期、Apk運(yùn)行期以及后續(xù)運(yùn)維修復(fù)期的各種問(wèn)題。
在工程期,實(shí)現(xiàn)工程獨(dú)立開(kāi)發(fā),調(diào)試的功能,工程模塊獨(dú)立。在運(yùn)行期,實(shí)現(xiàn)完整的組件生命周期的映射,類(lèi)隔離等機(jī)制。在運(yùn)維期,提供快速增量的更新修復(fù)能力,快速升級(jí)。Atlas是工程期和運(yùn)行期共同起作用的框架,它的特點(diǎn)是盡量將一些工作放到工程期,這樣來(lái)保證運(yùn)行期的簡(jiǎn)單,穩(wěn)定。
目前,Atlas在淘系A(chǔ)pp的應(yīng)用十分廣泛,手淘自身超過(guò)60+業(yè)務(wù)組件、20個(gè)協(xié)作團(tuán)隊(duì),以及百萬(wàn)行級(jí)別代碼都在Atlas上運(yùn)行,其快速迭代能力讓?xiě)?yīng)用的發(fā)布周期從每月到每周再到隨時(shí)發(fā)布,在過(guò)去半年里就發(fā)布了446次。另外Atlas本身非常輕量,只有90多個(gè)類(lèi),支持大小型App開(kāi)發(fā),從大型的手淘到相對(duì)小型的阿里健康等都是用的這個(gè)框架。其穩(wěn)定性也接受了考驗(yàn),兼容Android 4.x以上系統(tǒng)版本。整體手淘的Crash率一直維持在萬(wàn)分之五左右,因?yàn)槿萜鲗?dǎo)致的crash占比小于百分之一。
從這個(gè)意義上來(lái)說(shuō),Atlas首先要解決的問(wèn)題是大規(guī)模團(tuán)隊(duì)的協(xié)作問(wèn)題,訴求包括并行開(kāi)發(fā)、快速迭代、工程解耦,然后解決的問(wèn)題是客戶(hù)端動(dòng)態(tài)更新的問(wèn)題。手淘?xún)?nèi)部思考的解決方案就是組件化。
Atlas組件化實(shí)現(xiàn)組件化,業(yè)界稱(chēng)為插件化,不過(guò)這里Atlas的組件化和現(xiàn)在的插件化有一些不一樣的地方。組件化是需要去知道組件的功能,設(shè)計(jì)更規(guī)范。
(手淘APK包目錄結(jié)構(gòu))
這是一個(gè)手機(jī)淘寶的APK包,第一層目錄上與標(biāo)準(zhǔn)的APK是完全一樣的,在APP會(huì)有很多的so文件,如果解開(kāi)來(lái)看的話(huà),它的結(jié)構(gòu)類(lèi)似于完整的APK,但本身并不能獨(dú)立運(yùn)行,它跟很多插件化的差別是在運(yùn)行期,它是運(yùn)行在整個(gè)容器里的,每一個(gè)組件都是獨(dú)立的Bundle。
從模塊來(lái)劃分,手淘APK可以分為兩層,上層是經(jīng)過(guò)拆分的業(yè)務(wù)Bundle,掃碼、評(píng)價(jià)、詳情,各個(gè)業(yè)務(wù)之間可以進(jìn)行功能的調(diào)用,可以通過(guò)路由調(diào)度到其他業(yè)務(wù)方。下層是共享的底層中間件,向業(yè)務(wù)方開(kāi)放各種能力,如網(wǎng)絡(luò)庫(kù)、圖片庫(kù)等,會(huì)在容器里進(jìn)行統(tǒng)一地把控,這樣做的好處是包做到盡可能小,第二是性能佳。
這一塊是Atlas的整體設(shè)計(jì),分為五層:
第一層我們稱(chēng)之為Hack層,包括OS Hack toolkit & verifier,這里我們對(duì)系統(tǒng)能力做一些擴(kuò)展,然后做一些安全校驗(yàn)。
第二層是Bundle Franework,就是我們的容器基礎(chǔ)框架,提供Bundle管理、加載、生命周期、安全等一些最基本的能力。
第三層是運(yùn)行期管理層,包括清單,我們會(huì)把所有的Bundle和它們的能力列在一個(gè)清單上,在調(diào)用時(shí)方便查找;另外是版本管理,會(huì)對(duì)所有Bundle的版本進(jìn)行管理;再就是代理,這里就是和業(yè)界一些插件化框架機(jī)制類(lèi)似的地方,我們會(huì)代理系統(tǒng)的運(yùn)行環(huán)境,讓Bundle運(yùn)行在我們的容器框架上;然后還有調(diào)試和監(jiān)控工具,是為了方便工程期開(kāi)發(fā)調(diào)試。
第四層是業(yè)務(wù)層了,這里我們向業(yè)務(wù)方暴露了一些接口,如框架生命周期、配置文件、工具庫(kù)等等。
最上面一層是應(yīng)用接入層,就是我們的業(yè)務(wù)代碼了。
所以Atlas作為一個(gè)框架提供了相對(duì)完整的能力,業(yè)務(wù)層的開(kāi)發(fā)可以在框架生命周期的各個(gè)環(huán)節(jié)做一些自定義的動(dòng)作,也可以自由的調(diào)用系統(tǒng)、框架,乃至其它組件釋放的能力。
組件化技術(shù)細(xì)節(jié)
前面講的是容器層面的比較概要的東西,下面我們會(huì)講一些具體的細(xì)節(jié)。
關(guān)于Bundle的生命周期會(huì)提供細(xì)粒度的節(jié)點(diǎn),比如下面是一個(gè)Bundle從加載到運(yùn)行的周期:
startInstall:開(kāi)始加載。這個(gè)時(shí)候框架會(huì)做一些拷貝文件、釋放lib、加載Bundle的事情;Installed:加載完畢。這時(shí)框架會(huì)注入資源路徑,創(chuàng)建class loader;resolved:解析完畢,框架會(huì)檢查組件配置是否合法,是否能被解析;active:運(yùn)行組件,即開(kāi)始運(yùn)行組件Bundle;started:運(yùn)行成功。組件化涉及到的第一個(gè)問(wèn)題是Manifest處理,一個(gè)是因?yàn)閬?lái)源很多,有宿主Manifest、Aar Manifest以及組件Manifest,另外不同組件的Manifest經(jīng)常發(fā)生變化,要求我們靈活地去處理。這里的做法是在工程期將所有的Manifest進(jìn)行Merge操作,這里需要注意的是Bundle的依賴(lài)單獨(dú)Merge,因?yàn)檫@里涉及到依賴(lài)仲裁的問(wèn)題。最后解析各個(gè)Bundle的Merge Manifest,得到整包的BundleInfoList,就是上面我們提到的Bundle信息清單。
第二個(gè)是類(lèi)加載,這里利用Delegate ClassLoader來(lái)動(dòng)態(tài)加載組件的類(lèi)。Delegate ClassLoader先查找宿主Bundle的PathClassLoader,然后根據(jù)前面的BundleList找到對(duì)應(yīng)的BundleClassLoader.
第三個(gè)是資源,我們會(huì)用自己的DelegeteResources替換掉系統(tǒng)的resource,Bundle的資源會(huì)逐個(gè)在安裝的時(shí)候添加到AssertPath,由于添加Bundle的順序非固定,不分區(qū)會(huì)導(dǎo)致資源查找錯(cuò)亂。
另外,Dalvik和ART上的資源查找過(guò)程順序是不一樣的,加上小米等系統(tǒng)會(huì)重寫(xiě)自己的resources,所以我們會(huì)適配不同的機(jī)型,往后追加AssetsPath或者往前追加,系統(tǒng)AssetManager是個(gè)單例,默認(rèn)往后追加,如果往前追加,則需要重新創(chuàng)建AssetsManager對(duì)象,同樣主dex動(dòng)態(tài)部署的時(shí)候要達(dá)到替換原有resource的目的,必須保證插入順序與查找順序一致。
還有需要注意的是,每次更新resourceTable的時(shí)候,必須保證apkresource,runtime的系統(tǒng)resource,例如webview,bundle resource都已經(jīng)添加成功,而且唯一,順序正確。
不同Bundle的資源可能發(fā)生命名沖突,我們是用了一種相對(duì)來(lái)說(shuō)簡(jiǎn)單的方法,將各自的Bundle分配成不同的ID,保證所有的業(yè)務(wù)資源不會(huì)產(chǎn)生沖突,盡量將問(wèn)題放到工程期解決。在很多代碼里,通過(guò)反射來(lái)調(diào)用整個(gè)資源,在5.0以上的系統(tǒng)是沒(méi)有問(wèn)題的,它只找第一個(gè),對(duì)業(yè)務(wù)代碼而言,原來(lái)是怎么寫(xiě)的,今天還是怎么去寫(xiě)。
關(guān)于組件化性能這一部分,我們引入了按需加載,因?yàn)槭痔訟PK有70多個(gè)Bundle,每個(gè)用戶(hù)真正用的時(shí)候只需要5或10個(gè),所以不需要加載所有的Bundle。Bundle之間進(jìn)行隔離,通過(guò)Android四大原生組件進(jìn)行交互,這樣Bundle之間可以比較好的解耦。我們所有調(diào)用的入口都是基于BundleInfolist去做的,根據(jù)這個(gè)清單信息,得到組件所在Bundle,如果需要加載,我們就進(jìn)行install、dexopt等操作。
另外,對(duì)于解決組件依賴(lài)問(wèn)題,定義了兩種新的組件格式Awb(業(yè)務(wù)Bundle)和solib(so庫(kù)),前者與AAR一致,不過(guò)不添加本地lib,在構(gòu)建的時(shí)候做依賴(lài)仲裁區(qū)分,后者是Native so庫(kù)的依賴(lài)。Awb其實(shí)就是AAR,只是后綴修改了,如果你的包放在宿主Bundle就用AAR,如果是組件Bundle就用Awb。
對(duì)于業(yè)務(wù)Bundle的依賴(lài),我們?cè)跇?gòu)建期會(huì)將宿主Bundle和業(yè)務(wù)Bundle及其依賴(lài)分別打包,然后按照最短路徑、第一聲明原則進(jìn)行樹(shù)狀仲裁,得到每個(gè)Bundle需要的依賴(lài),在打包的時(shí)候會(huì)將依賴(lài)庫(kù)放到各自的Bundle里去。
最后是APK構(gòu)建,我們對(duì)它做了比較大的調(diào)整。上面的圖中,其實(shí)左邊這一部分是一個(gè)標(biāo)準(zhǔn)的APK的構(gòu)建過(guò)程,包括處理,編譯,到簽名的過(guò)程。我們這個(gè)不同的地方是多了Awb需要特殊處理,其中Awb的資源根據(jù)宿主的resource.ap_和包內(nèi)資源構(gòu)建,R文件由Bundle R資源和宿主R資源合并而來(lái),然后我們對(duì)Aapt進(jìn)行了修改,對(duì)每個(gè)awb分配不同的packageId,然后進(jìn)行統(tǒng)一混淆,生產(chǎn)各個(gè)AWB的Dex,打包為APK,簽名之后復(fù)制到libs,改名為so文件,然后合并到taobao APK. 這就是我們組件化的整個(gè)過(guò)程。
Atlas動(dòng)態(tài)化
在一個(gè)容器框架內(nèi),組件化和動(dòng)態(tài)化是相輔相成的,組件只是解決了解耦的問(wèn)題,但我們?nèi)绻胍S時(shí)發(fā)包,就必須讓容器框架具備動(dòng)態(tài)化能力。我們?cè)谕瓿闪薃tlas的組件化之后,做了動(dòng)態(tài)化的支持。動(dòng)態(tài)化的好處一個(gè)是包的大小縮減,我們可以將一些包在運(yùn)行后下載到應(yīng)用中,另一個(gè)是具備動(dòng)態(tài)發(fā)版和修復(fù)能力。
增量動(dòng)態(tài)化方案
Atlas提供了動(dòng)態(tài)部署的能力,主要目標(biāo)是動(dòng)態(tài)業(yè)務(wù)發(fā)布,以及問(wèn)題修復(fù)。它基于手淘自研差量算法,主Bundle基于ClassLoader機(jī)制,業(yè)務(wù)Bundle基于差量merge,支持全業(yè)務(wù)類(lèi)型。
另外,Atlas也支持Andfix作為插件使用,目標(biāo)是快速故障修復(fù),它的原理基于Native hook,主要做方法的修改,在實(shí)際中可以?xún)蓚€(gè)一起用。在工程構(gòu)建期適配之后,可以做到一套代碼兩套方案通用。
自研動(dòng)態(tài)部署功能實(shí)現(xiàn)原理,首先,對(duì)于Dex Patch的生成,我們通過(guò)修改Dex的字節(jié)碼實(shí)現(xiàn),將Dex文件轉(zhuǎn)為Smali,對(duì)其中的ClassDef和ClassDataMethod結(jié)構(gòu)體進(jìn)行分析,可以實(shí)現(xiàn)刪除、新增、修改類(lèi),然后通過(guò)Diff處理得到差量文件,再通過(guò)Merge處理即生成補(bǔ)丁。
其次是整個(gè)資源Patch的生成,分為兩塊,一個(gè)是業(yè)務(wù)Bundle,本來(lái)是一個(gè)不斷加載的過(guò)程,它實(shí)現(xiàn)起來(lái)會(huì)比較簡(jiǎn)單,通過(guò)Md5 diff/BSDiff即可得到。對(duì)于主Bundle,因?yàn)榘沧勘旧碛幸粋€(gè)限制,所有的資源必須得在base包里,新增一個(gè)資源是不生效的。所以一個(gè)做法是在打包的時(shí)候預(yù)留很多空資源。另外更新已有的資源則通過(guò)資源覆蓋來(lái)完成。
最后,如果新加業(yè)務(wù)的話(huà),會(huì)新加Activity,我們的做法首先在Manifest預(yù)埋一個(gè)StubActivity,然后在Instrumentation.execStartActivity()階段進(jìn)行替換,同時(shí)配合Intent setFlag模擬Activity launch mode并繼續(xù)startActivity,接著System_server進(jìn)程進(jìn)行處理,更新ActivityStack,創(chuàng)建binder,并通知ActivityThread進(jìn)行實(shí)例創(chuàng)建,最后我們?cè)贏ctivityThread的handler里面進(jìn)行攔截,更新ActivityInfo等信息,創(chuàng)建目標(biāo)Activity。
另外在工程實(shí)踐上,因?yàn)檠a(bǔ)丁的生成會(huì)涉及到Dex和資源的基線(xiàn),我們會(huì)在部署的時(shí)候,每次發(fā)布APK包同步發(fā)布AP(基線(xiàn)包)到Maven,AP基線(xiàn)包里是所有影響基線(xiàn)的文件,第一是安卓APK,第二是Mapping.txt,最后是Dependency.txt,這樣的話(huà)整個(gè)構(gòu)建的速度會(huì)非常的快。
所以我們這種方式,版本的升級(jí)是不同的方式。比如今天手淘的詳情要更新,會(huì)發(fā)布版本,這個(gè)版本可能不是到應(yīng)用市場(chǎng)的版本,而是一個(gè)Patch包。業(yè)務(wù)版本的動(dòng)態(tài)部署,我們是同步的,5.3.0到5.3.1到5.3.2,這樣一個(gè)好處是只要容器版本沒(méi)有升級(jí),只要有需求,patch就可以一直升級(jí),而且是無(wú)感知的差量升級(jí)。
周邊優(yōu)化點(diǎn)
最后來(lái)講講我們的周邊優(yōu)化點(diǎn),為什么到今天才說(shuō)要開(kāi)源,做的過(guò)程當(dāng)中還是遇到了不少問(wèn)題。
第一點(diǎn)是Bundle的重復(fù)資源合并。因?yàn)槲覀儼l(fā)現(xiàn),因?yàn)樗拗鲉?wèn)題,必然而然會(huì)出現(xiàn)沖突的問(wèn)題,包括圖片資源,我們會(huì)放到整個(gè)宿主類(lèi)目中去。
第二是Bundle的依賴(lài)校驗(yàn),以前是代碼的話(huà),是編譯過(guò)的,但因?yàn)榻裉焓嵌M(jìn)制,這個(gè)問(wèn)題會(huì)遺留到現(xiàn)場(chǎng)去,所以會(huì)看看API是否會(huì)影響B(tài)undle。
第三是類(lèi)庫(kù)“瘦身”,因?yàn)槭痔砸蕾?lài)的各種中間件類(lèi)庫(kù)太多了,導(dǎo)致手淘本身很臃腫,方法數(shù)很大;所以打包的時(shí)候?qū)︻?lèi)庫(kù)有一個(gè)裁剪的過(guò)程,優(yōu)化方法數(shù)。
第四是依賴(lài)導(dǎo)致的,依賴(lài)查詢(xún)庫(kù)。
第五是做Dex File等,進(jìn)行混淆Mapping。
最后是開(kāi)源準(zhǔn)備中,我們?cè)诠こ唐凇⑦\(yùn)行期都會(huì)去做開(kāi)源,并且將機(jī)制通過(guò)云服務(wù)的方式提供出來(lái),阿里百川會(huì)提供Atlas的研發(fā)支撐能力,包括快捷的生成,發(fā)布,回滾,監(jiān)控等能力。