你知道途牛 Android 客戶端架構是怎么優化的嗎?

xrjk5290 8年前發布 | 10K 次閱讀 安卓開發 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

 

 本文由用戶 xrjk5290 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!