何为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
: