何爲Virtualview
,簡單來講,就是經過xml
來描述視圖,而後壓縮成二進制格式,客戶端經過解析並渲染成原生view或交由Canvas繪製的過程。html
系列文章:java
GitHub地址:android
本文基於最新源碼分析。git
需求背景一文介紹了模塊化搭建頁面的由來,那有沒有想過這樣一種場景,有天產品靈光一閃,想要不發版把上圖下文
換成上文下圖
,又或者想要在每一個圖片右上角加個雙11大促角標來營造氛圍,因爲客戶端只預埋了上圖下文的樣式(如下簡稱cell
),即ImageTextView
,因此只能延期到下一班車,github
很顯然,即使咱們根據當下的業務抽象了一些經常使用的Cell
,好比上圖下文
、純文本
、單圖
等,並且還支持了一些通用的屬性配置如文本大小顏色等,也沒法知足多變的業務需求,也即cell
不夠用了,咱們要有線上生產cell
並下發的能力。因此,VirtualView
誕生了。json
VirtualView
的核心思想是,編寫xml
樣式文件,編譯壓縮成二進制文件,下發到客戶端,客戶端解析,轉成native view
,或者用canvas
繪製。引用官方的一張圖片,canvas
所以,當UI有細節變更時,只須要修改xml
,而後編譯好下發給客戶端替換便可。不過,咱們的生產環境用的是另一套基於flexbox-layout的方案而非VirtualView
,本文是站在學習的角度進行調研。數組
框架名字積木
和七巧板
,可見,類似的業務場景,衍生出了類似的技術方案。服務器
VirtualView
很讚的兩點是,他的二進制壓縮
和實時預覽
,接下來進行詳細分析。網絡
經過 XML 編寫的業務組件,若是直接加載解析,會有幾個問題:一是原始文件相對較大,由於 XML 裏會有冗餘信息,如空格、換行、還有重複出現的字符串等,文件體積比較大;二是解析 XML 會有必定開銷,相對於二進制數據直接解析,XML 解析會比較重,例如節點遍歷、屬性訪問等都顯得有些臃腫。經過提早將 XML 模板處理成二進制格式,能夠將繁重的解析工做從客戶端運行時中剝離出來,而經過將一些重複的資源作合併處理並創建索引,能夠減小冗餘信息,減小模板文件大小,一般狀況下,處理成二進制格式的模板比原始模板可減小 50% - 60% 的大小。
先來看一個簡單的xml
樣式文件,直接把他下發到客戶端存在兩個問題,一是冗餘字符引發的帶寬浪費,二是客戶端解析耗時和內存,在用戶手機內存吃緊時,面對一個樣式繁多的RecyclerView
時,即使存在複用機制也可能因解析引發oom(來自電商的痛),每每須要在編譯期就把xml
轉成view類,
<VHLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../vv.xsd" orientation="V" layoutWidth="match_parent" layoutHeight="200" background="#11000000"> <NText text="${text}" textSize="30" textColor="@{${items[0].info.textColor} ? ${items[1].subItems[0].info.textColor} : ${items[2].subItems[0].info.textColor}}" background="#fffff0" layoutWidth="match_parent" layoutHeight="200" gravity="h_center|v_center" /> </VHLayout>
連Android自帶的XmlPullParser
解析都足夠重了,那咱們能不能避開這個思路呢?來看看VirtualView
的思路,首先看到virtualview_tools工程,在virtualview_tools/compiler-tools/RealtimePreview/config.properties
文件中,
// 把內置支持的view映射成int,1000之內 VIEW_ID_FrameLayout=1 VIEW_ID_NImage=9 VIEW_ID_VImage=10 // 1000以上給外部自定義的view VIEW_ID_TotalContainer=1010 // 定義枚舉映射,即xml裏寫的row、row-reverse也會被轉成int flexDirection=Enum<row:0,row-reverse:1,column:2,column-reverse:3> orientation=Enum<H:1,V:0> // 定義一些屬性值的類型 borderWidth=Float itemWidth=Number
在進行類型的簡化後,約定一種數據格式,每一塊分別展現什麼信息,以下,
好比,開頭有版本區
,後面有組件區
、組件長度區
、字符串區
、字符串長度區
、表達式區
、表達式長度區
...這有點像JVM
校驗解析字節碼的過程。一些資源的映射處理,以下,
- 顏色:轉換成4字節整型顏色值,格式 AARRGGBB;
- 枚舉:按照預約義的整數轉換,好比 gravity 的類型,orientation 的類型;
- 字符串:以 hashCode 值做爲它的序列化後整數,並在字符串資源區創建以 hashCode 爲索引的列表,在解析的時候從中獲取原始的字符串值;
- 邏輯表達式:與字符串的處理相似;
- 數字:直接轉換成 4 字節的整型或者浮點型,並支持帶單位的類型;
字符串用hashCode值爲索引的列表方案,能夠節省重複字符串的空間,表達式是用來綁定動態數據如${text}
。
獲得二進制數據,
把二進制數據下發到客戶端,在Virtualview-Android工程中,能夠看到一個BinaryLoader
類,
//BinaryLoader.java //二進制數據,轉成byte數組進行讀取 public int loadFromBuffer(byte[] buf, boolean override) { CodeReader reader = new CodeReader(); reader.setCode(buf); reader.seekBy(Common.TAG.length()); // check version int majorVersion = reader.readShort(); //讀取主版本號 int minorVersion = reader.readShort(); //讀取副版本號 int patchVersion = reader.readShort(); //讀取修訂版本號 reader.setPatchVersion(patchVersion); int uiStartPos = reader.readInt(); //讀取UI開始位置 reader.seekBy(4); int strStartPos = reader.readInt(); //讀取字符串開始位置 reader.seekBy(4); int exprCodeStartPos = reader.readInt(); //讀取表達式開始位置 reader.seekBy(4); }
這樣,把xml
樣式文件壓縮成二進制文件,既節省了帶寬,又免去了客戶端比較重的XmlPullParser
解析,真是快樂Double~
VirtualView
翻譯成中文就是虛擬視圖,由於他裏邊有個虛擬控件的概念。能夠看到它裏邊有些控件有兩份,分別是V和N開頭的,如VImage
和NImage
、VText
和NText
,
V開頭指的是Virtual View
虛擬視圖,即不須要實際的ImageView
或TextView
,而是在一個Container
(如ViewGroup)內,直接拿他的畫布canvas
進行內容繪製,如drawText
或drawBitmap
等操做;
N開頭指的是Native View
即原生視圖,須要實際的ImageView
或TextView
來承載。
看下截圖更直觀,
Virtual View
:
Native View
:
虛擬視圖跟原生視圖相比會更輕量,固然具體還得結合業務使用,目前支持兩種視圖的混用,這樣就須要去避免一個問題,虛擬視圖畫在宿主上做爲」背景「,原生視圖放在宿主上有可能會遮擋虛擬視圖。
安裝fswatch
監聽文件修改,
brew install fswatch
安裝qrencode
生成二維碼(可選),
brew install qrencode
在virtualview_tools項目中virtualview_tools/compiler-tools/RealtimePreview
目錄下,執行./run.sh
啓動服務器,手機和電腦連同一網絡,手機運行Virtualview-Android項目(記得把HttpUtil
類中的ip地址改爲電腦的ip),進入模板實時預覽
,能夠加載服務器下發的HelloWorld
,點進去就能夠看樣式了,
接着修改文件保存,fswatch
監聽到修改,觸發服務器從新編譯HelloWorld
,
合併結果data.json
以下,
{ "templates": [ //樣式:xml -> 二進制 -> Base64.encode ,客戶端拿到後decode回二進制進行解析 "QUxJVlYAAQAAOMQAAAAvAAAAkAAAAL8AAAD1AAABuAAAAAAAAAG8AAAAAAABAAAAAAABAApIZWxsb1dvcmxkAH4AAAIEqjL10AAAAABc1fDxAAAAyLCYVS4RAAAAd3CsvP////8AAAACfREwBNF35jvOOvRwYx6r5gAAAAAHBcQtOs4AAAAUXNXw8QAAAMiwmFUu////8BC4ck4AAAAkd3CsvP////8AAAACADZFLUjWynnAmy42tiGl5gAAAQEAAAAGfREwBAAdeHNpOm5vTmFtZXNwYWNlU2NoZW1hTG9jYXRpb262IaXmAG9AeyR7aXRlbXNbMF0uaW5mby50ZXh0Q29sb3J9ID8gJHtpdGVtc1sxXS5zdWJJdGVtc1swXS5pbmZvLnRleHRDb2xvcn0gOiAke2l0ZW1zWzJdLnN1Ykl0ZW1zWzBdLmluZm8udGV4dENvbG9yfX1jHqvmAClodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZc469HAACXhtbG5zOnhzadF35jsADC4uLy4uL3Z2LnhzZEjWynkAByR7dGV4dH0AAAAA" ], "data": { //數據 "text": "Hello World!" } }
可見實時預覽時,服務端把二進制數據進行了Base64編碼(真實的業務場景也能夠參考),客戶端點擊Refresh
按鈕從新加載http://127.0.0.1:7788/helloworld/data.json
,在PreviewActivity
中,
//PreviewActivity.java //獲取網絡數據data.json PreviewData previewData = new Gson().fromJson(string, PreviewData.class); //取出templates字段 loadTemplates(previewData.templates); //進行Base64解碼,而後讀取二進制數據進行解析 sViewManager.loadBinBufferSync(Base64.decode(temp, Base64.DEFAULT));
在VirtualView
的加持下,Tangram
的動態能力獲得進一步提高,實現了線上生產cell
並下發替換。
Tangram
:
內部Lego
: