Android Virtualview:淘寶、天貓 又一個動態化、高性能的UI框架力做


前言

  • 淘寶、天貓一直致力於解決 頁面動態化的問題
  • 在2017年的4月發佈了v1.0解決方案:Tangram模型 及其對應的 Androidvlayout,該解決方案在手機淘寶、天貓 Android版 內普遍使用

電商圖

若還不瞭解Tangram模型 和 vlayout,具體請看文章前端

  1. Android Tangram模型:連淘寶、天貓都在用的UI框架模型你必定要懂
  2. Android開源庫V - Layout:淘寶、天貓都在用的UI框架,趕忙用起來吧!
  • 在同年的12月,阿里團隊對此做了重大更新:發佈了Tangram2.0版本,主要是補充了AndroidVirtualView,也普遍應用於淘寶、天貓客戶端 git

    示意圖

  • 今天,我將帶你們全面瞭解Tangram 2.0版本的新成員:Virtualviewgithub

Virtualview的Github地址json


目錄

示意圖


1. 爲何要向 Tangram模型 加入 VirtualView

即 爲何要更新 Tangram2.0版本數組

  • 結論
  1. 提高組件動態性,實現動態更新
  2. 提高了組件的渲染性能
  • 具體描述
    示意圖

而上述解決方案的承載方案,則是 VirtualView緩存

VirtualView的Github地址bash


2. VirtualView介紹

  • 簡介 微信

    示意圖

  • 特色架構

示意圖


3. 實現原理

3.1 核心思路

根據Tangram v1.0中 出現的問題:UI組件沒法動態更新 & 加載性能低,VirtualView的具體解決方案以下框架

示意圖

3.2 實現方案

  • 根據其原理,VirtualView的實現方案是:虛擬化開發
  • 虛擬化開發的本質:

示意圖

之因此稱爲虛擬化,是由於Canvas繪製的視圖不存在一一對應的實體View

3.3 總結

從上可知,VirtualView的創新在於:

  1. 經過 XML 模板實現組件的動態性
  2. 經過 虛擬化技術(本質 = Canvas)開發組件,提高了組件的渲染性能

4. 工做流程

  • 在瞭解了VirtualView的本質原理 & 總體架構後
  • 下面,我將開始講解VirtualView的工做流程

4.1 流程概述

  • 根據上述方案,VirtualView的工做流程分爲3大部分:建立UI組件、建立界面模板 & 客戶端加載界面
  • 具體以下

示意圖

4.2 流程詳細分析

下面我將對每一個流程的原理 & 過程詳細分析

流程1:建立UI組件

  • 具體描述 根據業務需求,建立所須要的UI組件

示意圖

有2種建立方式:使用框架內置(封裝好)的UI組件 / 自定義

1.1 使用框架內置(封裝好)的UI組件

  • 即 可直接使用封裝好的UI組件而不需自身建立
  • 具體以下(含組件基礎屬性)

注: a. 自定義組件應繼承基礎組件 b. 系統封裝UI組件的原理 同 「自定義UI組件,下面將具體講解

示意圖

###1.2 自定義UI組件 若框架內置的UI組件沒法知足需求,則開發者可自定義UI組件

  • 自定義流程 VirtualView抽象 & 封裝了 Canvas繪製視圖的流程,使得開發者只需按指定的接口協議實現1個組件的繪製邏輯:測量、繪製 & 繪製,即能實如今宿主容器經過 Canvas 直接繪製 UI內容,從而建立虛擬化組件

即 上述則是虛擬化建立組件的過程

  • 具體過程
  1. 實現基礎組件需遵循一個接口的規範:定義了渲染過程當中所需的3個流程:測量尺寸階段、佈局階段 & 繪製階段

a. 定義這3個階段是爲了符合Android系統的使用,即View繪製的三大流程:measure過程、layout過程、draw過程。若不瞭解,請看文章 (2)自定義View Measure過程 - 最易懂的自定義View原理系列 (3)自定義View Layout過程 - 最易懂的自定義View原理系列 (4)自定義View Draw過程- 最易懂的自定義View原理系列 b. 在 iOS 平臺下也需按照本方案的規範去處理

  1. 這3個過程具體以下:(與Android View繪製的三大流程類似)
    示意圖

