你知道途牛 Android 客戶端架構是怎么優化的嗎?
途牛APP的故事
成長
途牛APP經歷了誕生、發展、升級和優化的階段,系統越來越穩定,功能越來越豐富,技術架構越來越完善。
團隊
團隊的規模從最開始的2個人擴張到100人左右,APP的功能也是越來越豐富。
行業表現
當前途牛app的行業表現如下:
產品演進
產品界面原型從 2013年(左圖)的簡單,到2016年(右圖)的復雜。
架構演化
想象一下:“在你的面前有一堆代碼,耦合很嚴重,堆在一個倉庫里,質量很難控制,然后團隊規模迅速增大,功能越來越多,代碼堆砌速度很快。”
是不是有種快要失控的感覺?
架構的背景
最早無線中心包含自助、門票、跟團、酒店等業務,隨著公司規模增大,按照垂直業務劃分了機票、酒店、交通等事業部。同時也帶來了一個問題, 什么樣的架構才能比較好的支持不同團隊的協同開發?
機遇和挑戰
隨著團隊人數增多,團隊的拆分,給途牛APP架構帶來了一些機遇和挑戰。
我們的 機遇 包括:
第一,各個業務線可獨立開發,從提高開發的效率;
第二,可總結沉淀一些公共組件,為不同的業務團隊提供服務;
第三,搭建業務公共平臺,例如打點、性能監控。
我們的 挑戰 包括:
第一,項目之間的業務存在橫向和縱向的耦合,業務之間存在相互調用,業務和底層library也存在相互調用,成網狀的調用方式;
第二,項目中存在部分重復的代碼,例如,不同的業務相同的功能,城市選擇,存在兩份代碼;
第三,項目未沉淀一些平臺化的產品,需要抽象獨立出來,以SDK的形勢提供給各個產品或業務線使用。
解決問題
解決問題的思路是拆分,重構項目。 那如何拆分是比較好的拆分方式呢?
我們可以把 獨立性強的業務抽離出來 ,從而獨立開發、優化以及維護,公共的部分可由架構團隊統一管理和維護。
抽象、隔離、解耦各個業務線,以什么方式進行?
我們采取了 插件方式 把各條業務線獨立開來。
什么是插件?
插件是一個apk或者dex文件,無需安裝就可運行,插件好處有動態加載,增量更新,業務隔離。
插件擁有 兩種 思路:
一種是靜態代理的方式,通過主app的組件代理插件中的組件;
另外一種是360公司研發的動態hook的方式,主要原理是hook系統的API從而實現插件的運行。
經過團隊的調研和分析,我們的項目比較適合 靜態代理的插件 思路。
下面簡單演示一下靜態代理的方式:
主APP中有個ProxyActivity的模塊,這個Activity具有基本的生命周期,插件的PluginActivity不具有生命周期的概念, 通過ProxyActivity中插件PluginActivity的引用來實現 ProxyActivity的生命周期對插件的PluginActivity的代理。
插件要解決的幾個重要問題:
一個是資源加載的問題,另外一個是dex代碼加載的問題。
通過分析系統加載apk的源碼,發現 資源加載的核心方法 是addAssetPath,由于此方法沒有公開,通過反射的機制可以獲取方法并且調用,解決插件資源加載的問題。
AssetManager instance = AssetManager.class.newInstance();
Method addAssetPathMethod=
AssetManager.class.getDeclaredMethod(
" addAssetPath ", String.class);
addAssetPathMethod.invoke(instance, apkPath);
由于插件代碼都打在dex文件中,代碼的加載可以通過系統提供的DexClassLoader來完成。如下所示的核心代碼, 解決插件的calss加載問題 。
DexClassLoader pluginClassLoader = new DexClassLoader(dexPath, optimizedDirectory,libraryPath, parentClassLoader);
選定插件的靜態代理的基本設計思想后,可實現 插件的底層框架 。
途牛app的插件框架包括:
調度引擎,bridge模塊,性能監控,日志系統,增量更新和安全校驗。
調用引擎 負責插件的拷貝,加載策略等核心問題;
Bridge 負責插件之間、以及插件和主app之間的溝通;
性能監控 主要是監控插件的加載和運行時的加載時間、頁面耗時、接口耗時等性能數據;
日志統計 負責紀錄插件系統內的用戶行為,以便產品策略的優化;
增量更新 負責插件的動態加載更新,包括下載、合并等環節;
安全校驗模塊 負責插件 的安全,主要職責是插件的本地校驗。
插件系統的編譯要采取provided的形式。
由于插件是動態加載到主app中的,對于library的引用,采取provided方式,即不用把library代碼打進插件包中。
插件的采取分級調度策略,分為1,2,3優先級。
優先級為1 的插件在應用啟動時,就開始加載;
優先級2 的插件在首頁 渲染完成后,開始加載;
優先級3的插件,在首頁加載完成后,延遲加載。
插件的加載要收口,所有的插件加載 都是異步的方式,加載成功或失敗通過回調來通知調用者。
想知道我們在實踐中遇到問題是怎么解決的嗎?
來自:http://mp.weixin.qq.com/s/CfPlVKElv2SshAbfzHfRhg