支付寶客戶端架構解析:Android 容器化框架初探

螞蟻金服移動開發平臺mPaaS發表於2018-10-30

1. 前言

由本章節開始,我們將從支付寶客戶端的架構設計方案入手,細分拆解客戶端在“容器化框架設計”、“網路優化”、“效能啟動優化”、“自動化日誌收集”、“RPC 元件設計”、“移動應用監控、診斷、定位”等具體實現,帶領大家進一步瞭解支付寶在客戶端架構上的迭代與優化歷程。

本節將介紹支付寶 Android 容器化框架設計的基本思路。

1.1 開發背景

隨著 Android 應用程式所能實現的功能越來越強大和複雜,隨之而來的是:

  • Android 程式的的程式碼和資源越來越多,APK 檔案的 size 越來越大,Android 程式也越來越複雜;
  • 隨著應用的迭代、專案的擴張,團隊數量以及團隊人數的同時增多,基於傳統架構模式的並行開發也變得愈加困難。

此外,移動客戶端通常需要面對動態化開發的挑戰;Bug 緊急修復等運維需求;同時也有一些線上運營的需求,如動態下發廣告,推送接入活動等。如果每次有運維、運營需求,都需要一次客戶端發版,那將是傳統的開發人員的夢魘。

Android 開發者們深切體會到一個穩健可靠、可擴充套件的、支援大規模並行開發的客戶端開發框架對於平臺級別的客戶端 App 的重要性。事實上,客戶端框架設計的健壯性和擴充套件性,在面對上述需求和解決困難上,往往能達到事半功倍的效果,尤其是 Android 客戶端開發人員將深受其利。

那麼,作為平臺級別的 Android 客戶端 App 究竟該如何的進行框架設計,才能滿足千變萬化的移動網際網路時代的困難和需求?

1.2 平臺級客戶端框架面臨的問題

No. 問題 描述
1 專案工程複雜度高,開發、編譯、測試、整合都非常困難 支付寶 App 程式碼 200W 行+
2 平臺級App的內部微應用(團隊)非常多,並行開發要求高 內部多達幾十個應用
3 APK size 龐大 支付寶 App60+M,導致在某些廠商 Rom 中安裝不上
4 線上版本出現各種問題 如:發版後,UCSDK 被烏雲平臺暴露出安全漏洞
5 線上活動運營需求 春節紅包掃福活動,預案要動態推送新 so 檔案到客戶端

我們可以歸納為:平臺級客戶端框架必須要解決的是模組化動態化這兩大核心問題。

(本篇文章我們著重關注模組化相關內容,後續我們通過其他文章分析 動態化的能力。)

1.3 框架設計原則

為了解決上述模組化的問題,我們要遵循以下原則去設計客戶端框架:

  • 根據基礎技術層級、客戶端的業務線等原則,對客戶端應用程式進行模組化拆分。
  • 每一個模組由獨立的小團隊或者個人來進行開發、維護、測試、整合。
  • 模組與模組之間要做到徹底解耦,模組之間可以通過介面進行依賴。
  • 每一個模組可以進行熱插拔,單個模組的插拔不影響整體的工程的編譯執行。

2. Quinox 簡介

Quinox 客戶端框架是類 OSGi( like-as)框架的實現。Quinox 一詞來源於著名的 OSGi 框架的實現 Equinox。

基於此框架的客戶端 App,都是由一個個的積木搭建而成,這些積木被稱之為:Bundle。

bundle

3. Bundle 介紹

3.1 什麼是 Bundle

Bundle 是 OSGi 規範的模組化基本單位,與 Android 裡的 android.os.Bundle 是兩個完全不同的概念。 OSGi 裡的 Bundle 指的是 Java 應用程式的基本單位,它是一個模組單元(Jar 格式),也是上文 Quinox 簡介裡提到的積木。 基於 Quinox 容器框架開發的應用程式也是由眾多的 Bundle(APK 格式)構成。

