在剛剛過去的雲棲大會上,手淘宣佈其移動容器化框架Atlas將於2017年年初開源,對這個框架,在過去團隊對外部作過一些分享,外界也一直對其十分關注,到如今它終於即將開源了。web
本文將介紹Atlas的設計思路和手淘對容器化、組件化和動態化上的思考,主要內容來自阿里巴巴資深技術專家倪生華(玄黎)在雲棲大會上的分享。算法
Atlas是什麼安全
2013年,手淘航母戰略的制定,帶來了業務和開發人員的翻倍膨脹。從不到100人猛增四五倍,同時業務數量大增,整個客戶端的架構和發版節奏受到極大挑戰,Atlas做爲以前手淘客戶端的基礎框架,進行了一次大的重構,造成了今天的Atlas。網絡
Atlas是一個Android客戶端容器化框架,主要提供了組件化、動態性、解耦化的支持。支持工程師在工程編碼期、Apk運行期以及後續運維修復期的各類問題。架構
在工程期,實現工程獨立開發,調試的功能,工程模塊獨立。app
在運行期,實現完整的組件生命週期的映射,類隔離等機制。框架
在運維期,提供快速增量的更新修復能力,快速升級。運維
Atlas是工程期和運行期共同起做用的框架,它的特色是儘可能將一些工做放到工程期,這樣來保證運行期的簡單,穩定。工具
目前,Atlas在淘系App的應用十分普遍,手淘自身超過60+業務組件、20個協做團隊,以及百萬行級別代碼都在Atlas上運行,其快速迭代能力讓應用的發佈週期從每個月到每週再到隨時發佈,在過去半年裏就發佈了446次。組件化
另外Atlas自己很是輕量,只有90多個類,支持大小型App開發,從大型的手淘到相對小型的阿里健康等都是用的這個框架。其穩定性也接受了考驗,兼容Android 4.x以上系統版本。總體手淘的Crash率一直維持在萬分之五左右,由於容器致使的crash佔比小於百分之一。
從這個意義上來講,Atlas首先要解決的問題是大規模團隊的協做問題,訴求包括並行開發、快速迭代、工程解耦,而後解決的問題是客戶端動態更新的問題。手淘內部思考的解決方案就是組件化。
Atlas組件化實現
組件化,業界稱爲插件化,不過這裏Atlas的組件化和如今的插件化有一些不同的地方。組件化是須要去知道組件的功能,設計更規範。
手淘APK包目錄結構手淘APK包目錄結構
這是一個手機淘寶的APK包,第一層目錄上與標準的APK是徹底同樣的,在APP會有不少的so文件,若是解開來看的話,它的結構相似於完整的APK,但自己並不能獨立運行,它跟不少插件化的差異是在運行期,它是運行在整個容器裏的,每個組件都是獨立的Bundle。
從模塊來劃分,手淘APK能夠分爲兩層,上層是通過拆分的業務Bundle,掃碼、評價、詳情,各個業務之間能夠進行功能的調用,能夠經過路由調度到其餘業務方。下層是共享的底層中間件,向業務方開放各類能力,如網絡庫、圖片庫等,會在容器裏進行統一地把控,這樣作的好處是包作到儘量小,第二是性能佳。
這一塊是Atlas的總體設計,分爲五層:
第一層咱們稱之爲Hack層,包括OS Hack toolkit & verifier,這裏咱們對系統能力作一些擴展,而後作一些安全校驗。
第二層是Bundle Franework,就是咱們的容器基礎框架,提供Bundle管理、加載、生命週期、安全等一些最基本的能力。
第三層是運行期管理層,包括清單,咱們會把全部的Bundle和它們的能力列在一個清單上,在調用時方便查找;另外是版本管理,會對全部Bundle的版本進行管理;再就是代理,這裏就是和業界一些插件化框架機制相似的地方,咱們會代理系統的運行環境,讓Bundle運行在咱們的容器框架上;而後還有調試和監控工具,是爲了方便工程期開發調試。
第四層是業務層了,這裏咱們向業務方暴露了一些接口,如框架生命週期、配置文件、工具庫等等。
最上面一層是應用接入層,就是咱們的業務代碼了。
因此Atlas做爲一個框架提供了相對完整的能力,業務層的開發能夠在框架生命週期的各個環節作一些自定義的動做,也能夠自由的調用系統、框架,乃至其它組件釋放的能力。
組件化技術細節
前面講的是容器層面的比較概要的東西,下面咱們會講一些具體的細節。
關於Bundle的生命週期會提供細粒度的節點,好比下面是一個Bundle從加載到運行的週期:
startInstall:開始加載。這個時候框架會作一些拷貝文件、釋放lib、加載Bundle的事情;
Installed:加載完畢。這時框架會注入資源路徑,建立class loader;
resolved:解析完畢,框架會檢查組件配置是否合法,是否能被解析;
active:運行組件,即開始運行組件Bundle;
started:運行成功。
組件化涉及到的第一個問題是Manifest處理,一個是由於來源不少,有宿主Manifest、Aar Manifest以及組件Manifest,另外不一樣組件的Manifest常常發生變化,要求咱們靈活地去處理。
這裏的作法是在工程期將全部的Manifest進行Merge操做,這裏須要注意的是Bundle的依賴單獨Merge,由於這裏涉及到依賴仲裁的問題。最後解析各個Bundle的Merge Manifest,獲得整包的BundleInfoList,就是上面咱們提到的Bundle信息清單。
第二個是類加載,這裏利用Delegate ClassLoader來動態加載組件的類。Delegate ClassLoader先查找宿主Bundle的PathClassLoader,而後根據前面的BundleList找到對應的BundleClassLoader.
第三個是資源,咱們會用本身的DelegeteResources替換掉系統的resource,Bundle的資源會逐個在安裝的時候添加到AssertPath,因爲添加Bundle的順序非固定,不分區會致使資源查找錯亂。
另外,Dalvik和ART上的資源查找過程順序是不同的,加上小米等系統會重寫本身的resources,因此咱們會適配不一樣的機型,日後追加AssetsPath或者往前追加,系統AssetManager是個單例,默認日後追加,若是往前追加,則須要從新建立AssetsManager對象,一樣主dex動態部署的時候要達到替換原有resource的目的,必須保證插入順序與查找順序一致。
還有須要注意的是,每次更新resourceTable的時候,必須保證apkresource,runtime的系統resource,例如webview,bundle resource都已經添加成功,並且惟一,順序正確。
不一樣Bundle的資源可能發生命名衝突,咱們是用了一種相對來講簡單的方法,將各自的Bundle分配成不一樣的ID,保證全部的業務資源不會產生衝突,儘可能將問題放到工程期解決。在不少代碼裏,經過反射來調用整個資源,在5.0以上的系統是沒有問題的,它只找第一個,對業務代碼而言,原來是怎麼寫的,今天仍是怎麼去寫。
關於組件化性能這一部分,咱們引入了按需加載,由於手淘APK有70多個Bundle,每一個用戶真正用的時候只須要5或10個,因此不須要加載全部的Bundle。Bundle之間進行隔離,經過Android四大原生組件進行交互,這樣Bundle之間能夠比較好的解耦。
咱們全部調用的入口都是基於BundleInfolist去作的,根據這個清單信息,獲得組件所在Bundle,若是須要加載,咱們就進行install、dexopt等操做。
另外,對於解決組件依賴問題,定義了兩種新的組件格式Awb(業務Bundle)和solib(so庫),前者與AAR一致,不過不添加本地lib,在構建的時候作依賴仲裁區分,後者是Native so庫的依賴。Awb其實就是AAR,只是後綴修改了,若是你的包放在宿主Bundle就用AAR,若是是組件Bundle就用Awb。
對於業務Bundle的依賴,咱們在構建期會將宿主Bundle和業務Bundle及其依賴分別打包,而後按照最短路徑、第一聲明原則進行樹狀仲裁,獲得每一個Bundle須要的依賴,在打包的時候會將依賴庫放到各自的Bundle裏去。
最後是APK構建,咱們對它作了比較大的調整。上面的圖中,其實左邊這一部分是一個標準的APK的構建過程,包括處理,編譯,到簽名的過程。
咱們這個不一樣的地方是多了Awb須要特殊處理,其中Awb的資源根據宿主的resource.ap_和包內資源構建,R文件由Bundle R資源和宿主R資源合併而來,而後咱們對Aapt進行了修改,對每一個awb分配不一樣的packageId,而後進行統一混淆,生產各個AWB的Dex,打包爲APK,簽名以後複製到libs,更名爲so文件,而後合併到taobao APK. 這就是咱們組件化的整個過程。
Atlas動態化
在一個容器框架內,組件化和動態化是相輔相成的,組件只是解決了解耦的問題,但咱們若是想要隨時發包,就必須讓容器框架具有動態化能力。咱們在完成了Atlas的組件化以後,作了動態化的支持。動態化的好處一個是包的大小縮減,咱們能夠將一些包在運行後下載到應用中,另外一個是具有動態發版和修復能力。
增量動態化方案
Atlas提供了動態部署的能力,主要目標是動態業務發佈,以及問題修復。它基於手淘自研差量算法,主Bundle基於ClassLoader機制,業務Bundle基於差量merge,支持全業務類型。
另外,Atlas也支持Andfix做爲插件使用,目標是快速故障修復,它的原理基於Native hook,主要作方法的修改,在實際中能夠兩個一塊兒用。在工程構建期適配以後,能夠作到一套代碼兩套方案通用。
自研動態部署功能實現原理,首先,對於Dex Patch的生成,咱們經過修改Dex的字節碼實現,將Dex文件轉爲Smali,對其中的ClassDef和ClassDataMethod結構體進行分析,能夠實現刪除、新增、修改類,而後經過Diff處理獲得差量文件,再經過Merge處理即生成補丁。
其次是整個資源Patch的生成,分爲兩塊,一個是業務Bundle,原本是一個不斷加載的過程,它實現起來會比較簡單,經過Md5 diff/BSDiff便可獲得。對於主Bundle,由於安卓自己有一個限制,全部的資源必須得在base包裏,新增一個資源是不生效的。因此一個作法是在打包的時候預留不少空資源。另外更新已有的資源則經過資源覆蓋來完成。
最後,若是新加業務的話,會新加Activity,咱們的作法首先在Manifest預埋一個StubActivity,而後在Instrumentation.execStartActivity()階段進行替換,同時配合Intent setFlag模擬Activity launch mode並繼續startActivity,接着System_server進程進行處理,更新ActivityStack,建立binder,並通知ActivityThread進行實例建立,最後咱們在ActivityThread的handler裏面進行攔截,更新ActivityInfo等信息,建立目標Activity。
另外在工程實踐上,由於補丁的生成會涉及到Dex和資源的基線,咱們會在部署的時候,每次發佈APK包同步發佈AP(基線包)到Maven,AP基線包裏是全部影響基線的文件,第一是安卓APK,第二是Mapping.txt,最後是Dependency.txt,這樣的話整個構建的速度會很是的快。
因此咱們這種方式,版本的升級是不一樣的方式。好比今天手淘的詳情要更新,會發布版本,這個版本可能不是到應用市場的版本,而是一個Patch包。業務版本的動態部署,咱們是同步的,5.3.0到5.3.1到5.3.2,這樣一個好處是隻要容器版本沒有升級,只要有需求,patch就能夠一直升級,並且是無感知的差量升級。
周邊優化點
最後來說講咱們的周邊優化點,爲何到今天才說要開源,作的過程中仍是遇到了很多問題。
第一點是Bundle的重複資源合併。由於咱們發現,由於宿主問題,必然而然會出現衝突的問題,包括圖片資源,咱們會放到整個宿主類目中去。
第二是Bundle的依賴校驗,之前是代碼的話,是編譯過的,但由於今天是二進制,這個問題會遺留到現場去,因此會看看API是否會影響Bundle。
第三是類庫「瘦身」,由於手淘依賴的各類中間件類庫太多了,致使手淘自己很臃腫,方法數很大;因此打包的時候對類庫有一個裁剪的過程,優化方法數。
第四是依賴致使的,依賴查詢庫。
第五是作Dex File等,進行混淆Mapping。
最後是開源準備中,咱們在工程期、運行期都會去作開源,而且將機制經過雲服務的方式提供出來,阿里百川會提供Atlas的研發支撐能力,包括快捷的生成,發佈,回滾,監控等能力。