不管是虛擬 / 原生組件,都採用上述模型 & 流程定義 a. 對於虛擬組件:在這些接口裏實現相關邏輯 / 經過封裝原生組件實現 b. 對於原生組件:在這些接口的實現裏 調用原生組件的對應邏輯 結論:可混合使用虛擬控件 & 實體控件

至此,對於宿主的佈局容器來講,包裝在內部的組件不分虛擬化 / 原生,暴露在外的接口相同,只要將宿主容器像普通的 View 同樣添加到的視圖界面上,就可在後續的渲染過程當中顯示出來。

  • 特別注意 此處便可解釋 爲什麼渲染性能高:因虛擬組件使用得越多,View個數就越少,即層級越扁平

以下所示的組件: a. 普通的原生開發:2層(宿主容器層 + 圖片組件層) b. 虛擬化開發:採用虛擬化開發後,最終呈現的 View層級只有一個宿主容器(實際上,圖片組件被繪製在Canvas裏了)

示意圖

1.3 總結

建立UI組件有2種方式:

  1. 直接使用框架內置的UI組件
  2. 自定義組件:經過封裝好的Canvas流程,按照指定接口協議實現繪製邏輯 / 封裝原生組件

流程2:建立界面模板 & 下發

  • 該步驟包括多個步驟:建立XML界面模板、編譯成二進制數據、下發等
  • 具體以下

示意圖

2.1 建立XML界面模板

  • 具體描述 根據業務需求,使用XML編寫模板

注:需使用專門的工具virtualview_tools編寫,其 使用說明見文章virtualview_tools使用指南

  • 此方式相似:Android 平臺上經過 XML 搭建界面的方式
  • 區別在於:
    1. 脫離了平臺限制,即一套模板可同時在AndroidiOS上使用
    2. 運行時動態加載 XML 模板數據,動態更新界面結構
// 引用的組件經過流程1中獲取
// 動態數據經過表達式從 JSON 數據裏獲取

<?xml version="1.0" encoding="utf-8"?>
<VHLayout
        flag="flag_exposure|flag_clickable"
        orientation="H"
        layoutWidth="match_parent"
        layoutHeight="wrap_content">
    <NImage
            id="1"
            src="${logoUrl}"
            layoutMarginLeft="8"
            layoutMarginRight="8"
            layoutMarginTop="8"
            layoutMarginBottom="8"
            layoutWidth="32"
            layoutHeight="32"/>
    <NText
            id="2"
            text="${title}"
            layoutGravity="v_center"
            gravity="${style.text-align}"
            textSize="${style.font-size}"
            textColor="${style.color}"
            layoutWidth="match_parent"
            layoutHeight="wrap_content"/>
</VHLayout>


// JSON數據
{
  "style": {
    "text-align": "h_center",
    "font-size": "20",
    "color": "#FF5000"
  },
  "title": "超高性 99.9% 的用戶以爲很快",
  "logoUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png"
}


複製代碼

2.2 編譯成二進制數據

2.2.1 具體描述

使用專門的工具virtualview_tools將編寫好的XML界面模板編譯成二進制數據,編譯後的文件的後綴名是.out

示意圖

使用說明見文章virtualview_tools使用指南

注:爲何經過 XML 編寫的業務組件 不直接在客戶端裏運行使用,而是先進行一次二進制序列化操做?

示意圖

2.2.2 二進制文件描述

借鑑了 Android 系統編譯模板文件的思路,格式 & 描述具體以下

示意圖

2.2.2 編譯流程

  • 一個業務組件對應着一份 XML 模板 = 單獨編譯成二進制數據

編譯數據 含除內置字符串資源外 它依賴的全部字符串、表達式資源

  • 編譯規則 編譯時,模板裏涉及的資源包括顏色值、各類枚舉、基礎組件的類型等都會被序列化映射成整數;不能序列化成整數的資源如字符串,就分配一個索引 Id 指向它 & 將它們單獨存儲到一塊區域裏
  1. 緣由:當模板在線發佈、字符串有變更的狀況下,可以不影響原來的字符串資源索引;不然若按照帶有順序約定的協議來分配資源索引,很容易在模板變動時 同一索引值在變動先後指向的資源內容是不同,影響穩定性和動態性
  2. 序列化的規則以下:

