前言

  • 淘宝、天猫一直致力于解决 页面动态化的问题

  • 在2017年的4月发布了v1.0解决方案:Tangram模型 及其对应的 Android库 vlayout,该解决方案在手机淘宝、天猫 Android版 内广泛使用

电商图

  • 在同年的12月,阿里团队对此作了重大更新:发布了 Tangram2.0 版本,主要是补充了 Android 库 VirtualView,也广泛应用于淘宝、天猫客户端

    示意图

  • 今天,我将带大家全面了解Tangram 2.0版本的新成员:Virtualview

Virtualview的Github地址:

https://github.com/alibaba/Virtualview-Android


目录

示意图


1. 为什么要向 Tangram模型 加入 VirtualView

即 为什么要更新 Tangram2.0版本

  • 结论

  1. 提升组件动态性,实现动态更新

  2. 提升了组件的渲染性能

  • 具体描述

    示意图

而上述解决方案的承载方案,则是 VirtualView

VirtualView的Github地址:

https://github.com/alibaba/Virtualview-Android


2. VirtualView介绍

  • 简介

    示意图

  • 特点

示意图


3. 实现原理

3.1 核心思路

根据Tangram v1.0中 出现的问题:UI组件无法动态更新 & 加载性能低,VirtualView的具体解决方案如下

示意图

3.2 实现方案

  • 根据其原理,VirtualView的实现方案是:虚拟化开发

  • 虚拟化开发的本质:

示意图

之所以称为虚拟化,是因为Canvas绘制的视图不存在一一对应的实体View

3.3 总结

从上可知,VirtualView的创新在于:

  1. 通过 XML 模板实现组件的动态性

  2. 通过 虚拟化技术(本质 = Canvas)开发组件,提升了组件的渲染性能


4. 工作流程

  • 在了解了VirtualView的本质原理 & 整体架构后

  • 下面,我将开始讲解VirtualView的工作流程

4.1 流程概述

  • 根据上述方案,VirtualView的工作流程分为3大部分:创建UI组件、创建界面模板 & 客户端加载界面

  • 具体如下

示意图

4.2 流程详细分析

下面我将对每个流程的原理 & 过程详细分析

流程1:创建UI组件

  • 具体描述

    根据业务需求,创建所需要的UI组件

示意图

有2种创建方式:使用框架内置(封装好)的UI组件 / 自定义

1.1 使用框架内置(封装好)的UI组件

  • 即 可直接使用封装好的UI组件而不需自身创建

  • 具体如下(含组件基础属性)

注:

a. 自定义组件应继承基础组件

b. 系统封装UI组件的原理 同 “自定义UI组件,下面将具体讲解

示意图

1.2 自定义UI组件

若框架内置的UI组件无法满足需求,则开发者可自定义UI组件

  • 自定义流程

    VirtualView抽象 & 封装了 Canvas绘制视图的流程,使得开发者只需按指定的接口协议实现1个组件的绘制逻辑:测量、绘制 & 绘制,即能实现在宿主容器通过 Canvas 直接绘制 UI内容,从而创建虚拟化组件

即 上述则是虚拟化创建组件的过程

  • 具体过程

  1. 实现基础组件需遵循一个接口的规范:定义了渲染过程中所需的3个流程:测量尺寸阶段、布局阶段 & 绘制阶段

a. 定义这3个阶段是为了符合Android系统的使用,即View绘制的三大流程:measure过程、layout过程、draw过程。若不了解,请看文章

(2)自定义View Measure过程 - 最易懂的自定义View原理系列

(3)自定义View Layout过程 - 最易懂的自定义View原理系列

(4)自定义View Draw过程- 最易懂的自定义View原理系列

b. 在 iOS 平台下也需按照本方案的规范去处理

  1. 这3个过程具体如下:(与Android View绘制的三大流程相似)

    示意图

不论是虚拟 / 原生组件,都采用上述模型 & 流程定义

a. 对于虚拟组件:在这些接口里实现相关逻辑 / 通过封装原生组件实现

b. 对于原生组件:在这些接口的实现里 调用原生组件的对应逻辑

结论:可混合使用虚拟控件 & 实体控件

