Plaster动态化布局——原理篇

4,145 阅读9分钟

一、什么是Plaster

App的日常运营中经常遇到这样的诉求,我们希望能够快速调整页面结构和布局,特别是重运营类的App,比如商城类更是需要做到实时调整,以满足运营策略的随时上线,这时候动态化布局应运而生,Plaster也是为了应对这类问题而探索出的一套新的解决方案。Plaster意指雕塑的石膏,我们希望这套框架能够像石膏一样,能够按照产品诉求随意雕刻出想要的页面。

设计理念

提起动态化大家并不陌生,从最初的H5页面,到Facebook的ReactNative(RN),到微信的小程序,再到最近大热的Google Flutter,App开发者对已有方案的设计和性能追求从未停止。特定背景下,它们确实很好的承载了某些场景下开发者对动态化的诉求。但是这些方案有个共同的局限性,就是为了追求解决问题的完备性,通常都比较重量级,部分方案因为加载和渲染机制的原因,其性能体验也有所欠缺,使其更多被用来承载二级页面的功能。

1、首页的卡片图集

大概是在半年前,app上线了房间卡片样式的首页,初版设计了上图的卡片集合,上线运营过程中,调整和新增卡片样式的需求从未间断,开发只能跟着版本迭代作出调整,哪怕只是微小的改动也要按部就班地走开发、测试、灰度等步骤,整个流程下来少则大半个月,多则1个多月。

后来运营同学找到开发,问有没有能力根据运营策略,在首页随时上线新卡片,或调整已有卡片的样式,而不是只能通过发客户端版本才能灵活调整。我们考察了目前常用的动态化方案,发现无论是H5还是RN类的方案,它们动态化能力的关键在于随时可发布代码,是面向开发的动态化方案,发布代码意味着开发、测试、灰度等一系列流程都要完整跑通,显然难以满足产品和运营的诉求。

于是我们思考,有没有这样一种动态化方案:1、它可以简单开发测试后上线,不用跟随版本迭代;2、其性能良好,可以放在首页运行;3、可以和已开发的native卡片出现在一个页面。于是Plaster应运而生,Plaster是面向运营和产品的方案,它的动态能力体现在通过新增或修改样式Xml和后台数据,即可达到动态调整页面布局的目的,无须做客户端代码的任何改动。简单对比如下:

值得注意的是,对Plaster来说,动态布局的轻量级不代表动态能力弱,动态能力的大小取决于注册在客户端的组件丰富程度。2、几种动态化技术对比

技术定位

为了满足这样的使用诉求,我们明确了Plaster的技术定位:

  • 页面布局动态化。可以通过布局文件和后台数据的下发调整布局。
  • 布局元素组件化,动态布局的能力大小取决于客户端支持组件的丰富度。这里的布局元素既包含文本、图片、按钮等基本元素,也包含Banner,播放器等复合组件。
  • 使用动态化布局的页面区域必须和native布局方式灵活融合。也就是说动态化布局的范围可以是整个页面,也可以是RecyclerView的单个Item,或者仅仅是 Item内的部分区域。
  • 要支持Android和iOS的双端统一布局。

二、Plaster里的概念模型

要理解Plaster的实施原理,需要首先了解下Plaster的一些基础概念模型。

页面结构

在我们日常的App开发中,基于多类型布局的列表页面(Android称之为多类型Item的RecyclerView)占据非常重要的位置,它是许多复杂页面的布局基础,也是App内最常见和最多用的,因此大多数页面都可以抽象成如下结构:

3、页面结构模型

对列表内的某个Item作调整或新增一个Item,在日常开发中特别常见,对其实施动态化布局也最迫切,因此基于此结构模型,我们明确了Plaster两步走的技术路线:一期我们先支持item级别(包括局部区域)的动态化布局,并使其和native的布局方式相兼容。二期我们会支持整个Page的动态化,它需要以Item的动态化为基础。

4、技术路线

组件体系