示意圖

  • 編譯流程

示意圖

2.3 模板數據 下發到客戶端

即 客戶端獲取編譯後的二進制數據

獲取有2種路徑:

  1. 直接將編譯後的模板打包到客戶端裏,開發者經過代碼加載
  2. 框架先發布到模板管理後臺,客戶端在線更新到模板數據(即實現了動態更新)

流程3:客戶端加載界面

  • 客戶端獲取到編譯後的界面模板後,進行加載 & 解析,最終渲染出視圖界面
  • 步驟流程以下圖

示意圖

3.1 解析模板數據

  • 具體描述 客戶端得到編譯後的模板數據(二進制數據)後,當即 進行解析
  1. 如校驗版本號,合法性,讀取頭信息等
  2. 客戶端渲染組件 從解析 編譯後的模板數據開始
  • 流程解析 解析過程 = 二進制編譯的逆過程

但解析流程只負責提取原始數據 & 組織格式,並沒有構建出組件對象

示意圖

3.2 加載組件視圖

  • 具體描述 當用戶傳入一個模板名稱,框架內部就會根據名稱去以前解析XML界面模板的數據裏找到 與此名稱匹配的模板數據,而後加載 & 建立出真正的組件

  • 流程解析

示意圖

3.3 綁定業務數據

  • 具體描述 開發者在組件屬性裏可經過 表達式 指定使用哪一個數據字段,即將業務數據綁定到組件上

因業務數據是動態的,故從模板建立的組件不含業務數據

  • 流程解析 在建立組件的過程當中,當解析屬性碰到表達式時,會將該屬性的key、表達式值、所屬的基礎組件等關係存儲起來,等真實數據到達後再經過 表達式裏的定義 訪問數據 & 將真實值設置給組件的屬性,即將真實的數據綁定到基礎組件的屬性上
  1. 經過表達式解析、訪問獲得的屬性值,會緩存起來,當原始數據引用不變時,每次訪問都會獲取到緩存值
  2. 此處接收的數據是 JSON 格式

示意圖

4.3 總結

示意圖


5. 總體架構設計

  • 根據上述方案 & 工做流程,VirtualView的總體框架分爲2部分:核心功能模塊(5個模塊) + 配套工具 & 服務。具體以下:

示意圖

  • 下面,我將對每部分進行詳細分析

模塊1:加載模塊

  • 示意圖

    示意圖

  • 說明

    示意圖

模塊2:構造模塊

  • 示意圖

    示意圖

  • 說明

    示意圖

此處詳細分析 基礎組件模型 & 虛擬組件

a. 基礎組件模型

含基礎組件 & 基礎屬性,具體以下

注:自定義的基礎組件應繼承基礎定義 & 擴展

示意圖

模塊3:輔助模塊

  • 示意圖

    示意圖

  • 說明

    示意圖

  • 特別注意:引入用戶數據綁定的表達式的緣由 開發業務組件時,基礎屬性 / 樣式不能在模板裏直接寫死,而是需從數據裏動態獲取

/**
  * 訪問數據屬性的表達式
  * 語法說明
  *       a. 以 「${」 開頭、以 「}」 結束
  *       b. 對於Map,經過「.」操做符訪問
  *       c. 對於 Array  /  List,經過 「[]」 操做符訪問
  * 示例以下
  */

  ${benefitImgUrl}${data[0].benefitImgUrl};

/**
  * 條件表達式
  * 做用:根據數據中某個字段 來設置值的屬性
  * 語法說明
  *       a. 以 「@{」 開頭、以 「}」 結束,
  *       b. 中間部分 = 表達式的具體內容:  條件表達式 ? 結果表達式[1] : 結果表達式[2]
  *           注:1. 當條件表達式成立的時,使用結果表達式[1],不然使用結果表達式[2]
  *              2. 條件表達式支持布爾類型、字符串類型、JSONObject、JSONArray
  *       c. 對於 Array  /  List,經過 「[]」 操做符訪問
  * 示例以下
  */
  @{${logoUrl} ? visible : invisible };