本章節將從專案開發的三個不同的時期對 Bundle 的形態進行闡述:

時期 形態
開發期 Bundle 工程
構建期 Bundle 包
整合期 整合客戶端的 Bundle 基線

3.2 Bundle 工程

常規的 Android 專案開發,程式碼工程通常有兩種(兩級)型別

工程型別 Library Application
工程輸出 Aar Apk

基於 Quinox 容器框架開發的 Android 專案,程式碼工程則有三種(三級)型別

工程型別 Library Bundle(工程包) Application(測試包/安裝包/Final APK)
工程輸出 Aar Apk(.jar) Apk

關於 Bundle 工程,我們需要了解以下三點:

  • Bundle 工程跟常規的 Android Application 工程非常的類似:它內部也會有多個 Library(Android Module);它的輸出形式也是 APK 格式。
  • 雖然 Bundle 包檔案本質上是 APK 格式,但是該 APK 是無法執行的。同時,Bundle 工程被 deploy 到 mvn 倉庫裡時,它的字尾名是會改為.jar。
  • 基於 Quinox 容器的 Application 工程(可稱之為 Portal 工程)則是將眾多 Bundle(APK)合併成一個 APK(Final)的過程。這裡是合併,而不是編譯,所以生成最終 APK 的速度將會非常快,因為編譯已經被分散式的進行在各個 Bundle 中了。基於 Quinox 容器開發的客戶端程式,需使用 mPaaS 定製的構建工具(即打包外掛)。

關於 Bundle 工程的結構圖請參考:

bundle_project

3.3 Bundle 包

如上所述,Bundle 工程的輸出也是 APK 檔案。

在 OSGi 規範中,Bundle 是有很多屬性的。Bundle 工程輸出的 APK 與常規的 APK 有一個不同點,mPaaS 外掛會將 Bundle 的所有屬性生成一個特殊的檔案放在這個 APK 中,供容器去解讀。

除了 APK 檔案之外,Bundle 工程的構建結果還包含:

  • AndroidMannifest.xml
  • Bundle 介面包(可以理解為一個 jar 包,它包含且暴露該 Bundle 提供的介面類)
  • mapping.txt

Bundle 包檔案,在構建完成之後,通常要 deploy 到本地/遠端的 mvn 倉庫中,以供其他 Bundle 工程引用,或是被 Portal 工程整合。

3.4 Bundle 基線

前面已經講述過,構建 Final APK 其實主要就是將很多的 Budnle APK 合併成最終的 APK 的過程,而這些眾多的 Bundle APK 們都存放於 mvn 倉庫中。

因此我們將這些 Bundle 的 GAV(GroupId,ArtifactId,Vesion)的集合,稱之為基線。

當某一個團隊/個人開完一個 Bundle 工程的新功能,並經過測試達到可釋出狀態,就可以更新基線裡的版本號。我們將這個過程稱之為進基線。我們認為:基線裡打出來的 APK 是穩定可執行的;沒有穩定 Bundle 工程包不應該進基線。

Bundle 基線機制可以很好的隔離了模組之間的相互影響,保障了不同團隊間開發環境的和諧與穩定,達到了我們之前的設計的初衷,因此可以很好的支援多團隊並行開發。

3.5 Bundle 包屬性及配置辦法

關於 Bundle 屬性,我們可以參考 OSGi 的 Bundle 屬性。Quinox 容器框下定義的 Bundle 屬性要簡單的多。

下表將列舉 Bundle 的所有屬性以及配置方法:

