在剛剛過去的雲栖大會上,手淘宣布其移動容器化架構atlas将于2017年年初開源,對這個架構,在過去團隊對外部做過一些分享,外界也一直對其十分關注,到現在它終于即将開源了。
本文将介紹atlas的設計思路和手淘對容器化、元件化和動态化上的思考,主要内容來自阿裡巴巴資深技術專家倪生華(玄黎)在雲栖大會上的分享。
atlas是什麼
2013年,手淘航母戰略的制定,帶來了業務和開發人員的翻倍膨脹。從不到100人猛增四五倍,同時業務數量大增,整個用戶端的架構和發版節奏受到極大挑戰,atlas作為之前手淘用戶端的基礎架構,進行了一次大的重構,形成了今天的atlas。
atlas是一個android用戶端容器化架構,主要提供了元件化、動态性、解耦化的支援。支援工程師在工程編碼期、apk運作期以及後續運維修複期的各種問題。
在工程期,實作工程獨立開發,調試的功能,工程子產品獨立。
在運作期,實作完整的元件生命周期的映射,類隔離等機制。
在運維期,提供快速增量的更新修複能力,快速更新。
atlas是工程期和運作期共同起作用的架構,它的特點是盡量将一些工作放到工程期,這樣來保證運作期的簡單,穩定。
目前,atlas在淘系app的應用十分廣泛,手淘自身超過60+業務元件、20個協作團隊,以及百萬行級别代碼都在atlas上運作,其快速疊代能力讓應用的釋出周期從每月到每周再到随時釋出,在過去半年裡就釋出了446次。
另外atlas本身非常輕量,隻有90多個類,支援大小型app開發,從大型的手淘到相對小型的阿裡健康等都是用的這個架構。其穩定性也接受了考驗,相容android 4.x以上系統版本。整體手淘的crash率一直維持在萬分之五左右,因為容器導緻的crash占比小于百分之一。
從這個意義上來說,atlas首先要解決的問題是大規模團隊的協作問題,訴求包括并行開發、快速疊代、工程解耦,然後解決的問題是用戶端動态更新的問題。手淘内部思考的解決方案就是元件化。
atlas元件化實作
元件化,業界稱為插件化,不過這裡atlas的元件化和現在的插件化有一些不一樣的地方。元件化是需要去知道元件的功能,設計更規範。
(手淘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的研發支撐能力,包括快捷的生成,釋出,復原,監控等能力。