你知道支付寶容器化架構是怎麼搭建的嗎?

NO IMAGE

1、前言

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

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

1.1 開發背景

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

  • Android 程序的的代碼和資源越來越多,APK 文件的 size 越來越大,Android 程序也越來越複雜;

  • 隨著應用的迭代、項目的擴張,團隊數量以及團隊人數的同時增多,基於傳統架構模式的並行開發也變得愈加困難。

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

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

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

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

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

我們可以歸納為:平臺級客戶端框架必須要解決的是模塊化動態化這兩大核心問題。 (本篇文章我們著重關注模塊化相關內容,後續我們通過其他文章分析動態化的能力。)

1.3 框架設計原則

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

  • 根據基礎技術層級、客戶端的業務線等原則,對客戶端應用程序進行模塊化拆分。

  • 每一個模塊由獨立的小團隊或者個人來進行開發、維護、測試、集成。

  • 模塊與模塊之間要做到徹底解耦,模塊之間可以通過接口進行依賴。

  • 每一個模塊可以進行熱插拔,單個模塊的插拔不影響整體的工程的編譯運行。

2、Quinox 簡介

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

基於此框架的客戶端 App,都是由一個個的積木搭建而成,這些積木被稱之為: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 項目開發,代碼工程通常有兩種(兩級)類型

工程類型LibraryApplication
工程輸出AarApk

基於 Quinox 容器框架開發的 Android 項目,代碼工程則有三種(三級)類型

工程類型Library

Bundle

工程包

Application

(測試包/安裝包/Final APK)

工程輸出AarApk(.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 工程的結構圖請參考:你知道支付寶容器化架構是怎麼搭建的嗎?

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-NameBundle 的名稱,作為 key 值存在。同一個客戶端 apk 中,不允許同名的 Bundle 存在由 mPaaS 插件根據 Bundle 工程的 GAV 的 GroupId、ArtifactId ,以一定的規則生成而來。
Bundle-VersionBundle 的版本號,各個 Bundle 的接口包必須做到 API 版本向下兼容。由 mPaaS 插件根據 Bundle 工程的 GAV 中的 Version 生成而來
Init-Level已廢棄配置為 1 即可
Package-Name已廢棄配置為 ” 即可
Component-NameBundle 中聲明的 Android Component。它跟 Export-Pacakges 屬性一樣,是 Bundle 的入口類。由 mPaaS 插件根據 AndroidManifest.xml 文件中定義的 Activity,Service,BroadcastReceiver,ContentProvider 等生成
Package-IdBundle 工程的資源的 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 列表:

[email protected] 的格式。

由 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

如下圖所示:

你知道支付寶容器化架構是怎麼搭建的嗎?

到這裡,很多讀者應該已經理解到了,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、總結

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

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

相關文章

想做架構師?你真的明白Android事件分發機制嗎?

帶你走進Android之基礎篇Bitmap加載

Android架構組件讓天下沒有難做的App

帶你走進Android之基礎篇文件解壓縮