苹果核 - 页面动态化的基础 —— Tangram
12月10日在SFDC(SegmentFault Developer Conference)大會上初次介紹了手機天貓的Tangram方案,現(xiàn)場時間有限,講得匆忙,特此整理記錄。這篇內(nèi)容是Tangram的整體介紹與相關(guān)業(yè)務開發(fā)實踐的介紹,后續(xù)逐步會將更詳細的方案整理成文分享出來。
1. 什么是Tangram
顧名思義,Tangram中文名是七巧板的意思,我們希望這個框架提供一系列基本單元,就像積木塊一樣,通過快速拼裝就能搭建出一個頁面或者調(diào)整頁面的結(jié)構(gòu)。重運營的業(yè)務特別是電商業(yè)務,往往講究靈活多變,需要對線上業(yè)務做實時調(diào)整,此類頁面動態(tài)化的需求便應運而生。
2. Tangram的設計理念
對于客戶端開發(fā)來說,版本發(fā)出去之后,再要修改代碼,是一件成本比較高的事情,針對線上實時調(diào)整比較多的地方,往往就采用了H5的方式上線。由于H5的體驗相對Native欠缺一些,就有了后來Facebook的ReactNative(RN),以及阿里自己的解決方案Weex,以Native的方式實現(xiàn)頁面動態(tài)調(diào)整的能力。在如今,表面上看起來Tangram的方案會有些多余,但是通過了解它的設計與演變,那就知道它還是有存在的理由。
在歷史上,大概兩年前的這個時候,我們團隊接手天貓首頁的業(yè)務,迫切需要一套頁面動態(tài)化方案。那個時候RN剛剛面世不久,特別是Android版本的RN還不穩(wěn)定,更不用說后來的Weex了。而我們手里有的一套方案是自己開發(fā)過的Dynative(可以理解為初級版本的Weex)。但這些方案有個共同點就是比較重量級,它們都期望從基本的UI元素開始做一套純動態(tài)的方案。在種種現(xiàn)有框架不成熟的時候,對于首頁這種重量級的頁面,我們還是希望以一種更加純粹的Native開發(fā)模式來支撐業(yè)務。
在設計理念上,Tangram也有它的特殊之處,無論是H5還是Weex之類的方案,它們的動態(tài)能力在于隨時可發(fā)布代碼,它們是面向開發(fā)的動態(tài)化方案,發(fā)布代碼異味著測試、灰度、發(fā)布等一系列流程。而Tangram是面向運營和產(chǎn)品的方案,它的動態(tài)能力體現(xiàn)在無須做代碼改動,提供足夠多的動態(tài)可配置的能力,通過在后臺做樣式的調(diào)整來達到頁面調(diào)整的目的。所以簡單比較如下:
| 動態(tài)能力 | 強 | 較強 |
| 面向人員 | 開發(fā) | 開發(fā) |
| 體量 | 完善的體系 | 較重量級 |
| 體驗 | 常規(guī)H5體驗 | 鑒于H5和native之間 |
鑒于這樣的設計目標,在這個框架里,重點有四個方面的抓手:
3. Tangram里重要的概念模型
3.1 頁面拆解
從一個實例出發(fā),上圖中展示的是一個早期的天貓首頁,根據(jù)導購頁面的特點,我們將頁面拆分成三個層次:頁面——卡片——組件,頁面(第一張圖)指的就是整體可滑動頁面實體,并沒有特殊之處;卡片指的是頁面內(nèi)可按行劃分的一個一個獨立區(qū)塊(參考第二張圖),組件(參考第三張圖)指的是卡片內(nèi)部一個獨立的、業(yè)務級別的單元,它可以是一張圖,也可以是文字+圖的組合。因此整體整個頁面可以這樣描述:一個頁面嵌套了多個卡片,一個卡片嵌套了多個組件。
3.2 頁面結(jié)構(gòu)
通過將頁面拆分成三層結(jié)構(gòu),整個頁面在model上就可以描述成這樣一個樹狀結(jié)構(gòu)。這里最重要的兩個model是卡片和組件的model,整個頁面的動態(tài)化將通過它們的動態(tài)化以及它們之間組合關(guān)系的動態(tài)化來完成。下面看這兩層的具體協(xié)議描述:
3.3 卡片模型
卡片的職責是負責對組件進行布局,那么如何描述布局呢,前面說過,我們采用的是粗粒度的動態(tài)化方式,卡片的布局描述就是一種聲明式的方式,因此卡片不需要布局模板,只要在model的數(shù)據(jù)里描述卡片的類型即可,至于卡片有哪些類型,則是注冊在Tangram框架里的,業(yè)務方在接入框架的時候也可以注冊自定義的卡片類型。這樣就讓Tangram省去了對布局模板的解析,簡化了框架復雜度的同時,簡化了開發(fā)復雜度。
卡片model描述上有四個組成:header、footer、body、style。最重要的是body部分,它包含了內(nèi)嵌的組件model,如果卡片沒有body,即沒有組件,也就不在視覺上做渲染。卡片的布局也就是對body里包含的組件來進行布局。Tangram內(nèi)置了一系列布局能力對組件進行布局,包括流式布局、瀑布流布局、吸頂布局、懸浮布局、輪播布局等等,基本上常見的布局方式都可以覆蓋到。header、footer是卡片的標題和尾部,這是根據(jù)業(yè)務場景設計的可選內(nèi)容,因為很多時候一塊業(yè)務區(qū)域會有個標題之類的東西。在實現(xiàn)的時候,我們可以將他們轉(zhuǎn)換到body里的組件,但在概念上,單獨描述會更容易理解。style是對布局樣式的描述,所有布局會有一些通用的樣式屬性,也有一些特有的,通過樣式的描述,可以讓布局能力更加豐富。畫圖舉幾個例子:
3.4 卡片樣式簡介
這里對卡片的樣式做一些介紹,因為很多時候頁面調(diào)整就是對樣式的調(diào)整,結(jié)構(gòu)調(diào)整也會涉及到樣式調(diào)整,因此樣式的動態(tài)性對頁面的動態(tài)性具有重要貢獻,這里舉例的是幾個通用的樣式屬性,如果卡片比較特殊,還可以自定義樣式屬性。
- backgroundColor: 卡片的背景,在做頁面氛圍的時候經(jīng)常會用到。
- margin/padding: 卡片外邊距、內(nèi)邊距,這是通用UI系統(tǒng)都會支持的屬性。
- gap: 卡片內(nèi)的組件往往需要增加間距,如果通過組件的margin來實現(xiàn),會有很多不便之處,相鄰組件間左右或者上下都配置了margin,則需要考慮去重的實現(xiàn),要么就在配置的時候?qū)ο噜徑M件的margin做精心控制。用gap的概念則很方便,它可以指定水平方向間距、垂直方向間距。
- cols: 默認情況下,流式布局每一列寬度都是等分屏幕寬度的,如果需要做不等分的布局,就可以通過cols來指定每一列的占比,這樣布局能力就能更加豐富了。
3.5 組件模型
組件的職責是負責業(yè)務邏輯和UI元素展示,它是盡可能小的業(yè)務單元,一般以實際設計稿出發(fā),抽象出最小可復用單元。組件也是聲明式的,需要在model的數(shù)據(jù)里描述組件的類型,至于有哪些類型,也是業(yè)務方在接入時預先注冊,因為組件的業(yè)務成分比較重,Tangram一般就不內(nèi)置了。除了類型描述,model數(shù)據(jù)里剩下的就是組件的數(shù)據(jù)和樣式描述了。組件的數(shù)據(jù)不做具體規(guī)范,一般滿足組件自身的需求即可,樣式也不做強制規(guī)范,但有一些和布局相關(guān)的樣式在框架層面會進行支持,這個下文介紹。
在組件的實現(xiàn)上,它首先是一個普通的View,并特殊之處,如果脫離Tangram框架,它也應該能正常運行使用。但在Tangram里,我們?yōu)榻M件設計了一個統(tǒng)一的ViewModel,定義了幾個生命周期事件;通過ViewModel對組件的屬性進行賦值,在組件初始化時會調(diào)用init,在滑入屏幕綁定數(shù)據(jù)時候調(diào)用bind,在滑出屏幕解除綁定時調(diào)用unbind。
除此之外組件的行為基本上都是業(yè)務邏輯了,不做過多介紹,這里再介紹幾個和頁面動態(tài)性相關(guān)的樣式。
3.6 組件樣式簡介
- backgroundColor: 組件的背景,同樣也是在做頁面氛圍的時候經(jīng)常會用到。
- margin/padding: 組件外邊距、內(nèi)邊距,同樣也是UI系統(tǒng)都會支持的屬性。
- display: 參考css的設計,特別是在流式布局里,組件默認都是內(nèi)聯(lián)(inline)的,當布局占滿屏幕寬度時,再考慮換行。如果在正常的流式卡片布局里要橫插一行,則可以將組件聲明為block,不然的化,就得將這個卡片打散成三個卡片才行。
- colspan: 默認情況下,流式布局每一列寬度都是等分屏幕寬度的,也就是占用一個格子,組件上聲明colspan可以讓這個組件占用多個格子。它與卡片上的cols區(qū)別在于它占用的寬度值是離散的,而cols通過百分比可以做到寬度值的連續(xù)分布。
- width/height: 其實是組件的寬高比,用來對組件進行對齊,利于界面排版。
每個組件都可以聲明額外的自定義樣式屬性,比如字體顏色、字體大小等等,這里就不做過多介紹。通過卡片和組件的樣式,基本上就可以組合出大部分場景的頁面結(jié)構(gòu)了,也就是Tangram的初衷——像搭積木一樣拼裝一個頁面。
4. 實現(xiàn)原理
上面介紹了整個Tangram的基本概念,花了這么多篇幅講概念模型,除了告訴大家這個東西是什么、它做什么、它是怎么設計的,最重要傳遞的一個信息是,作為業(yè)務系統(tǒng),需要首先在概念模型上做好架構(gòu)設計,在協(xié)議規(guī)范上做好統(tǒng)一,這樣具體的平臺去實現(xiàn)的時候,都能根據(jù)這個規(guī)范來做實現(xiàn),不管誰實現(xiàn)的,都屬于Tangram,這就好比JAVA虛擬機規(guī)范和JAVA虛擬機的關(guān)系一樣。對于我們團隊來說,對Tangram的實現(xiàn)也經(jīng)歷了一系列變更,但基本規(guī)范沒怎么變動,這也是能大規(guī)模去支持業(yè)務的一個重要支點。下面會介紹目前實現(xiàn)上的思路和重要技術(shù)點。
4.1 基本結(jié)構(gòu)和流程
主要有這么幾個組成:核心引擎、數(shù)據(jù)解析器、卡片庫、組件庫、布局框架,核心引擎負責調(diào)度整個流程,在啟動框架的時候要核心引擎要做一系列初始化,包括初始化卡片庫和組件庫,也就將內(nèi)置的卡片類型注冊進框架,將外部業(yè)務提供的組件也注冊好,同時也要將數(shù)據(jù)解析器初始化好,布局框架也要初始化好。當頁面數(shù)據(jù)傳入的時候,核心引擎調(diào)用數(shù)據(jù)解析器將數(shù)據(jù)轉(zhuǎn)換成卡片和組件的model對象,解析過程會根據(jù)之前注冊過的卡片、組件類型來解析,不認識的數(shù)據(jù)將會被拋棄,卡片和組件的基本樣式也會解析。解析完畢的卡片、組件model將會扔給布局框架進行頁面渲染。布局框架根據(jù)卡片提供的布局信息進行布局,根據(jù)組件提供的組件信息進一步獲取組件實例,貼到布局容器里。
4.2 布局框架實現(xiàn)
實現(xiàn)上難度最大的在于布局框架,布局框架的靈活性、性能決定了整個Tangram的靈活性和性能。在Android上,布局框架基于RecyclerView+自定義LayoutManager的方式實現(xiàn);在iOS上,布局框架基于自定義的LazyScrollView來實現(xiàn)。這兩框架基本上都能做到對頁面的扁平化實現(xiàn),提供了夸卡片的組件級別復用能力。先對這兩塊做一個介紹:
整個頁面樹被解析出卡片+組件的數(shù)據(jù)列表之后,會對塊數(shù)據(jù)做進一步轉(zhuǎn)換。首先提取所有組件model,也就是將組件都打平到同一級別的列表,這個列表會被傳遞給RecyclerView的Adapter,因此數(shù)據(jù)的位置其實就對應了RecyclerView看到的組件位置。而卡片model,將會拿來構(gòu)建一個個LayoutHelper,這些LayoutHelper是負責具體布局的對象,一種布局類型的卡片對應于一種LayoutHelper,而且LayoutHelper還包含了它負責的組件的位置起始區(qū)域,它們會被傳遞給自定義的LayoutManager。當RecyclerView開始渲染頁面或者滑動時,它內(nèi)部維護了一個布局狀態(tài),獲取當前屏幕范圍內(nèi)還有多少區(qū)域是空白的,下一個要加載的View的位置是多少,然后把這些信息告訴LayoutManager去加載View做布局。我們的自定義LayoutManager拿到這個位置之后,就反向查找對應的LayoutHelper,然后交給LayoutHelper去布局,這個過程還會涉及到從回收復用池或者通過Adapter獲取一個組件實例。不同的LayoutHelper會按照約定的協(xié)議進行進一步布局。
對于iOS來說,也有類似的布局邏輯,但這里重點介紹iOS的頁面容器LazyScrollView。這是一個自定義的滾動布局,具備回收復用能力。它的回收復用算法是這樣的:在頁面渲染前先計算所有組件的位置信息,根據(jù)組件在頁面內(nèi)位置的上邊距做一個排序索引,根據(jù)下邊距再做一個排序索引。頁面滾動的時候通過滾動區(qū)域與上下邊距的取交集,就可以獲取到當前可見范圍的組件是哪些,然后不可見范圍內(nèi)的組件實例可以回收,新進入可視區(qū)域的組件可以從回收復用池里拿到組件實例或者新創(chuàng)建一個組件實例貼到布局里。
4.3 擴展
上面介紹的內(nèi)容構(gòu)建里Tangram的基本骨架,但要支撐起業(yè)務,還需要很多輔助工具,如果沒有這些擴展,將很難支撐業(yè)務。這些擴展有些是內(nèi)部注冊在框架,也有些是外部注入。
- 點擊處理模塊,組件都需要有點擊交互,點擊處理模塊定義了接口,業(yè)務方根據(jù)接口實現(xiàn)具體模塊然后注入。
- 曝光處理模塊,與點擊模塊類似,可提供組件曝光時的業(yè)務邏輯加載。
- 通用定時器模塊,用來提供計時功能,滿足組件內(nèi)的倒計時需求、定時需求。
- 事件總線,用來做組件與卡片的通信,或者組件與外部通信等等。
- 腳本動畫,將動畫腳本畫,提供動畫的動態(tài)能力,讓組件的交互更加豐富。
- 通用請求模塊,有時候卡片數(shù)據(jù)、或者組件數(shù)據(jù)需要調(diào)用遠程接口更新,同通用請求也是定義了加載接口,外部業(yè)務方自行實現(xiàn)注入。
- 純動態(tài)組件,解決組件動態(tài)問題,因為我們的動態(tài)化是粗粒度的,行走江湖免不了內(nèi)置動態(tài)能力滿足不了一個臨時需求的場景。動態(tài)組件集成了集團內(nèi)的動態(tài)化方案,目前最主要的就是阿里的Weex方案,通過Weex的動態(tài)能力來解決組件的動態(tài)能力。
有了這些擴展功能,整個Tangram落地到業(yè)務就非常方便了,目前我們支撐了天貓首頁、天貓直播首頁、天貓超市首頁等重要業(yè)務,還推廣到了集團其他部門。
5. 實踐經(jīng)驗
最后分享一些業(yè)務開發(fā)的經(jīng)驗,分客戶端和后端運營兩方面介紹。延伸到其他業(yè)務,這些經(jīng)驗應該也是有借鑒意義的。
5.1 客戶端
首先是規(guī)范與協(xié)議的統(tǒng)一,我們這個框架支撐了多個業(yè)務,不同業(yè)務、不同平臺之間,只有規(guī)范統(tǒng)一,才能盡可能支撐多的業(yè)務,否則不同業(yè)務接入,要做轉(zhuǎn)換,是一件成本很高的事情。在不同平臺之間統(tǒng)一規(guī)范,也可以讓一份數(shù)據(jù)在多端使用。
在客戶端上開發(fā),穩(wěn)定性是一個非常重要的指標,整個應用應該有自己的保護模式,對于框架來說,我們也要有防御性編程的思維,特別是是動態(tài)化方案,往往根據(jù)數(shù)據(jù)來執(zhí)行代碼,訪問數(shù)據(jù)本身要足夠小心,像空字段、類型轉(zhuǎn)換、數(shù)組越界都是場景的問題,通過安全方法的使用,可以在一個地方保證數(shù)據(jù)訪問的安全性。
組件庫,也是一個非常重要的建設,不同業(yè)務之間可以復用相同的組件,減少組件開發(fā)。
容器化是實現(xiàn)頁面在線拼裝的一個必要的建設,客戶端需要有一個容器頁面,就像webview一樣,給一個url,就可以加載頁面,后端也也需要做數(shù)據(jù)的容器化接入,能導入業(yè)務數(shù)據(jù),按照Tangram協(xié)議輸出。再利用組件庫里的現(xiàn)有組件,就可以完成一個頁面搭建,目前有一些簡單的頁面(搜索專輯、推薦專輯)就是這樣完成上線的。
解耦也是一個老生常談的問題,在Tangram里實現(xiàn)擴展能力的時候,解耦這一方面做得就特別棒,像腳本動畫、Weex都是其他團隊的成果,但是可以很方便的插入到Tangram框架里,而且可以熱插拔,業(yè)務方不想使用就可以不接入。
5.2 運營管理
Tangram是一個動態(tài)框架,雖然它的重點技術(shù)在客戶端,但是沒有后端的話是不完整的,必須要有一個完善的后端管理平臺來做頁面的日常運維才行。我們開發(fā)了一個專門的管理后臺,可以對Tangram頁面做多維度的管理。后端管理平臺還承載了頁面的穩(wěn)定性、頁面發(fā)布的效率、頁面試錯等能力。
Tangram頁面動態(tài)調(diào)整都是配置發(fā)布,視覺調(diào)整。因此我們有獨特的發(fā)布流程,首先后臺變更完成之后不會直接發(fā)布,而是進入到預發(fā)布狀態(tài),在這個狀態(tài)下,可以通過白名單預覽提前檢查變更效果,預覽的方式是將變更生成一個二維碼,在手機上掃碼預覽,檢查最真實的效果。通過時間機器調(diào)整時間,不僅可以預覽這次變更在當前時間的效果,還可以預覽將來某個時間的效果,因為不同的時間點,生效的數(shù)據(jù)不一樣,因此時間維度的預覽特別有用。另外管理平臺對變更人員也做了權(quán)限控制,每個業(yè)務方人員只能變更自己負責的業(yè)務,不會改動到其他業(yè)務的頁面,通過對接流程平臺,讓每次變更都有記錄可查,防止線上數(shù)據(jù)隨意更改。
有了頁面發(fā)布、變更的穩(wěn)定性保證,發(fā)布的效率也是下一個重要考慮的問題。定時發(fā)布可以讓變更在指定時間生效,比如雙十一的時候很多東西要0點生效,如果0點做變更、預覽、再上線,風險很大,有了定時發(fā)布,可以提前做好準備。一鍵下線的功能,可以做多版本里的某個共用卡片批量下線,特別是緊急情況。批量復制創(chuàng)建卡片和版本通配,都是為了解決新版本發(fā)布時候的效率,目前我們針對一個版本客戶端就發(fā)布一份頁面配置數(shù)據(jù),有了版本通配,可以減少頁面配置的數(shù)量,有了批量復制創(chuàng)建,可以在要創(chuàng)建新版本頁面的時候復制頁面。
快速試錯,這是近年來非常熱門的一個領(lǐng)域,當頁面要進行調(diào)整的時候,我們希望看到調(diào)整的效果,這個時候abtest就派上了用場。天貓有一套自己的試錯平臺,Tangram前后端都對接了這條試錯平臺,在管理平臺可以將變更做成實驗變更,然后導入到試錯平臺下發(fā)到客戶端,進入到實驗分桶的用戶就可以訪問到實驗變更,同時試驗平臺在端上也做了數(shù)據(jù)采集,這樣可以在小范圍內(nèi)先試驗變更的效果,根據(jù)數(shù)據(jù)來做接下來的決策。
6. 小結(jié)
本次分享整體性的介紹了Tangram的技術(shù)方案和一些開發(fā)經(jīng)驗,內(nèi)容比較多,很多地方只能在整體思路上進行介紹,后續(xù)我們會逐步將一些細節(jié)開放出來分享。本文提到的一些天貓相關(guān)技術(shù)在團隊博客上有介紹,歡迎訪問:
- 團隊博客
- Data-Driven在雙11中小試牛刀
- iOS 高性能異構(gòu)滾動視圖構(gòu)建方案 —— LazyScrollView
- 快速決策方案 —— Airtrack
總結(jié)
以上是生活随笔為你收集整理的苹果核 - 页面动态化的基础 —— Tangram的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果核 - Tangram 的基础 ——
- 下一篇: GitBook使用教程