Tangram 2.0——猫客页面内组件的动态化方案

2.0 库iOS背景 技术背景
一直以来 , 无线应用都在不断寻求动态化页面的解决方案,在阿里巴巴集团内,除了风风火火地 Weex 项目外,各个团队都有大大小小的解决方案 。我们猫客一直持续基于方案来解决页面动态化的问题,然而在面对持续升级的业务需求时 , 原有的开发模式也慢慢变得无法胜任 , 本年度以来,我们体系在各个层面都进行了大跨度的技术升级(可参考文章天猫APP改版之首页架构&开发模式全面升级),本文再详细介绍一下页面内组件体系升级方案 。
老组件体系的问题
在原有的体系里 , 主要解决了页面内布局结构的动态化能力,通过 json 数据描述可以组合出常用的页面结构 。然而页面内具体的坑位样式,我们称之为业务组件,是采用常规的代码开发的,除非内置了足够多的逻辑 , 否则组件的样式调整或者新组件的开发都要发布版本,无法满足业务节奏;当然我们也尝试过使用 Weex 开发业务组件贴到页面上,但是在体验和性能上还是有较大的缺陷 。
所以总结起来,就是两点问题:
业务组件无法动态更新;现有的动态组件方案较重,影响性能和体验; 解决之道
对于上述问题,解决思路其实是比较通用的,要动态更新界面视图,就需要用界面模板描述视图,模板与数据分离 。将动态下发的模板和数据在端上绑定渲染 。要提升性能,也有三大着力点——减少视图层级与个数 , 结构尽量扁平化;异步布局渲染流程,解放主线程计算量;回收与复用组件,减少内存开销 。
新的组件体系就是在模板化描述视图,动态更新视图,减少视图层级几个方面做文章 , 至于组件的回收复用,则是在页面级别统一完成;而异步布局渲染流程,则是后续的优化方向 。
新的组件方案称之为 ,简称 VV,也称为2.0组件,它的设计遵循以下几个思路:
以了一种虚拟化开发基础组件的技术,使用方只要按照指定协议实现一个基础组件的尺寸计算、绘制逻辑、布局逻辑,即能实现在宿主容器的里实现直接绘制 UI 内容的,让最终渲染出来的视图结构呈现扁平化,提升组件渲染性能 。同时为了解决虚拟化 View 带来的原生 View 的能力损失的问题,它支持加载和渲染原生基础组件,两者组合产生合力 , 既能减少开销,又能满足特殊场景下的业务需求 。内置实现了一系列基础组件,可以让使用方直接上手尝试;而搭建业务组件的方式采用 XML 模板来编写 , 配套 XML 模板更新 sdk,这使得业务组件动态更新成为了可能 。XML 模板里还支持写数据绑定的表达式,在样式动态化、数据动态化的场景下能非常方便地实现业务需求 。XML 模板里涉及到的基础节点、属性、字符串资源等都被提前编译成二进制资源,客户端加载通过加载编译后的模板数据来创建视图 。设计方案 整体架构
先从整体上预览一下整个方案的大体结构:

Tangram 2.0——猫客页面内组件的动态化方案

文章插图
自下往上 , 自左往右的顺序介绍各个模块:
运行流程
有了上述基础,当我们要开发新的业务组件的时候,除了有新增逻辑的需求场景(比如新增视频功能),大部分需求都可以告别原生代码的编写,转而编写组件模板 。
Tangram 2.0——猫客页面内组件的动态化方案