複製代碼

模塊4:管理模塊

  • 示意圖

    示意圖

  • 說明

    示意圖

模塊5:更新模塊

  • 示意圖

    示意圖

  • 說明

    示意圖

配套使用的工具 & 服務

  • 示意圖

    示意圖

  • 說明

    示意圖

總結

示意圖


6. 使用教程

  • 根據上述工做流程,其使用流程一樣分爲3步:建立UI組件、建立界面模板 & 客戶端加載界面
  • 下面,我將根據上述3個步驟進行詳細解析

6.1 建立UI組件

從一文可知,建立UI組件有2種方式:

  1. 直接使用框架內置的UI組件
  2. 自定義組件:經過封裝好的Canvas流程,按照指定接口協議實現繪製邏輯 / 封裝原生組件

此處爲方便講解,直接使用框架內置的UI組件

6.2 建立界面模板

此步驟包括:建立XML界面模板、編譯成二進制數據、模板下發

6.2.1 建立XML界面模板

根據業務需求,使用XML編寫模板

注:需使用專門的工具virtualview_tools編寫,其 使用說明見文章virtualview_tools使用指南

  • 示例佈局
/**
  * 使用說明:
  *     1. 控件引用:經過XML引用控件爲方便講解,XML內引用的VHLayout、NImage、NText 都是框架內置的控件:2個橫向線性佈局;每一個佈局 = 1個圖 + 1個文本
  *     2. 屬性設置:可寫死 / 經過表達式綁定一個數據字段(JSON)引用
  * 佈局說明:
  *     1. 引用的控件VHLayout、NImage、NText等都是框架內置的控件
  *     2. 整個佈局 = 2個橫向線性佈局,每一個佈局 = 1個圖 + 1個文本
  */

<?xml version="1.0" encoding="utf-8"?>
<VHLayout
        flag="flag_exposure|flag_clickable"
        orientation="V"
        layoutWidth="match_parent"
        layoutHeight="wrap_content">
    <VHLayout
            flag="flag_exposure|flag_clickable"
            orientation="H"
            layoutWidth="match_parent"
            layoutHeight="wrap_content">
        <NImage
                id="1"
                src="${logoUrl}"
                layoutMarginLeft="8"
                layoutMarginRight="8"
                layoutMarginTop="8"
                layoutMarginBottom="8"
                layoutWidth="32"
                layoutHeight="32"/>
        <NText
                id="2"
                text="${title}"
                layoutGravity="v_center"
                gravity="${style.text-align}"
                textSize="${style.font-size}"
                textColor="${style.color}"
                layoutWidth="match_parent"
                layoutHeight="wrap_content"/>
    </VHLayout>
    <VHLayout
            flag="flag_exposure|flag_clickable"
            orientation="H"
            layoutWidth="match_parent"
            layoutHeight="wrap_content">
        <VImage
                id="1"
                src="${logoUrl}"
                layoutMarginLeft="8"
                layoutMarginRight="8"
                layoutMarginTop="8"
                layoutMarginBottom="8"
                layoutWidth="32"
                layoutHeight="32"/>
        <VText
                id="2"
                text="${title}"
                layoutGravity="v_center"
                gravity="${style.text-align}"
                textSize="${style.font-size}"
                textColor="${style.color}"
                layoutWidth="match_parent"
                layoutHeight="wrap_content"/>
    </VHLayout>
</VHLayout>


複製代碼
  • 屬性數據來源:JSON
{
  "style": {
    "text-align": "h_center",
    "font-size": "20",
    "color": "#FF5000"
  },
  "title": "超高性 99.9% 的用戶以爲很快",
  "logoUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png"
}

複製代碼

6.2.2 編譯成二進制數據

使用專門的工具virtualview_tools將編寫好的XML界面模板編譯成二進制數據,編譯後的文件的後綴名是.out

示意圖

使用說明見文章virtualview_tools使用指南

6.2.3 模板下發到客戶端

有2種路徑:

  1. 直接將編譯後的模板打包到客戶端裏,開發者經過代碼加載
  2. 框架先發布到模板管理後臺,客戶端在線更新到模板數據(即實現了動態更新)