Plaster将所有的页面布局拆分成一个个组件,通过组件的任意组合生成复杂的页面。组件分为三类:基础组件、复合组件、流式组件。

  • 基础组件是组成页面的基本单元,其功能单一,包括原子组件(类似Android的View)和布局组件(类似Android的ViewGroup);
  • 复合组件是为了实现复杂功能的自定义组件,其功能通常无法用单一基础组件实现,比如Video组件就包括播放器View和覆盖其上的控制View等;
  • 流式组件是Plaster抽象出的概念,它是包含其他布局的容器,其实现基于MVC的设计模式,其显示由后台的下发数据决定,包括布局的样式和数量。其数据可能是父布局直接下发的数据字段,也可能父布局仅仅下发了请求接口,客户端再通过接口异步加载数据。

值得注意的是,目前除了流式组件内置在Plaster框架内,基础组件和复合组件的包方式的按照通用性分为三个类别,最通用的内置在Plaster内,次通用的放置在plaster-widget内,不具有通用性的组件由开发者自己在外部实现并注册到Plaster内。其组件分类如下:

5、布局组件体系

三、技术实现

基于Xml的DSL定义

无论是H5还是RN,动态化都需要有描述页面结构的语言。H5和RN为了追求开发模式的完备,各自定义了一套通用编程语言(GPL)。Plaster的技术定位并不追求开发模式的完备,所以采用的办法是定义一套轻量级的领域专用语言(DSL)。动态化的DSL定义主流有两种形式:一种是使用Json的形式描述,另一种是使用Xml的形式描述。Json的形式因为大家都熟悉,所有使用成本更低,而Xml的形式组织页面更加直观,描述复杂页面更加简练,同时也更接近Android平台的开发模式。由此可见,json的方式相比Xml的方式除了使用成本低外难言其它优势,所以我们最后采用了基于Xml的DSL描述。

Plaster的DSL分为两个部分,一是布局DSL,二是表达式DSL。下图为目前首页布局描述的部分截图样例,其中Flexbox、Image、Text都属于布局DSL领域,而属性中带@符号的运算式属于表达式DSL。

6、动态化布局文件示例

虚拟节点树

从XML文件到页面UI布局树从原理上是一个映射过程,考虑到布局性能和复用的原因,这样的映射不是直接完成的,需要中介数据结构:虚拟节点树。虚拟结点树。它应包含树形结构的层级信息、Flex属性信息、数据解析处理后的内容信息以及基本的渲染信息。

7、节点流程

双端布局组件选择

为了最大化的支持双端布局的一致性,Plaster选择 Flexbox作为布局的容器,其主要基于如下几点考虑:

  • Flexbox 为盒状模型提供最大的灵活性,跨平台方案,双端统一;
  • 开源环境中有优秀开源库的本地化。iOS端Facebook开源的Yoga已经经过10+release 迭代,有13.8k+的Star,(ComponentKit底层也是依赖于Yoga),在生产环境经过长时间验证;
  • Android端最终选择Google的FlexboxLayout框架来解析,该框架也非常成熟,在和 iOS Yoga配合中仅需要很少的双端适配。

实现流程

上图介绍了Plaster的基本概念,主要是理清楚Plaster是什么,做什么的问题。下面要介绍Plaster怎么做的问题,当然本文不涉及具体的实现细节,只从原理上让大家有一个整体的概念。

8、动态化实现原理

主要流程有这么几个核心模块组成:核心引擎、下载模块、表达式引擎、布局框架,整个流程主要由这些模块串联起来。其过程如下:

  • 核心组件在初始化时加载所有注册的组件,并构建组件树。
  • 后台下发数据绑定了布局id,下载模块比较本地布局缓存信息决定要不要下载最新的布局文件,如果有必要则下载文件到本地。
  • 核心模块解析Xml文件,并按照和组件树的对应关系建立模版节点树。
  • 核心模块收集模版节点树内的表达式属性,并将表达式和后台下发的数据传入表达式引擎求值,求值完成后生成实体节点树。
  • 实体节点树按照和原生组件的一一对应关系,生成native的布局节点树。
  • 布局框架将native的布局节点树,按照平台的页面组织方式生成对应的页面,完整动态化的布局。

四、业务试点

Plaster目前已在Android客户端的首页试点尝试,如下图的官方房间卡片即采用Plaster实现,验证了包含Xml下载、节点树建立、基于表达式的数据绑定、布局框架等功能,证明了其广泛的应用前景。

9、app首页

五、小结

本文整体性的介绍了Plaster的技术方案和一些实现原理,由于具体实施时需要考虑的细节比较多,很多地方只能在整体思路上进行介绍,后续我们会逐步将一些细节开放出来分享。