至此,对于宿主的布局容器来说,包装在内部的组件不分虚拟化 /

原生,暴露在外的接口相同,只要将宿主容器像普通的 View 一样添加到的视图界面上,就可在后续的渲染过程中显示出来。

  • 特别注意

    此处即可解释 为何渲染性能高:因虚拟组件使用得越多,View个数就越少,即层级越扁平

如下所示的组件:

a. 普通的原生开发:2层(宿主容器层 + 图片组件层)

b. 虚拟化开发:采用虚拟化开发后,最终呈现的 View层级只有一个宿主容器(实际上,图片组件被绘制在Canvas里了)

示意图

1.3 总结

创建UI组件有2种方式:

  1. 直接使用框架内置的UI组件

  2. 自定义组件:通过封装好的Canvas流程,按照指定接口协议实现绘制逻辑 / 封装原生组件


流程2:创建界面模板 & 下发

  • 该步骤包括多个步骤:创建XML界面模板、编译成二进制数据、下发等

  • 具体如下

示意图

2.1 创建XML界面模板

  • 具体描述

    根据业务需求,使用XML编写模板

注:需使用专门的工具virtualview_tools编写,其

使用说明见文章virtualview_tools使用指南

  • 此方式类似:Android 平台上通过 XML 搭建界面的方式

  • 区别在于:

    1. 脱离了平台限制,即一套模板可同时在AndroidiOS上使用

    2. 运行时动态加载 XML 模板数据,动态更新界面结构

// 引用的组件通过流程1中获取

// 动态数据通过表达式从 JSON 数据里获取

"1.0" encoding="utf-8"?>

<vhlayout

       flag="flag_exposure|flag_clickable"

       orientation="H"

       layoutWidth="match_parent"

       layoutHeight="wrap_content">

   <nimage

           id="1"

           src="${logoUrl}"

           layoutMarginLeft="8"

           layoutMarginRight="8"

           layoutMarginTop="8"

           layoutMarginBottom="8"

           layoutWidth="32"

           layoutHeight="32"/>

   <ntext

           id="2"

           text="${title}"

           layoutGravity="v_center"

           gravity="${style.text-align}"

           textSize="${style.font-size}"

           textColor="${style.color}"

           layoutWidth="match_parent"

           layoutHeight="wrap_content"/>

// JSON数据