此處選擇方式1

6.3 客戶端解析 & 加載界面模板

具體使用以下

// 1. 初始化圖片加載器
    VafContext.loadImageLoader(mContext.getApplicationContext());

// 2. 初始化 ViewManager 對象
    ViewManager viewManager = vafContext.getViewManager();
    viewManager.init(mContext.getApplicationContext());

// 3. 加載編譯後的模板數據(二進制文件)
     // 方式1:直接加載二進制字節數組(推薦使用)
     viewManager.loadBinBufferSync(TMALLCOMPONENT1.BIN);
     viewManager.loadBinBufferSync(TMALLCOMPONENT2.BIN);
     // 方式2:經過二進制文件路徑加載
     viewManager.loadBinFileSync(TMALLCOMPONENT1_PATH);
     viewManager.loadBinFileSync(TMALLCOMPONENT2_PATH);

// 4. 註冊事件處理器,如經常使用的點擊、曝光處理
    vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {

        @Override
        public boolean process(EventData data) {
            //handle here
            return true;
        }
    });
    vafContext.getEventManager().register(EventManager.TYPE_Exposure, new IEventProcessor() {

        @Override
        public boolean process(EventData data) {
            //handle here
            return true;
        }
    });

// 5. 經過組件名參數 name 生成組件實例
    View container = vafContext.getContainerService().getContainer(name, true);
    mLinearLayout.addView(container);

// 6. 爲組件綁定真實的數據
    // 倘若您在組件模板裏寫了數據綁定的表達式
    IContainer iContainer = (IContainer)container;
    JSONObject json = getJSONDataFromAsset(data);
    if (json != null) {
        iContainer.getVirtualView().setVData(json);
    }

複製代碼
  • 測試結果

下圖展現的「超高性 99.9% 的用戶以爲很快」即爲VirtualView的展現效果

示意圖

至此,關於VirtualView的使用講解完畢

更加詳細使用,請參考文章VirtualView的使用文檔


7.VirtualView 的意義

對於我的的見解,VirtualView的補充其重大意義在於2個方面:對於 阿里Tangram 模型 & 整個原生開發技術(Android、iOS)

7.1 對於 Tangram 模型

VirtualView的解決的問題 在於:

  1. 實現組件的動態性:可在端上綁定動態下發的 XML 界面模板 & 數據
  2. 提高了組件的渲染性能:經過 虛擬化技術(本質 = Canvas)開發組件

7.2 對於整個原生開發技術(Android、iOS)

VirtualView的創新在於:解決了 原生開發中一直被詬病 而 常被叫喧會被 前端、RN技術取代的問題

  1. 開發週期長 & 成本大 VirtualView 採用XML描述視圖,XML界面模板具有跨平臺使用的特性

  2. 沒法熱更新 VirtualView可在端上綁定動態下發的 XML 界面模板 & 數據,從而實現熱更新

  • 相比於前幾年產品開發的一味求快,現在互聯網行業發展暫緩、用戶需求基本知足的狀況下,更加 講求的是用戶體驗

  • 因此,實際上對比於 前端、RN技術在客戶端的實現,VirtualView的優點或許會更明顯:在解決了原生開發效率慢、週期長的前提下,保證了原生開發的優點:性能好

7.3 呼籲

  • 雖然VirtualView 推進了原生開發的發展,但目前來講,VirtualView 仍是存在很多問題
  • 但願你們能一塊兒在Github - alibaba - VirtualView 上進行完善,共同爲開源事業作貢獻吧!

8. 總結

  • 看完本文,你應該很是瞭解阿里出品的VirtualView 的使用 & 原理
  • 關於Tangram的使用,建議看文章:
  1. Android Tangram模型:連淘寶、天貓都在用的UI框架模型你必定要懂
  2. Android開源庫V - Layout:淘寶、天貓都在用的UI框架,趕忙用起來吧!

請幫頂!由於你的鼓勵是我寫做的最大動力!

參考文章: juejin.im/post/5a2a71… tangram.pingguohe.net/docs/virtua…


歡迎關注carson_ho的微信公衆號

示意圖

示意圖
相關文章
相關標籤/搜索