名稱 說明 配置辦法
Bundle-Name Bundle 的名稱,作為 key 值存在。同一個客戶端 apk 中,不允許同名的 Bundle 存在 由 mPaaS 外掛根據 Bundle 工程的 GAV 的 GroupId、ArtifactId ,以一定的規則生成而來。
Bundle-Version Bundle 的版本號,各個 Bundle 的介面包必須做到 API 版本向下相容。 由 mPaaS 外掛根據 Bundle 工程的 GAV 中的 Version 生成而來
Init-Level 已廢棄 配置為 1 即可
Package-Name 已廢棄 配置為 '' 即可
Component-Name Bundle 中宣告的 Android Component。它跟 Export-Pacakges 屬性一樣,是 Bundle 的入口類。 由 mPaaS 外掛根據 AndroidManifest.xml 檔案中定義的 Activity,Service,BroadcastReceiver,ContentProvider 等生成
Package-Id Bundle 工程的資源的 packageid,具體技術細節請參考4.2章節 必須由開發同學在 Bundle 工程中設定屬性 packageId,其值的設定區間為【27, 127】,如果沒有資源,則設定為 127,如果 Bundle 為 Bundle 依賴關係樹上根節點的 Bundle,則設定為27。
Contains-Dex 此 Bundle 中是否包含程式碼(classes.dex) 由 mPaaS 外掛根據 Bundle 檔案中是否包含 classes.dex 節點判斷得來。備註:靜態連結的 Bundle 由於 classes.dex merge 到了主 apk 中,所以該屬性會被修正為 false
Contains-Res 此 Bundle 中是否包含資源(resources.arsc) 由 mPaaS 外掛根據 Bundle 檔案中是否包含 resources.arsc 檔案判斷得來。
Native-Library 此 Bundle 中是否包含 native so(lib/xxx/libxxx.so) 由 mPaaS 外掛根據 Bundle 檔案中 native so 檔案判斷得來。備註:在構建最終 apk 時,Bundle 中所有的 so 檔案在構建 Final apk 時,會 merge 到 Final apk 中,所以該屬性會被修正為 null
Required-Bundle 此 Bundle 依賴的 Bundle 列表:為 Budnle-Name@Bundle-Version 的格式。 由 mPaaS 外掛根據Bundle專案工程依賴其他Bundle介面包,來生成此屬性。
Export-Pacakges Bundle 匯出包(請參考 OSGi 的匯出包概念)。Quinox 容器將根據匯出包,從對應的 Bundle 中載入類。 必須由開發同學在 Bundle 工程中設定屬性 exportPackages。例如:某個非靜態連線的 Bundle 提供了類: com.alipay.android.phone.framework.api.A作為介面給其他 Bundle 使用,則須將com.alipay.android.phone.framework.api配置為匯出包。(反射被使用的類也應納入匯出包)。有多個匯出包的用','隔開。為了效能考慮,匯出包不應設定太多,或者範圍太廣。

4. 資源管理

4.1 資源管理器

作為 Android 開發人員,我們知道通過 android.content.res.Resources 物件可以獲取字串、佈局、圖片、動畫等資源。

在 Quinox 容器化的框架內,原生的資源管理肯定無法實現多 Bundle 的資源管理,這時候,我們就構建了 Bundle 資源管理器,來專門處理各個 Bundle 的資源的載入、呼叫等工作,替代了 Android 原生的資源管理邏輯。

但是,由於所有 Bundle 包都是獨立編譯的,它們中的資源極可能存在著相同的資源 id。因此,當存在相同資源時,就可能存在衝突,那麼如何解決資源 id 的衝突呢?

4.2 資源 id

作為 Android 程式設計師,我們都知道資源 id 是一個 int 值,它包含4個 byte。它是在構建 APK 工程時,由 aapt 工具生成,定義在 R 檔案中。

示例程式碼:

public final class R {
    public static final class drawable {
        public static int xxx_bg=0x1e020000;
    }
    public static final class id {
        public static int xxx_id=0x1e050001;
    }
    public static final class layout {
        public static int xxx_layout=0x1e030000;
    }
    public static final class string {
        public static int xxx_str=0x1e040001;
    }
}
複製程式碼

