一直以來,無線應用都在不斷尋求動態化頁面的解決方案,在阿里巴巴集團內,除了風風火火地 Weex 項目外,各個團隊都有大大小小的解決方案。咱們貓客一直持續基於 Tangram 方案來解決頁面動態化的問題,然而在面對持續升級的業務需求時,原有的開發模式也慢慢變得沒法勝任,本年度以來,咱們 Tangram 體系在各個層面都進行了大跨度的技術升級(可參考文章天貓APP改版之首頁架構&開發模式全面升級),本文再詳細介紹一下頁面內組件體系升級方案。html
在原有的 Tangram 體系裏,主要解決了頁面內佈局結構的動態化能力,經過 json 數據描述能夠組合出經常使用的頁面結構。然而頁面內具體的坑位樣式,咱們稱之爲業務組件,是採用常規的 native 代碼開發的,除非內置了足夠多的邏輯,不然組件的樣式調整或者新組件的開發都要發佈版本,沒法知足業務節奏;固然咱們也嘗試過使用 Weex 開發業務組件貼到頁面上,可是在體驗和性能上仍是有較大的缺陷。git
因此總結起來,就是兩點問題:github
對於上述問題,解決思路實際上是比較通用的,要動態更新界面視圖,就須要用界面模板描述視圖,模板與數據分離。將動態下發的模板和數據在端上綁定渲染。要提高性能,也有三大着力點——減小視圖層級與個數,結構儘可能扁平化;異步佈局渲染流程,解放主線程計算量;回收與複用組件,減小內存開銷。json
新的組件體系就是在模板化描述視圖,動態更新視圖,減小視圖層級幾個方面作文章,至於組件的回收複用,則是在頁面級別統一完成;而異步佈局渲染流程,則是後續的優化方向。canvas
新的組件方案稱之爲 VirtualView,簡稱 VV,也稱爲2.0組件,它的設計遵循如下幾個思路:後端
先從總體上預覽一下整個方案的大致結構:數組
自下往上,自左往右的順序介紹各個模塊:安全
有了上述基礎,當咱們要開發新的業務組件的時候,除了有新增 Native 邏輯的需求場景(好比新增視頻功能),大部分需求均可以告別原生代碼的編寫,轉而編寫組件模板。性能優化
值得注意的是,在上述架構及流程裏,描述了一個完整的實踐經驗,但對於本方案來講,核心點在於提供了對組件從編寫到展現流程的實現,其周邊的配套設施,並無內置在框架裏,包括客戶端上的模板管理、更新、註冊模塊,以及後端的模板發佈服務,由於這些模塊每每涉及業務邏輯,且與各個應用的基礎設施相關,內置在框架裏反而限制了使用方的接入。這裏提供一些可供參考的經驗:bash
對於組件,咱們作了以下定義,每個基礎的原子組件或者容器組件都會有如下屬性,自定義的基礎組件應當繼承自基礎定義並作擴展。
名稱 | 類型 | 默認值 | 描述 |
---|---|---|---|
id | int | 0 | 組件id |
layoutWidth | int/float/enum(match_parent/wrap_content) | 0 | 組件的佈局寬度,與Android裏的概念相似,寫絕對值的時候表示絕對寬高,match_parent表示儘量撐滿父容器提供的寬高,wrap_content表示根據自身內容的寬高來佈局 |
layoutHeight | int/float/enum(match_parent/wrap_content) | 0 | 組件的佈局寬度,與Android裏的概念相似,寫絕對值的時候表示絕對寬高,match_parent表示儘量撐滿父容器提供的寬高,wrap_content表示根據自身內容的寬高來佈局 |
layoutGravity | enum(left/right/top/bottom/v_center/h_center) | left|top | 描述組件在容器中的對齊方式,left:靠左,right:靠右,top:靠上,bottom:靠底,v_center:垂直方向居中,h_center:水平方向居中,可用或 組合描述 |
autoDimX | int/float | 1 | 組件寬高比計算的橫向值 |
autoDimY | int/float | 1 | 組件寬高比計算的豎向值 |
autoDimDirection | enum(X/Y/NONE) | NONE | 組件在佈局中的基準方向,用於計算組件的寬高比,與autoDimX、autoDimY配合使用,設置了這三個屬性時,在計算組件尺寸時具備更高的優先級。當autoDimDirection=X時,組件的寬度由layoutWidth和父容器決策決定,但高度 = width * (autoDimY / autoDimX),當autoDimDirection=Y時,組件的高度由layoutHeight和父容器決策決定,但寬度 = height * (autoDimX / autoDimY) |
minWidth | int/float | 0 | 最小寬度 |
minHeight | int/float | 0 | 最小高度 |
paddingLeft | int/float | 0 | 左內邊距 |
paddingRight | int/float | 0 | 右內邊距 |
paddingTop | int/float | 0 | 上內邊距 |
paddingBottom | int/float | 0 | 下內邊距 |
layoutMarginLeft | int/float | 0 | 左外邊距 |
layoutMarginRight | int/float | 0 | 右外邊距 |
layoutMarginTop | int/float | 0 | 上外邊距 |
layoutMarginBottom | int/float | 0 | 下外邊距 |
background | int | 0 | 背景色 |
backgroundImage | string | null | 背景圖地址 |
borderWidth | int | 0 | 邊框寬度 |
borderColor | int | 0 | 邊框顏色 |
visibility | enum(visible/invisible/gone) | visible | 可見性,與Android裏的概念相似,visible:可見,invisible:不可見,但佔位,gone:不可見也不佔位 |
gravity | enum(left/right/top/bottom/v_center/h_center) | left|top | 描述內容的對齊,好比文字在文本組件裏的位置、原子組件在容器裏的位置,left:靠左,right:靠右,top:靠上,bottom:靠底,v_center:垂直方向居中,h_center:水平方向居中,可用或 組合描述 |
方案內內置了一系列基礎組件,完整的組件列表以下:
上文提到虛擬化開發的組件的技術,簡稱虛擬組件。不少作性能優化的方案、建議都會提到採用 Canvas 直接繪製的方式來減小 View 的個數,虛擬將這個開發流程作了抽象與規範,可讓開發人員像定義原生組件同樣定義虛擬組件。
具體來說,基礎組件須要遵循一個接口的規範,這個口定義了渲染過程當中須要的三個流程:計算尺寸階段、佈局階段、繪製階段;定義這個三個階段是爲了更好的與系統平臺特別是 Android 平臺對接,由於在 Android 原平生臺下也會有這個三個階段,在 iOS 平臺下則也須要按照本方案裏要求的規範去處理。計算尺寸階段定義要觸發一次尺寸計算,須要對其包含的子組件進行計算調用;佈局階段定義了要觸發一次佈局,將子元素按照計算好的位置尺寸排布,也要對包含的子組件進行佈局調用;繪製階段定義要進行視圖繪製,固然也要對起包含的子組件進行繪製的調用;對於虛擬組件,就在這些接口裏實現相關邏輯,而對於原生組件,在這些接口實現裏調用原生組件的對應邏輯。
不管是虛擬化組件仍是原生組件,都採用上述相同的模型來定義,再加上相同的尺寸計算接口、佈局接口、繪製接口,這樣對於宿主容器來講,包裝在內部的組件就不分虛擬化仍是原生,一視同仁,暴露給外面的接口也是同樣的,只要將宿主容器像普通的 View 同樣添加到的視圖界面上,就能夠在後續的渲染過程當中顯示出來。若是虛擬組件使用的越多,View 的個數就越少,對於系統來講層級越扁平。如下圖示例的組件來講,最終呈現的 View 只有宿主容器和兩個圖片組件,若是將圖片也用虛擬化的方式實現,最終 View 只有一個宿主容器,而界面仍然保持不變。
經過 XML 編寫的業務組件,並不直接在客戶端裏運行使用,而是先進行一次二進制序列化操做,原始的 XML 模板文件保存成文件的時候,就是以純文本的形式存在,會包含不少冗餘信息,好比空格、換行、還有重複出現的字符串等,文件體積比較大,以xml解析器去解析的時候,也會須要大量字符串操做,效率和性能不能達到最優。而將它編譯成二進制格式,會避免這些問題,好比文件重複出現的字符串只保留一份,經過字符串索引去引用它,全部的組件類型也都會被轉換成一個數字索引,在客戶端內經過數字索引反過來找到對應的類實例化。這樣文件格式會很是緊湊,體積更小。整個設計也借鑑了 Android 系統編譯模板文件的思路。它的具體格式說明以下:
按照圖中從左往右、從上往下的順序分別說明每一個段的做用:
開發業務組件的時候,基礎屬性或者樣式每每不能在模板裏直接寫死,而是須要從數據裏獲取,因此引入了用戶數據綁定的表達式,語法和實現上目前比較簡單,參考了不少同類的設計,儘量符合開發人員的直覺。
語法上以 ${ 開頭,以 } 結束。對於Map,經過 . 操做符進行訪問,對於 Array 或者 List 經過 [] 操做符進行訪問。
好比:
${benefitImgUrl}
${data[0].benefitImgUrl}
複製代碼
用來給那些須要根據數據中某個字段來設置值的屬性,語法上以 @{ 開頭,以 } 結束,中間部分爲表達式的具體內容。
條件表達式 ? 結果表達式[1] : 結果表達式[2]
複製代碼
當條件表達式成立的時候,使用結果表達式[1],不然使用結果表達式[2]。 其中: 條件表達式支持布爾類型、字符串類型、JSONObject、JSONArray。 如下場景均爲 false:
好比:
@{${logoUrl} ? visible : invisible }
複製代碼
考慮到篇幅限制,不能將上述架構和流程中的每一細節徹底展開,詳情能夠參考蘋果核這裏的文檔。
VirtualView 方案是 Tangram 的極大補充,能夠解決80%場景下的動態化需求,而 Tangram 依賴的數據則經過 TAC 提供解決,三者結合能夠造成一個閉環,讓一個開發從端到端地解決整塊業務的開發。
以雙十一期間爲例,90%的雙十一業務組件都是動態下發的,且隨時可根據業務節奏調整。
儘管在功能流程上已經逐步穩定,能承載起平常及大促的需求變動,咱們的方案仍是有不少不足之處的,好比咱們指望更高的運行效率、更加扁平化的UI結構、更加方便的開發體驗,對此也作了更進一步的規劃建設:
功能 | 計劃 |
---|---|
提供更加完善的文檔和教程、Demo,內外版本同步,創建以 github 爲中心的迭代開發機制 | 17年12月 |
組件建立、佈局計算、數據綁定機制優化,提高性能 | 18年1月 |
重構模板編譯工具,提高編譯開發體驗 | 18年1月 |
提供預覽服務,提高開發效率 | 18年3月 |
提供配套的後端數據服務與基礎設施,即 TAC 平臺開放 | 18年3月 |