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

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

1. 前言

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

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

1.1 開發背景

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

  • 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 文件到客戶端

咱們能夠概括爲:平臺級客戶端框架必需要解決的是模塊化和 動態化這兩大核心問題。app

(本篇文章咱們着重關注模塊化相關內容,後續咱們經過其餘文章分析 動態化的能力。)框架

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 工程的結構圖請參考:

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

以下圖所示:

到這裏,不少讀者應該已經理解到了,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 在安卓端容器化框架的設計思路和相應模塊。因爲篇幅限制,不少技術要點咱們沒法一一展開。



本文做者:josephjin

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索