文章插图
先编写业务组件的模板 。通过工具将模板数据编译成二进制数据 。客户端加载二进制数据可以有两种路径,一是直接打包到客户端里,写代码加载,另一种是发布到模板管理后台 , 客户端在线更新到模板数据 。不论哪种方式加载二进制数据,客户端接下来的工作是解析二进制数据里,比如校验版本号 , 合法性,读取头信息等等 。等要真正创建组件的时候,根据组件名称找到二进制数据 , 从中解析并创建出真正的组件模型数据 。从模板里创建在组件往往不含有业务数据,因为业务数据是动态性的,用户需要获取到业务数据绑定到组件上,组件的属性里可以写表达式来指定使用哪一个数据字段 。
值得注意的是,在上述架构及流程里,描述了一个完整的实践经验 , 但对于本方案来说 , 核心点在于提供了对组件从编写到展示流程的实现,其周边的配套设施,并没有内置在框架里 , 包括客户端上的模板管理、更新、注册模块,以及后端的模板发布服务,因为这些模块往往涉及业务逻辑,且与各个应用的基础设施相关,内置在框架里反而限制了使用方的接入 。这里提供一些可供参考的经验:
模板管理后台要能对模板的进行发布、更新,并且按照客户端版本、平台、组件版本、生效优先级等几个维度来管理模板;模板文件可以存放到 CDN 上供客户端下载 , 管理平台只是对比下发远信息;下载文件要做足够的校验;客户端要内置一份打底的模板数据,这样不至于因为模板不存在而出现空窗;客户端可提供一个统一的模板管理模块,面向全应用提供服务,在合适的时候请求管理平台检查有没有更新,比如启动、用户刷新、推送指令的到达 , 并且负责下载、文件校验、通知页面刷新等功能;页面刷新可以做优先级区分,比如高优先级的模板更新主动去刷新下页面,而低优先级的可以等二次进入页面或者刷新页面的时候生效; 几个核心设计 组件的基础模型
对于组件,我们做了如下定义,每一个基础的原子组件或者容器组件都会有以下属性,自定义的基础组件应当继承自基础定义并做扩展 。
名称类型默认值描述
id
int
组件id
int/float/enum(/)
组件的布局宽度,与里的概念类似,写绝对值的时候表示绝对宽高,表示尽可能撑满父容器提供的宽高,表示根据自身内容的宽高来布局
int/float/enum(/)
组件的布局宽度,与里的概念类似,写绝对值的时候表示绝对宽高,表示尽可能撑满父容器提供的宽高,表示根据自身内容的宽高来布局
enum(left/right/top///)
left|top
描述组件在容器中的对齐方式,left:靠左,right:靠右 , top:靠上,:靠底,:垂直方向居中 , :水平方向居中,可用或组合描述
int/float
组件宽高比计算的横向值
int/float
组件宽高比计算的竖向值
enum(X/Y/NONE)
NONE
组件在布局中的基准方向,用于计算组件的宽高比,与、配合使用,设置了这三个属性时,在计算组件尺寸时具有更高的优先级 。当=X时,组件的宽度由和父容器决策决定,但高度 = width * ( / ),当=Y时,组件的高度由和父容器决策决定,但宽度 =* ( / )
int/float
最小宽度
Tangram 2.0——猫客页面内组件的动态化方案

文章插图
int/float
最小高度
int/float
左内边距
int/float
右内边距
int/float
上内边距
int/float
下内边距
int/float
左外边距
int/float
右外边距
int/float
上外边距
int/float
下外边距
int
背景色
null
背景图地址
int
边框宽度
int
边框颜色
enum(//gone)
可见性,与里的概念类似,:可见 , :不可见 , 但占位,gone:不可见也不占位
enum(left/right/top///)
left|top
Tangram 2.0——猫客页面内组件的动态化方案

文章插图
描述内容的对齐,比如文字在文本组件里的位置、原子组件在容器里的位置,left:靠左,right:靠右,top:靠上,:靠底 , :垂直方向居中,:水平方向居中,可用或组合描述
方案内内置了一系列基础组件,完整的组件列表如下:
虚拟组件
上文提到虚拟化开发的组件的技术,简称虚拟组件 。很多做性能优化的方案、建议都会提到采用直接绘制的方式来减少 View 的个数,虚拟将这个开发流程做了抽象与规范,可以让开发人员像定义原生组件一样定义虚拟组件 。
具体来讲,基础组件需要遵循一个接口的规范 , 这个口定义了渲染过程中需要的三个流程:计算尺寸阶段、布局阶段、绘制阶段;定义这个三个阶段是为了更好的与系统平台特别是平台对接,因为在原生平台下也会有这个三个阶段,在 iOS 平台下则也需要按照本方案里要求的规范去处理 。计算尺寸阶段定义要触发一次尺寸计算,需要对其包含的子组件进行计算调用;布局阶段定义了要触发一次布局 , 将子元素按照计算好的位置尺寸排布,也要对包含的子组件进行布局调用;绘制阶段定义要进行视图绘制,当然也要对起包含的子组件进行绘制的调用;对于虚拟组件,就在这些接口里实现相关逻辑,而对于原生组件,在这些接口实现里调用原生组件的对应逻辑 。
不论是虚拟化组件还是原生组件,都采用上述相同的模型来定义,再加上相同的尺寸计算接口、布局接口、绘制接口,这样对于宿主容器来说,包装在内部的组件就不分虚拟化还是原生 , 一视同仁 , 暴露给外面的接口也是一样的,只要将宿主容器像普通的 View 一样添加到的视图界面上,就可以在后续的渲染过程中显示出来 。如果虚拟组件使用的越多,View 的个数就越少,对于系统来说层级越扁平 。以下图示例的组件来说,最终呈现的 View 只有宿主容器和两个图片组件,如果将图片也用虚拟化的方式实现,最终 View 只有一个宿主容器,而界面仍然保持不变 。
Tangram 2.0——猫客页面内组件的动态化方案

文章插图
二进制文件的格式
通过 XML 编写的业务组件,并不直接在客户端里运行使用,而是先进行一次二进制序列化操作 , 原始的 XML 模板文件保存成文件的时候,就是以纯文本的形式存在,会包含很多冗余信息,比如空格、换行、还有重复出现的字符串等,文件体积比较大,以xml解析器去解析的时候 , 也会需要大量字符串操作,效率和性能不能达到最优 。而将它编译成二进制格式,会避免这些问题,比如文件重复出现的字符串只保留一份,通过字符串索引去引用它,所有的组件类型也都会被转换成一个数字索引,在客户端内通过数字索引反过来找到对应的类实例化 。这样文件格式会非常紧凑,体积更小 。整个设计也借鉴了系统编译模板文件的思路 。它的具体格式说明如下:
Tangram 2.0——猫客页面内组件的动态化方案

文章插图
按照图中从左往右、从上往下的顺序分别说明每个段的作用:
绑定数据的表达式
开发业务组件的时候,基础属性或者样式往往不能在模板里直接写死,而是需要从数据里获取,所以引入了用户数据绑定的表达式,语法和实现上目前比较简单,参考了很多同类的设计,尽可能符合开发人员的直觉 。
语法上以 ${ 开头,以 } 结束 。对于Map,通过 . 操作符进行访问,对于 Array 或者 List 通过 [] 操作符进行访问 。
比如:
${benefitImgUrl}${data[0].benefitImgUrl}
用来给那些需要根据数据中某个字段来设置值的属性 , 语法上以 @{ 开头,以 } 结束,中间部分为表达式的具体内容 。
条件表达式 ? 结果表达式[1] : 结果表达式[2]
当条件表达式成立的时候,使用结果表达式[1],否则使用结果表达式[2] 。其中: 条件表达式支持布尔类型、字符串类型、、 。以下场景均为 false:
比如:
@{${logoUrl} ? visible : invisible }
考虑到篇幅限制 , 不能将上述架构和流程中的每一细节完全展开,详情可以参考苹果核这里的文档 。
效果 与及 TAC 结合
方案是的极大补充,可以解决80%场景下的动态化需求,而依赖的数据则通过 TAC 提供解决,三者结合可以形成一个闭环,让一个开发从端到端地解决整块业务的开发 。
组件动态下发
以双十一期间为例,90%的双十一业务组件都是动态下发的 , 且随时可根据业务节奏调整 。
Tangram 2.0——猫客页面内组件的动态化方案

文章插图

Tangram 2.0——猫客页面内组件的动态化方案

文章插图

Tangram 2.0——猫客页面内组件的动态化方案

文章插图
展望
尽管在功能流程上已经逐步稳定 , 能承载起日常及大促的需求变更 , 我们的方案还是有很多不足之处的,比如我们期望更高的运行效率、更加扁平化的UI结构、更加方便的开发体验,对此也做了更进一步的规划建设:
功能计划
提供更加完善的文档和教程、Demo,内外版本同步,建立以为中心的迭代开发机制
17年12月
组件创建、布局计算、数据绑定机制优化,提升性能
18年1月
重构模板编译工具,提升编译开发体验
18年1月
提供预览服务 , 提升开发效率
18年3月
提供配套的后端数据服务与基础设施,即 TAC 平台开放
18年3月
附录2.0 主要更新说明 组件模型的概念升级,从原来的『卡片』+『组件』升级成『布局』+『组件』 , 即原来的『卡片』认为是一种具有布局能力的组件,具备嵌套另一组件的能力;页面结构优化,页面下可以直接挂载组件,不需要嵌套一层布局;组件类型的语义化,从原来的 1、2、3、4…等数字枚举类型定义,升级成字符串类型的定义,兼容解析原有的数字枚举定义;更好的嵌套布局实现 , 流式布局在模型描述上支持多层次的嵌套,并优化了端上的实现方式; 去重的实现 , 同一层级的容器组件或原子组件直接,支持外边距的去重,使得动态数据下控制间距更方便;支持 ,无论是容器组件还是原子组件,支持在其样式上配置  ,  值越大 , 绘制层次越高;升级组件开发方式,引入动态化组件开发技术 , 提升组件动态性,实现组件样式的高效渲染与动态更新; 其他相关的库
iOS
工具
【Tangram 2.0——猫客页面内组件的动态化方案】摘自:苹果核 - 猫客页面内组件的动态化方案- 2.0