{  "style": {    "text-align": "h_center",    "font-size": "20",    "color": "#FF5000"

 },  "title": "超高性 99.9% 的用户觉得很快",  "logoUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1xaxxbsxpxa-72-72.png"

2.2 编译成二进制数据

2.2.1 具体描述

使用专门的工具virtualview_tools将编写好的XML界面模板编译成二进制数据,编译后的文件的后缀名是.out

示意图

使用说明见文章virtualview_tools使用指南

注:为什么通过 XML 编写的业务组件 不直接在客户端里运行使用,而是先进行一次二进制序列化操作?

示意图

2.2.2 二进制文件描述

借鉴了 Android 系统编译模板文件的思路,格式 & 描述具体如下

示意图

2.2.2 编译流程

  • 一个业务组件对应着一份 XML 模板 = 单独编译成二进制数据

编译数据 含除内置字符串资源外 它依赖的所有字符串、表达式资源

  • 编译规则

    编译时,模板里涉及的资源包括颜色值、各种枚举、基础组件的类型等都会被序列化映射成整数;不能序列化成整数的资源如字符串,就分配一个索引 Id 指向它 & 将它们单独存储到一块区域里

  1. 原因:当模板在线发布、字符串有变动的情况下,能够不影响原来的字符串资源索引;否则若按照带有顺序约定的协议来分配资源索引,很容易在模板变更时 同一索引值在变更前后指向的资源内容是不一样,影响稳定性和动态性

  2. 序列化的规则如下:

示意图

  • 编译流程

示意图

2.3 模板数据 下发到客户端

即 客户端获取编译后的二进制数据

获取有2种路径:

  1. 直接将编译后的模板打包到客户端里,开发者通过代码加载

  2. 框架先发布到模板管理后台,客户端在线更新到模板数据(即实现了动

    态更新)


流程3:客户端加载界面

  • 客户端获取到编译后的界面模板后,进行加载 & 解析,最终渲染出视图界面

  • 步骤流程如下图

示意图

3.1 解析模板数据

  • 具体描述

    客户端获得编译后的模板数据(二进制数据)后,立即 进行解析

  1. 如校验版本号,合法性,读取头信息等

  2. 客户端渲染组件 从解析 编译后的模板数据开始

  • 流程解析

    解析过程 = 二进制编译的逆过程

但解析流程只负责提取原始数据 & 组织格式,并无构建出组件对象

示意图

3.2 加载组件视图

  • 具体描述

    当用户传入一个模板名称,框架内部就会根据名称去之前解析XML界面模板的数据里找到 与此名称匹配的模板数据,然后加载 & 创建出真正的组件

  • 流程解析

示意图

3.3 绑定业务数据

  • 具体描述

    开发者在组件属性里可通过 表达式 指定使用哪个数据字段,即将业务数据绑定到组件上

因业务数据是动态的,故从模板创建的组件不含业务数据

  • 流程解析

    在创建组件的过程中,当解析属性碰到表达式时,会将该属性的key、表达式值、所属的基础组件等关系存储起来,等真实数据到达后再通过 表达式里的定义 访问数据 & 将真实值设置给组件的属性,即将真实的数据绑定到基础组件的属性上

  1. 通过表达式解析、访问得到的属性值,会缓存起来,当原始数据引用不变时,每次访问都会获取到缓存值

  2. 此处接收的数据是 JSON 格式

示意图

4.3 总结

示意图


5. 整体架构设计

  • 根据上述方案 & 工作流程,VirtualView的整体框架分为2部分:核心功能模块(5个模块) + 配套工具 & 服务。具体如下:

示意图

  • 下面,我将对每部分进行详细分析

模块1:加载模块

  • 示意图

    示意图

  • 说明

    示意图

模块2:构造模块

  • 示意图

    示意图

  • 说明

    示意图

此处详细分析 基础组件模型 & 虚拟组件

a. 基础组件模型

含基础组件 & 基础属性,具体如下

注:自定义的基础组件应继承基础定义 & 扩展

示意图

模块3:辅助模块

  • 示意图

    示意图

  • 说明

    示意图

  • 特别注意:引入用户数据绑定的表达式的原因

    开发业务组件时,基础属性 / 样式不能在模板里直接写死,而是需从数据里动态获取

/**

 * 访问数据属性的表达式

 * 语法说明

 *       a. 以 “${” 开头、以 “}” 结束

 *       b. 对于Map,通过“.”操作符访问

 *       c. 对于 Array  /  List,通过 “[]” 操作符访问

 * 示例如下

 */  ${benefitImgUrl};  ${data[0].benefitImgUrl};

/**

 * 条件表达式

 * 作用:根据数据中某个字段 来设置值的属性

 * 语法说明

 *       a. 以 “@{” 开头、以 “}” 结束,

 *       b. 中间部分 = 表达式的具体内容:  条件表达式 ? 结果表达式[1] : 结果表达式[2]

 *           注:1. 当条件表达式成立的时,使用结果表达式[1],否则使用结果表达式[2]

 *              2. 条件表达式支持布尔类型、字符串类型、JSONObject、JSONArray

 *       c. 对于 Array  /  List,通过 “[]” 操作符访问

 * 示例如下

 */

 @{${logoUrl} ? visible : invisible };

模块4:管理模块

  • 示意图

    示意图

  • 说明

    示意图

模块5:更新模块

  • 示意图

    示意图

  • 说明

    示意图

配套使用的工具 & 服务

  • 示意图

    示意图

  • 说明

    示意图

总结

示意图


6. 使用教程

  • 根据上述工作流程,其使用流程同样分为3步:创建UI组件、创建界面模板 & 客户端加载界面

  • 下面,我将根据上述3个步骤进行详细解析

6.1 创建UI组件

从一文可知,创建UI组件有2种方式:

  1. 直接使用框架内置的UI组件

  2. 自定义组件:通过封装好的Canvas流程,按照指定接口协议实现绘制逻辑 / 封装原生组件

此处为方便讲解,直接使用框架内置的UI组件

6.2 创建界面模板

此步骤包括:创建XML界面模板、编译成二进制数据、模板下发

6.2.1 创建XML界面模板

根据业务需求,使用XML编写模板

注:需使用专门的工具virtualview_tools编写,其

使用说明见文章virtualview_tools使用指南

  • 示例布局

/**

 * 使用说明:

 *     1. 控件引用:通过XML引用控件为方便讲解,XML内引用的VHLayout、NImage、NText 都是框架内置的控件:2个横向线性布局;每个布局 = 1个图 + 1个文本

 *     2. 属性设置:可写死 / 通过表达式绑定一个数据字段(JSON)引用

 * 布局说明:

 *     1. 引用的控件VHLayout、NImage、NText等都是框架内置的控件

 *     2. 整个布局 = 2个横向线性布局,每个布局 = 1个图 + 1个文本

 */xml version="1.0" encoding="utf-8"?><VHLayout

       flag="flag_exposure|flag_clickable"

       orientation="V"

       layoutWidth="match_parent"

       layoutHeight="wrap_content">

   <VHLayout

           flag="flag_exposure|flag_clickable"

           orientation="H"

           layoutWidth="match_parent"

           layoutHeight="wrap_content">

       <NImage

               id="1"

               src="${logoUrl}"

               layoutMarginLeft="8"

               layoutMarginRight="8"

               layoutMarginTop="8"

               layoutMarginBottom="8"

               layoutWidth="32"

               layoutHeight="32"/>

       <NText

               id="2"

               text="${title}"

               layoutGravity="v_center"

               gravity="${style.text-align}"

               textSize="${style.font-size}"

               textColor="${style.color}"

               layoutWidth="match_parent"

               layoutHeight="wrap_content"/>

   VHLayout>

   <VHLayout

           flag="flag_exposure|flag_clickable"

           orientation="H"

           layoutWidth="match_parent"

           layoutHeight="wrap_content">

       <VImage

               id="1"

               src="${logoUrl}"

               layoutMarginLeft="8"

               layoutMarginRight="8"

               layoutMarginTop="8"

               layoutMarginBottom="8"

               layoutWidth="32"

               layoutHeight="32"/>

       <VText

               id="2"

               text="${title}"

               layoutGravity="v_center"

               gravity="${style.text-align}"

               textSize="${style.font-size}"

               textColor="${style.color}"

               layoutWidth="match_parent"

               layoutHeight="wrap_content"/>

   VHLayout>VHLayout>
  • 属性数据来源:JSON

{  "style": {    "text-align": "h_center",    "font-size": "20",    "color": "#FF5000"

 },  "title": "超高性 99.9% 的用户觉得很快",  "logoUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png"}

6.2.2 编译成二进制数据

使用专门的工具virtualview_tools将编写好的XML界面模板编译成二进制数据,编译后的文件的后缀名是.out

示意图

使用说明见文章virtualview_tools使用指南

6.2.3 模板下发到客户端

有2种路径:

  1. 直接将编译后的模板打包到客户端里,开发者通过代码加载

  2. 框架先发布到模板管理后台,客户端在线更新到模板数据(即实现了动态更新)

6.3 客户端解析 & 加载界面模板

具体使用如下

// 1. 初始化图片加载器

   VafContext.loadImageLoader(mContext.getApplicationContext());// 2. 初始化 ViewManager 对象

   ViewManager viewManager = vafContext.getViewManager();

   viewManager.init(mContext.getApplicationContext());// 3. 加载编译后的模板数据(二进制文件)

    // 方式1:直接加载二进制字节数组(推荐使用)

    viewManager.loadBinBufferSync(TMALLCOMPONENT1.BIN);

    viewManager.loadBinBufferSync(TMALLCOMPONENT2.BIN);     // 方式2:通过二进制文件路径加载

    viewManager.loadBinFileSync(TMALLCOMPONENT1_PATH);

    viewManager.loadBinFileSync(TMALLCOMPONENT2_PATH);// 4. 注册事件处理器,如常用的点击、曝光处理

   vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {        @Override

       publicbooleanprocess(EventData data){            //handle here

           returntrue;

       }

   });

   vafContext.getEventManager().register(EventManager.TYPE_Exposure, new IEventProcessor() {        @Override

       publicbooleanprocess(EventData data){            //handle here

           returntrue;

       }

   });// 5. 通过组件名参数 name 生成组件实例

   View container = vafContext.getContainerService().getContainer(name, true);

   mLinearLayout.addView(container);// 6. 为组件绑定真实的数据

   // 假若您在组件模板里写了数据绑定的表达式

   IContainer iContainer = (IContainer)container;

   JSONObject json = getJSONDataFromAsset(data);    if (json != null) {

       iContainer.getVirtualView().setVData(json);

   }
  • 测试结果

