Categories
程式開發

Android | Tangram动态页面之路(七)硬核的Virtualview


何为Virtualview,简单来说,就是通过xml来描述视图,然后压缩成二进制格式,客户端通过解析并渲染成原生view或交由Canvas绘制的过程。

系列文章:

需求背景Tangram和vlayout介绍Tangram的使用vlayout原理Tangram原理json模板和数据分离

GitHub地址:

GitHub – Virtualview-AndroidGitHub – virtualview_tools

本文基于最新源码分析。

VirtualView

需求背景“一文介绍了模块化搭建页面的由来,那有没有想过这样一种场景,有天产品灵光一闪,想要不发版把上图下文换成上文下图,又或者想要在每个图片右上角加个双11大促角标来营造氛围,由于客户端只预埋了上图下文的样式(以下简称cell),即ImageTextView,所以只能延期到下一班车,

Android | Tangram动态页面之路(七)硬核的Virtualview 1

很显然,即便我们根据当下的业务抽象了一些常用的Cell,比如上图下文、纯文本、单图等,而且还支持了一些通用的属性配置如文本大小颜色等,也无法满足多变的业务需求,也即cell不够用了,我们要有线上生产cell并下发的能力。所以,VirtualView诞生了。

VirtualView的核心思想是,编写xml样式文件,编译压缩成二进制文件,下发到客户端,客户端解析,转成native view,或者用canvas绘制。引用官方的一张图片,

Android | Tangram动态页面之路(七)硬核的Virtualview 2

因此,当UI有细节变动时,只需要修改xml,然后编译好下发给客户端替换即可。不过,我们的生产环境用的是另外一套基于flexbox-layout“的方案而非VirtualView,本文是站在学习的角度进行调研。

Android | Tangram动态页面之路(七)硬核的Virtualview 3

框架名字积木和七巧板,可见,相似的业务场景,衍生出了相似的技术方案。

Android | Tangram动态页面之路(七)硬核的Virtualview 4

VirtualView很赞的两点是,他的二进制压缩和实时预览,接下来进行详细分析。

二进制压缩

通过 XML 编写的业务组件,如果直接加载解析,会有几个问题:一是原始文件相对较大,因为 XML 里会有冗余信息,如空格、换行、还有重复出现的字符串等,文件体积比较大;二是解析 XML 会有一定开销,相对于二进制数据直接解析,XML 解析会比较重,例如节点遍历、属性访问等都显得有些臃肿。通过提前将 XML 模板处理成二进制格式,可以将繁重的解析工作从客户端运行时中剥离出来,而通过将一些重复的资源做合并处理并建立索引,可以减少冗余信息,减少模板文件大小,通常情况下,处理成二进制格式的模板比原始模板可减少 50% – 60% 的大小。引用自苹果核 – VirtualView Android实现详解(一)—— 文件格式与模板编译

先来看一个简单的xml样式文件,直接把他下发到客户端存在两个问题,一是冗余字符引起的带宽浪费,二是客户端解析耗时和内存,在用户手机内存吃紧时,面对一个样式繁多的RecyclerView时,即便存在复用机制也可能因解析引起oom(来自电商的痛),往往需要在编译期就把xml转成view类,

连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
orientation=Enum

// 定义一些属性值的类型
borderWidth=Float
itemWidth=Number

在进行类型的简化后,约定一种数据格式,每一块分别展示什么信息,如下,

Android | Tangram动态页面之路(七)硬核的Virtualview 5

比如,开头有版本区,后面有组件区、组件长度区、字符串区、字符串长度区、表达式区、表达式长度区…这有点像JVM校验解析字节码的过程。一些资源的映射处理,如下,

– 颜色:转换成4字节整型颜色值,格式 AARRGGBB;- 枚举:按照预定义的整数转换,比如 gravity 的类型,orientation 的类型;- 字符串:以 hashCode 值作为它的序列化后整数,并在字符串资源区建立以 hashCode 为索引的列表,在解析的时候从中获取原始的字符串值;- 逻辑表达式:与字符串的处理类似;- 数字:直接转换成 4 字节的整型或者浮点型,并支持带单位的类型;引用自苹果核 – VirtualView Android实现详解(一)—— 文件格式与模板编译

字符串用hashCode值为索引的列表方案,可以节省重复字符串的空间,表达式是用来绑定动态数据如${text}。

得到二进制数据,

Android | Tangram动态页面之路(七)硬核的Virtualview 6

把二进制数据下发到客户端,在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~

Android | Tangram动态页面之路(七)硬核的Virtualview 7

原生控件和虚拟控件

VirtualView翻译成中文就是虚拟视图,因为他里边有个虚拟控件的概念。可以看到它里边有些控件有两份,分别是V和N开头的,如VImage和NImage、VText和NText,

V开头指的是Virtual View虚拟视图,即不需要实际的ImageView或TextView,而是在一个Container(如ViewGroup)内,直接拿他的画布canvas进行内容绘制,如drawText或drawBitmap等操作;

N开头指的是Native View即原生视图,需要实际的ImageView或TextView来承载。

看下截图更直观,

Virtual View:

Android | Tangram动态页面之路(七)硬核的Virtualview 8

Native View:

Android | Tangram动态页面之路(七)硬核的Virtualview 9

虚拟视图跟原生视图相比会更轻量,当然具体还得结合业务使用,目前支持两种视图的混用,这样就需要去避免一个问题,虚拟视图画在宿主上作为”背景“,原生视图放在宿主上有可能会遮挡虚拟视图。

实时预览

安装fswatch监听文件修改,

brew install fswatch

安装qrencode生成二维码(可选),

brew install qrencode

virtualview_tools项目“中virtualview_tools/compiler-tools/RealtimePreview目录下,执行./run.sh启动服务器,手机和电脑连同一网络,手机运行[Virtualview-Android项目](https://github.com/alibaba/Virtualview-Android)(记得把HttpUtil类中的ip地址改成电脑的ip),进入模板实时预览,可以加载服务器下发的HelloWorld,点进去就可以看样式了,

接着修改文件保存,fswatch监听到修改,触发服务器重新编译HelloWorld,

Android | Tangram动态页面之路(七)硬核的Virtualview 10

合并结果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:

官方show-case“、

Android | Tangram动态页面之路(七)硬核的Virtualview 11

内部Lego:

Android | Tangram动态页面之路(七)硬核的Virtualview 12

参考文章

苹果核 – 天猫客户端组件动态化的方案——VirtualView 上手体验苹果核 – VirtualView 工具大更新啦苹果核 – 提升开发体验,预览 VirtualView苹果核 – VirtualView Android 实现详解(三)—— 添加一个自定义控件文档 – Virtualview

Android | Tangram动态页面之路(七)硬核的Virtualview 13