美團外賣Android平臺化的複用實踐

美團外賣平臺化複用主要是指多端代碼複用,正如美團外賣iOS多端複用的推進、支撐與思考文章所述,多端包含有兩層意思:其一是相同業務的多入口,指美團外賣業務須要在美團外賣App(下文簡稱外賣App)和美團App外賣頻道(下文簡稱外賣頻道)同時上線;其二是指平臺上各個業務線,美團外賣不一樣業務線都依賴外賣基礎服務,好比登錄、定位等。html

多入口及多業務線給美團外賣平臺化複用帶來了巨大的挑戰,此前咱們的一篇博客《美團外賣Android平臺化架構演進實踐》(下文簡稱《架構演進實踐》)也提到了這個問題,本文將在「代碼複用」這一章節的基礎上,進一步介紹平臺化複用工做面臨的挑戰以及相應的解決方案。android

美團外賣平臺化複用背景

美團外賣App和美團App外賣頻道業務基本同樣,但因爲歷史緣由,兩端代碼差別較大,形成一樣的子業務需求在一端上線後,另外一端幾乎須要從新實現,嚴重浪費開發資源。在《架構演進實踐》一文中,將美團外賣Android客戶端平臺化架構分爲平臺層、業務層和宿主層,咱們但願可以在平臺化架構中實現平臺層和業務層的多端複用,從而節省子業務需求開發資源,實現多端部署。微信

難點總結

兩端業務雖然基本一致,可是仍舊存在差別,UI、基礎服務、需求差別等。這些差別存在於美團外賣平臺化架構中的平臺層和業務層各個模塊中,給平臺化複用帶來了巨大的挑戰。咱們總結了兩端代碼的差別點,主要包括如下幾個方面:網絡

  1. 基礎服務的差別:包括基礎Activity、網絡庫、圖片庫等底層庫的差別。
  2. 組件的實現差別:包括基礎數據Model、下拉刷新、頁面跳轉等基礎組件的差別。
  3. 頁面的差別:包括兩端的UI、交互、業務和版本發佈時間不一致等差別。

前期探索

前期,咱們嘗試經過一些設計方案來繞過上述差別,從而實現兩端的代碼複用。咱們選擇了二級頻道頁(下文統稱金剛頁)進行方案嘗試,設計以下:架構

其中,KingKongDelegate是Activity生命週期實現的代理類,包含onCreate、onResume等Activity生命週期回調方法。在外賣App和外賣頻道兩端分別基於各自的基礎Activity實現WMKingKongAcitivity和MTKingKongActivity,分別會經過調用KingKongDelegate的方法對Activity的生命週期進行分發。框架

KingKongInjector是兩端差別部分的接口集合,包括頁面跳轉(兩端頁面差別)、獲取頁面刷新間隔時間、默認資源等,在外賣App和外賣頻道分別有對應的接口實現WMKingKongInjector和MTKingKongInjector。運維

NetworkController則是用Retrofit實現統一的網絡請求封裝,PageListController是對列表分頁加載邏輯以及頁面空白、網絡加載失敗等異常邏輯處理。模塊化

在金剛頁設計方案中,咱們採用了「代理+繼承」的方式,實現了用統一的網絡庫實現網絡請求,定義了統一的基礎數據Model,統一了部分基礎服務以及基礎數據。經過KingKongDelegate屏蔽了兩端基礎Acitivity的差別,同時,經過KingKongInjector實現了兩端差別部分的處理。可是咱們發現這種設計方案存在如下問題:工具

  1. 雖然這樣能夠解決網絡庫和圖片的差別,可是不能屏蔽兩端基礎Activity的差別。
  2. KingKongInjector提供了一種解決兩端差別的處理方式,可是KingKongInjector會存在不少不相關的方法集合,不易控制其邊界。此外,多個子模塊須要調用KingKongInjector,會致使KingKongInjector不便管理。
  3. 因爲兩端Model不一樣,須要實現這個模塊使用的統一Model,可是並未和其餘頁面使用的相同含義的Model統一。

平臺化複用方案設計

經過代碼複用初步嘗試總結,咱們總結出平臺化複用,須要考慮四件事情:組件化

  1. 差別化的統一管理。
  2. 基礎服務的複用。
  3. 基礎組件的複用。
  4. 頁面的複用。

總體設計

咱們在實現平臺化架構的基礎上,通過不斷的探索,最終造成適合外賣業務的平臺化複用設計:總體分爲基礎服務層-基礎組件層-業務層-宿主層。設計圖以下:

  1. 基礎服務層:包含多端統一的基礎服務和有差別的基礎服務,其中統一的基礎服務包括網絡庫、圖片庫、統計、監控等。對於登陸、分享、定位等外賣App和外賣頻道兩端有差別的部分,咱們經過抽象服務層來屏蔽兩端的差別。
  2. 基礎組件層:包括統一的兩端Model、埋點、下拉刷新、權限、Toast、A/B測試、Utils等兩端複用的基礎組件。
  3. 業務層:包括外賣的具體業務模塊,目前能夠分爲列表頁模塊(如首頁、金剛頁等)、商家模塊(如商家頁、商品詳情頁等)和訂單模塊(以下單頁、訂單狀態頁等)。這些業務模塊的特色是:模塊間複用可能性小,模塊內的複用可能性大。
  4. 宿主層:主要是初始化服務,例如Application的初始化、dex加載和其餘各類必要的組件的初始化。

分層架構可以實現各層功能的職責分離,同時,咱們要求上層不感知下層的多端差別。在各層中進行組件劃分,一樣,咱們也要求實現調用組件方不感知組件的多端差別。經過這樣的設計,可以使得總體架構更加清晰明朗,複用率提升的同時,不影響架構的複雜度和靈活度。

差別化管理

須要多端複用的業務相對於普通業務而言,最大的挑戰在於差別化管理。首先多端的先天條件就決定了多端複用業務會存在差別;其次,多端複用的業務有個性化的需求。在多端複用的差別化管理方案中,咱們總結了如下兩種方案:

  1. 差別分支管理方案。
  2. pins工程+Flavor管理的方案。
差別分支管理

分支管理經常使用於多個需求在一端上線後,須要在另外一端某一個時間節點跟進的場景,以下圖所示:

兩端開發1.0版本時,分別要在wm分支(外賣App對應分支)開發feature1和mt分支(外賣頻道對應分支)開發feature2。開發2.0版本時,feature1須要在外賣頻道上線,feature2須要在外賣App上線,則分別將feature1分支代碼合入mt分支,feature2代碼合入wm分支。這樣經過拉取新需求分支管理的方式,知足了需求的差別化管理。可是這種實現方式存在兩個問題:

  1. 兩端需求差別太多的話,就會存在不少分支,形成分支管理困難。
  2. 不支持細粒度的差別化管理,好比模塊內部的差別化管理。
pins工程+Flavor的差別化管理

在Android官網《配置構建變體》章節中介紹了Product Flavor(下文簡稱Flavor)能夠用於實現full版本以及demo版本的差別化管理,經過配置Gradle,能夠基於不一樣的Flavor生成不一樣的apk版本。所以,模塊內部的差別化管理是經過Flavor來實現,其原理以下圖所示:

其中Common是兩端複用的代碼,DiffHandler是兩端差別部分接口,WMDiffHandler是外賣App對應的Flavor下的DiffHandler實現,MTDiffHandler是外賣頻道對應Flavor下的DiffHandler實現。經過兩端分別依賴不一樣Flavor代碼實現模塊內差別化管理。

對於需求在兩端版本差別化管理,也能夠經過配置Flavor來實現,以下圖所示:

在1.0版本時,feature1只在外賣App上線,feature2只在外賣頻道上線。當2.0版本時,若是feature一、feature2須要同時在兩端上線,只須要將對應業務代碼移動到共用SourceSet便可實現feature一、feature2代碼複用。

綜合兩種差別代碼實現來看,咱們選擇使用Flavor方式來實現代碼差別化管理。其優點以下:

  1. 一個功能模塊只須要維護一套代碼。
  2. 差別代碼在業務庫不一樣Flavor中實現,方便追溯代碼實現歷史以及作差別實現對比。
  3. 對於上層來講,只會依賴下層代碼的不一樣Flavor版本;下層對上層暴露接口也基本同樣,上層不用關心下層差別實現。
  4. 需求版本差別,也只需先在上線一端對應的Flavor中實現,當須要複用時移動到共用的SourceSet下面,就能實現需求代碼複用。

從Android工程結構來看,使用Flavor只能在module內複用,可是以module爲粒度的複用對於差別化管理來講約束過重。這意味着同個module內不一樣模塊的差別代碼同時存在於對應Flavor目錄下,或者說須要將每一個子模塊都建立成不一樣的module,這樣管理代碼是很是不便的。《微信Android模塊化架構重構實踐》一文中提到了一個重要的概念pins工程,pins工程能在module以內再次構建完整的多子工程結構。咱們經過創造性的使用pins工程+Flavor的方案,將差別化的管理單元從module降到了pins工程。而pins工程能夠定義到最小的業務單元,例如一個Java文件。總體的設計實現以下:

具體的配置過程,首先須要在Android Studio工程裏首先要定義兩個Flavor:wm、mt。

productFlavors {
     wm {}
     mt {}
}

而後使用pins工程結構,把每一個子業務做爲一個pins工程,實現以下Gradle配置:

最終的工程目錄結構以下:

以名爲base的pins工程爲例,src/base/main是該工程的兩端共用代碼,src/base/wm是該工程的外賣App使用的代碼,src/base/mt是外賣頻道使用的代碼。同時,咱們作了代碼檢查,除了base pins工程能夠依賴之外,其餘pins不存在直接依賴關係。經過這樣實現了module內部更細粒度的工程依賴,同時配合Gradle配置能夠實現只編譯部分pins工程,使總體代碼更加靈活。

經過pins工程+Flavor的差別化管理方式,咱們既實現了需求級別的差別化管理,也實現了模塊內的功能差別化管理。同時,pins工程更好的控制了代碼粒度以及代碼邊界,也將差別代碼控制在比module更小的粒度。

基礎服務的複用

對於一個App來講,基礎服務的重要性不言而喻,因此在平臺化複用中,每每基礎服務的差別最大。因爲基礎服務的使用範圍比較廣,若是基礎服務的差別得不到有效的處理,讓上層感知到差別,就會增長架構層與層之間的耦合,上層自己實現業務的難度也會加大。下文裏講解一個咱們在實踐過程當中遇到的例子,來闡述咱們的主要解決思路。

在前期探索章節中,咱們提到金剛頁因爲兩端基礎Activity差別,以至於要使用代理類來實現Activity生命週期分發。經過採用統一接口以及Flavor方式,咱們能夠統一兩端基礎Activity組件,以下圖所示:

分別將兩端WMBaseActivity和MTBaseActivity的差別接口統一成DialogController、ToastController以及ActionBarController等通用接口,而後在wm、mt兩個Flavor目錄下分別定義全限定名徹底相同的BaseActivity,分別繼承MTBaseActivity和MTBaseActivity並實現統一接口,接口實現儘可能保持一致。對於上層來講,若是繼承BaseActivity,其可調用的接口徹底一致,從而達到屏蔽兩端基礎Activity差別的目的。

對於一些通用基礎組件,因爲使用範圍比較廣,若是不統一或者差別較大,會形成業務層代碼實現差別較大,不利於代碼複用。因此咱們採用的策略是外賣App向外賣頻道看齊。代碼複用前,外賣App主要使用的網絡庫是Volley,統一切換爲外賣頻道使用的MTRetrofit;外賣使用的圖片庫是Fresco,統一切換爲外賣頻道使用的MTPicasso;其餘統一的組件還包括動態加載框架、WebView加載組件、網絡監控Cat、線上監控Holmes、日誌回撈Logan以及降級限流等。兩端代碼複用時,修復問題、監控數據能力方面保持統一。

對於登陸、定位等通用基礎服務,咱們的原則是能統一儘可能統一,這樣能夠有效的減小多端複用中來帶的多端維護成本,多份變成一份。而對於沒法統一的服務,抽象出統一的服務接口,讓上層不感知差別,從而減小上層的複用成本。

組件複用

組件化能夠大大的提升一個App的複用率。對於平臺化複用的業務而言,也是同樣。多個模塊之間也是會常用相同的功能,例以下拉刷新、分頁加載、埋點、樣式等功能。將這些經常使用的功能抽離成組件供上層業務層調用,將能夠大大提升複用效果。能夠說組件化是平臺化複用的必要條件之一。

面對外賣App包含複雜衆多的業務功能,一個功能能夠被拆分紅組件的基本原則是不一樣業務庫中不一樣業務的共用的業務功能或行爲功能。而後按照業務實現中相關性的遠近,自上而下的依賴性將抽離出來的組件劃分爲基礎通用組件、基礎業務組件、UI公共組件。

基礎通用組件指那些變化不大,與業務無關的組件,例如頁面加載下拉刷新組件(p_refresh),日誌記錄相關組件(p_log),異常兜底組件(p_exception)。基礎業務組件指以業務爲基礎的組件:評論通用組件(p_ugc),埋點組件(p_judas),搜索通用組件(p_search),紅包通用組件(p_coupon)等。UI公共組件指公用View或者UI樣式組件,與View 相關的通用組件(p_widget),與UI樣式相關的通用組件(p_theme)。

對於抽離出來的基礎組件,多端之間的差別怎麼處理呢? 例如兜底組件,外賣兜底樣式以黃色爲主調,而外賣頻道中以綠色小團爲主調,如圖所示:

咱們首先將這個組件劃分爲一個pins工程,對於多端的差別,在pins工程裏面利用Flavor管理多端之間的差別。這樣的方案,首先組件是一個獨立的模塊,其次多端的差別在組件內部被統一處理了,上層業務不用感知組件的實現差別。而因爲基礎服務層已經將差別化管理了,組件層也不用感知基礎服務的差別,減小了組件層的複用成本。

頁面複用

對兩端同一個頁面來講,絕大部分的功能模塊是可複用的,可是也存在不一致的功能模塊。之外賣App和美團外賣頻道首頁爲例,中部流量區等業務基本相同,可是頂部導航欄樣式功能和中部流量區佈局在兩端不同,以下圖所示:

針對上述問題,咱們頁面複用的實現思路是頁面模塊化:先將頁面功能按照業務類似性以及兩端差別拆分紅高內聚低耦合的功能單元Block,而後兩端頁面使用拆分的功能單元Block像搭積木似的搭建頁面,單個的單元Block能夠採用MVP模式實現。美團點評內部酒旅的Ripper和到店綜合Shield頁面模塊化開發框架也是採用這樣的思路。因爲咱們要實現兩端複用,還要考慮頁面之間的差別。對於兩端頁面差別,咱們統一使用上文中提到的Flavor機制在業務單元內對兩端差別化管理,業務單元所在頁面不感知業務單元的差別性。對於不一樣的差別,單元Block能夠在MVP不一樣層作差別化管理。

以首頁爲例,首頁Block化複用架構以下圖。兩端首頁頭部導航欄UI展現、數據、功能不同,導航欄整個功能就以一個Flavor在兩端分別實現;商家列表中部流量區部分雖然總體UI佈局不同,可是裏面單個功能Block業務邏輯、整個數據同樣,繼續將中部流量區裏面的業務Block化;下方的商家列表項兩端同樣的功能,用一個公有的Block實現。在各個單元Block已經實現的基礎上,兩端首頁搭建成首頁Fragment。

頁面模塊化後,將兩端不一樣的差別在各個單元Block以Flavor方式處理,業務單元Block所在頁面不用關心各個Block實現差別,不只實現了頁面的複用,各個模塊功能職責分離,還提升了可維護性。

總結和展望

美團外賣業務須要在外賣平臺和美團平臺同時部署,所以,在美團外賣平臺化架構過程當中就產生了平臺化複用的問題。而怎麼去實現平臺化複用呢?筆者認爲須要從不一樣粒度去考慮:基礎服務、組件、頁面。對於基礎服務,咱們須要儘量的統一,不能統一的就抽象服務層。組件級別,須要分塊分層,將依賴梳理好。頁面的複用,最重要的是頁面模塊化和頁面內模塊作到職責分離。平臺化複用最大的難點在於:差別的管理和屏蔽。本文提出使用pins工程+Flavor的方案,可使得差別代碼的管理獲得有效的解決。同時利用分層策略,每層都本身處理好本身的差別,使得上層不用關心下層的差別。平臺化複用不能單純的追求複用率,同時要考慮到端的個性化。

到目前爲止,咱們實現了絕大部分外賣App和外賣頻道代碼複用,總體代碼複用率達到88.35%,人效提高70%以上。將來,咱們可能會在外賣平臺、美團平臺、大衆點評平臺三個平臺進行代碼複用,其場景將會更加複雜。固然,咱們在作平臺化複用的時候,要合理地進行評估,複用帶來的「成本節約」和爲了複用帶來的「成本增長」之間的比率。另外,平臺化複用視角不該該侷限於業務頁面的複用,對於監控、測試、研發工具、運維工具等也能夠進行復用,這也是平臺化複用理念的核心價值所在。

參考資料

  1. 美團外賣Android平臺化架構演進實踐
  2. 美團外賣iOS多端複用的推進、支撐與思考
  3. 微信Android模塊化架構重構實踐
  4. 配置構建變體
  5. Shield—開源的移動端頁面模塊化開發框架

做者簡介

曉飛,美團點評技術專家。2015年加入美團點評,外賣Android的早期開發者之一。目前是外賣Android App負責人,主要負責版本管理和業務架構。

金光,美團點評高級工程師。2017年加入美團點評,主要負責代碼複用及外賣平臺化相關工做。

王芳,美團點評高級工程師。2017年加入美團點評,主要負責商家列表頁面等相關頁面業務。

招聘

美團外賣長期招聘Android、iOS、FE 高級/資深工程師和技術專家,Base 北京、上海、成都,歡迎有興趣的同窗投遞簡歷到wukai05@meituan.com。

相關文章
相關標籤/搜索