下图展示的“超高性 99.9% 的用户觉得很快”即为VirtualView的展示效果

示意图

至此,关于VirtualView的使用讲解完毕

更加详细使用,请参考文章VirtualView的使用文档


7.VirtualView 的意义

对于个人的看法,VirtualView的补充其重大意义在于2个方面:对于 阿里Tangram 模型 & 整个原生开发技术(Android、iOS)

7.1 对于 Tangram 模型

VirtualView的解决的问题 在于:

  1. 实现组件的动态性:可在端上绑定动态下发的 XML 界面模板 & 数据

  2. 提升了组件的渲染性能:通过 虚拟化技术(本质 = Canvas)开发组件

7.2 对于整个原生开发技术(Android、iOS)

VirtualView的创新在于:解决了 原生开发中一直被诟病 而 常被叫喧会被 前端、RN技术取代的问题:

  1. 开发周期长  & 成本大

    VirtualView 采用XML描述视图,XML界面模板具备跨平台使用的特性

  2. 无法热更新

    VirtualView可在端上绑定动态下发的 XML 界面模板 & 数据,从而实现热更新

  • 相比于前几年产品开发的一味求快,如今互联网行业发展暂缓、用户需求基本满足的情况下,更加 讲求的是用户体验

  • 所以,实际上对比于 前端、RN技术在客户端的实现,VirtualView的优势或许会更明显:在解决了原生开发效率慢、周期长的前提下,保证了原生开发的优势:性能好