這四個 byte 含義如下:

  • 第一個位元組為:pacakgeId。
  • 第二個位元組為:typeId。它表示的是不同的資源型別,如字串,佈局,圖片,動畫等。
  • 第三第四兩個位元組合起來為:資源名稱的 id

如下圖所示:

packageid

到這裡,很多讀者應該已經理解到了,Quinox 容器框架關於資源 id 衝突的解決方案是,讓 mPaaS 打包外掛使用改造過 aapt 工具,對每一個 Bundle 工程都指定不同 packageId,進行分割槽隔離,從而確保不同的 Bundle 之間資源 id 是不會重複的。這也是為什麼 Bundle 工程裡需要指定 packageId 的緣故。

5.容器化

關於 Quinox 容器化這裡,由於目前為止,Quinox 暫未開源,所以本章節內,我們暫時不涉及到原始碼分析。

上面我們聊了很多關於 Bundle 的話題,那麼整個容器化的核心,也是如何管理各個 Bundle。這時就要引出我們的容器管理器了,容器管理器的主要工作就是協調各個 Bundle,對各種資訊進行增刪改查。

在應用啟動後,我們的容器管理器會讀取配置資訊,生成各 Bundle 的資訊例項。

5.1 容器管理器:增、刪

Quinox 容器框架的目標是解決 Android 客戶端 App 模組化和動態化這兩大核心問題。增、刪這兩項能力,更多的是用來實現動態化能力的,方便容器對各個 Bundle 進行動態新增、刪除。由於本文著重描寫模組化的能力,所以這部分,我們後續單開專題來分析容器的動態化能力。

5.2 容器管理器:改

關於容器管理器的改的能力,Quinox 主要是利用改的能力,做一些啟動效能的優化,以及機型適配上的工作,這裡涉及原始碼較多,我們不做過多分析。

5.3 容器管理器:查

容器管理器使用最頻繁的功能介面應該是查詢介面:

  • 根據 BundleName 進行查詢(還記得 Bundle 的 Require-Bundle 屬性麼?)
  • 根據 packageId 進行查詢(非27的 Bundle)
  • 根據 Android Component 類名進行查詢(還記得 Bundle 的 Component-Name 屬性麼?)

通過管理器的查詢介面,我們進行各個 Bundle 之間的協調、通訊,完成容器化的功能。

5.4 元件的啟動

除了容器管理器,還有一個重要的點就是元件的啟動器。Quinox 容器定製類原生 Android Activity 的啟動流程,從而自主管理 Activity 的建立以及生命週期。

同時,由於 Activity 是我們自主的啟動器進行的建立,我們還可以對 Activity 進行一些定製化的改造,方便其更好的適配容器這套體系。比如說給 Activity 賦予我們自定義的資源管理器,管理 Activity 堆疊並對外提供介面,對 Activity 各生命週期做一些切面工作等等。

定製化的元件啟動器,還有一個好處就是可以做到 Activity 的動態執行。所謂動態執行,是指執行出廠未註冊在 Manifest 中的 Activity。這塊功能,更多是為了支援容器動態化的能力。

由於 Activity 動態執行的實現邏輯涉及較多的核心技術點,所以我們暫時不進行具體實現的剖析。

6. 小結

通過本節內容,我們已經初步瞭解了 mPaaS 在安卓端容器化框架的設計思路和相應模組。由於篇幅限制,很多技術要點我們無法一一展開。

歡迎大家上手體驗 mPaaS。關於安卓端容器化框架的設計思路和具體實踐,同樣期待你們的反饋,歡迎一起探討交流。

往期閱讀

《開篇 | 模組化與解耦式開發在螞蟻金服 mPaaS 深度實踐探討》

《口碑 App 各 Bundle 之間的依賴分析指南》

《原始碼剖析 | 螞蟻金服 mPaaS 框架下的 RPC 呼叫歷程》

《支付寶移動端動態化方案實踐》

關注我們公眾號,獲得第一手 mPaaS 技術實踐乾貨

QRCode

相關文章