7.3 呼吁

  • 虽然VirtualView 推动了原生开发的发展,但目前来说,VirtualView 还是存在不少问题

  • 希望大家能一起在Github - alibaba - VirtualView 上进行完善,共同为开源事业做贡献吧!

8. 总结

  • 看完本文,你应该非常了解阿里出品的VirtualView 的使用 & 原理

编辑:千锋UI设计

来源:简书

链接:https://www.jianshu.com/p/5bd7a210b800

最新文章

  1. 19.dnw打不开
  2. tcl学习
  3. 【DIOCP知识库】连接上下文TIocpClientContext
  4. jenkins2 groovy语法
  5. NoSQL系列:选择合适的数据库
  6. BAT文件执行完成后如何删除自身的解决办法
  7. 四则运算&lt;3&gt;
  8. mysql无法插入中文字符解决
  9. 提供给开发者 10 款最好的 Python IDE
  10. iOS中浅淡UIApplication单例-b
  11. Oracle Client: TNS: Connect timeout ocurred.
  12. flask+gevent+gunicorn+nginx 初试
  13. VBS基础篇 - 对象(3) - FileSystemObject对象
  14. uva1354 枚举二叉树
  15. centes7安装wdcp
  16. CentOS 与Ubuntu 下配置IP地址
  17. 设置 img 在 div 中水平居中和垂直居中
  18. ubuntu 安装NVIDIA驱动过程
  19. 这五件事,二次SaaS创业的老炮儿都在做(转)
  20. redis常用性能分析命令

热门文章

  1. DOS 批处理命令For循环命令详解
  2. js数组的初始化
  3. 转载 深入理解java类加载器
  4. 【转】ECharts3.x中的点击事件与行为
  5. 给Jquery easyui 的datagrid 每行增加操作链接(转)
  6. redis该怎么用
  7. myeclipse 代码提示
  8. php缓存类
  9. as3 加载库声音报错
  10. 9 random模块