跨平台是最近几年很火的技术栈,也是未来的趋势,一次开发,多出处行,能节约大量的人力和时间成本。目前主要的跨平台方案有三种
1. 以原生组件为基础,对接web生态RN,快应用,weex这这一类为代表
2. 以Web为代表
3. 以独立绘,自有生态的flutter为代表
这里来通过横向对比这三类技术,来有一个深度和广度的认识。对于这三种技术,我这都通过介绍他们的渲染流程,优点,缺点,如何优化来进行一个横向的比较。
浏览器Web
在了解web是如何渲染之前,我们要先了解web是什么?一个完整的web页面由html,css和js这三部分组成。html负责描述页面结构和内容,css设定页面样式风格,js控制页面动态逻辑,通过这三部分,一个web页面就可以渲染成开发预期的ui效果。
渲染
我们接下来看看浏览器web是如何渲染的。
通过上图可以看到,一个web页面的完整渲染流程是这样的
1. 通过网络或者缓存加载我们想要展示的web页面及资源
2. 将我们获取到的web页面解析,遇到html的部分,就采用htmlparser来解析,遇到js的内容,就采用jsengine来解析,遇到css部分就采用css parser来解析。解析后的最终的结果是一棵节点带CSS Style、会响应自定义事件的Styled DOM树
3. 将我们生产的dom数执行layout过程,这个过程主要涉及生成渲染树,以及根据盒模型来进行测量和布局
4. 将经过测量和布局的渲染数绘制出来
我们来详细看一下上面的流程
parser过程
webkit在解析网页内容时,通过解码,分词,解析和建树四个步骤,将html最终转换成dom树。
- 解码:将网络上接收到的经过编码的字节流,解码成Unicode字符。
- 分词:按照一定的切词规则,将Unicode字符流切成一个个的词语(Tokens)。
- 解析:根据词语的语义,创建相应的节点(Node)。
- 建树:将节点关联到一起,创建DOM树、Render树和RenderLayer树。
<html>
<body>
<p>Hello, World</p>
<div><img src=”example.png”/><div/>
</body>
</html>
对于上面的一段html代码,解析过程会经历下面两个阶段
通过上面的html和dom的对比,其实可以发现dom和html是一一对应的。每有个html的标签,都有对应的dom element来表示。
我们在通过一张图看看parser的流程
layout过程
layout分为两个步骤,一是创建渲染(Render)树,二是执行布局,我们分别来看这两个过程。
1,创建布局树主要有下面几个步骤
- 在创建dom树的同时,创建render树。render树并不是在dom树创建完成之后才创建的,而是同时创建的。当调用创建节点的attach函数时,render树就会创建。
- 绑定css属性。render树创建完成后,会attach上css的规则
通过上图可以看看,render树与dom树在很大程度上也是一一对应的,但是dom树中一些节点,比如head,不需要渲染的是不会生成render节点的。
2,计算布局
- 布局主要基于css的盒子模型,通过margin,padding,border,content以及static,releative,absolute,fixed等定位属性来进行测量和布局计算。
3,paint
- Render树映射成可视的图形,它会遍历Render树调用每个Render节点的绘制方法将其内容显示在一块画布或者位图上,并最终呈现在浏览器应用窗口中成为用户看到的实际页面
我们在通过下面的流程图回顾一下web的绘制流程
优缺点
我们接着看看web的优缺点
优点
- 稳定而且一致的跨平台能力
缺点
- js的执行和DOM的计算都在主线程中,如果js频繁改动dom或者js有耗时操作,就会阻塞主线程。
- js是解释性语言,解释型语言的程序不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低。
优化
那么我们如何来优化web呢?我这里将web的流程分成了三个步骤,网页资源准备,运行,渲染。
- 下载主要是html,css,js等资源的下载流程;
- 运行表示web网页的运行流程,包括dom的 解析,js的运行等;
- 渲染主要是页面的渲染及绘制流程
资源准备
资源准备过程是很依赖网络的,所以可以通过http2.0,cdn,加服务器,加带宽等优化服务器的速度。除掉网络的条件,我们可以通过资源管理管理来优化这个过程,主要包括细粒度控制有效资源的加载时机,预加载,懒加载,资源压缩,资源分割,分包,缓存等。
运行
在对scripting流程进行优化时,我们先看看httparchive官方统计数据
可以看到,最耗时的是布局,其次是执行脚本和重新计算样式
在布局上,为了兼容W3C标准,导致非常繁杂。开发者除了优化页面结构布局外,比较难做优化。所以我们主要看js脚本执行这一块。
其次就是针对js脚本运行慢,这一点主要是因为语言执行慢,针对这一瓶颈,退出了webassembly技术。webassembly可以直接用c++等高效语言写脚本,然后编译成机器码,V8是能比较好的支持webassembly的。我们大概来了解一下webassembly。
通过上图可以看到,当v8执行一个js脚本时,首先会将js转换成字节码,也就是parse过程,其次,通过compile和optimize将字节码转换成机器码,最后在运行。而采用webassembly,运行前就已经将脚本转换成了机器码,直接省去了parser和很大部分的compile过程。而且webassembly用的是强语言,运行的速度也会比js快很多。
渲染
和硬件运算速度有关,开发者比较难优化
其他优化方案
服务端渲染主要是将动态的数据在服务端渲染出来,返回给客户端静态页面。节约客户端再去请求数据的时间。
RN
我们接着开始看看以快应用,weex,rn这这一类为代表的跨平台技术,这类技术最大的优势是既能使用系统原生组件,又能对接前端生态。我们来看一下这类方案通用的渲染流程。
渲染
rn,weex和快应用,都主要由四部分组成,JsFramework,jsbridge,v8,和客户端组成。我分别讲一下这四个部分的作用
- JsFramework主要在于创建Vdom,以及向端侧发送指令(render,method),响应端侧发过来的指令(call function)
- jsbridge主要做jsframework和端测的通信桥联
- v8或者jscore主要用来执行js文件
- 端侧则响应jsframework传来的渲染指令,创建vdom,进行渲染工作
优缺点
我们接着再了解一下,这类技术的优缺点
优点
- 能使用系统原生组件和方法
- 渲染速度比web快
- 稳定的跨平台能力
缺点
- 不能完全兼容W3C的规范,比如W3C里面,可以轻易设置圆角的大小,粗细,边框是实现和虚线,但是在客户端,这个实现起来都比较难。所以这类技术都只能有限的支持W3C的标准。
- js运行性能瓶颈
- 数据通信的性能瓶颈
优化
上面讲了web的优化分为下载,运行,渲染这三个步骤,RN,WEEX的优化同样也需要将拆分流程,因为weex和rn都是开源,我们可以改动的控件更大一点,所以这里我会把把这个流程分的更细一点。打开一个rn页面,主要有:
- 资源准备
- JS文件执行
- JsFramework创建VDom并生成对应的组件数据指令
- 指令数据传输
- 客户端解析指令
- 客户端执行组件数据指令创建VDom
- 客户端渲染VDom
资源准备
资源准备的优化,主要有两个点,一是提高网络性能,二是减少下载的资源大小
提高网络性能这一点在介绍浏览器的时候说过,快应用也是需要同样的方法,对于客户端开发来说,是很难控制网络的条件的,所以这一方面能做出的优化有限
减少下载的资源大小也和web的方案是通用的,我这里说一下这几种通用的方式
- 数据缓存,数据缓存相当于将资源下载的大小将为零
- 分批或者按需下载,首次启动时,只下载首页的数据,然后后台去下载其他页面的数据
- 资源的压缩,选择压缩率更高的压缩算法
- 预下载,预下载就是在我们使用资源前就将资源下载至本地
JS文件执行
执行js文件的主要是v8引擎,所以想要优化js文件的运行效率,就需要优化v8引擎,weex,rn相比web页面的优势是集成了独立的V8引擎,我们可以对v8做出优化,而web页面的v8受制于浏览器的限制,我们没法做出修改。
在讲浏览器的时候,提到过webassembly,那么weex和rn可以用这个技术吗?这个是比较难的,因为webassembly并不支持js语言,如果想要使用webassembly,需要让第三方开发换强语言,这对于已有的生态来说,是不可能实现的。
但是我们可以提前将js文件提前编译成字节码或者机器码,就能省掉js文件的编译时间。
JsFramework创建VDom并生成渲染指令
在这个过程,需要把握渲染指令的传递时机
在前面的渲染流程我们可以知道,页面的js文件会将页面的dom数据传到jsframework,jsframework此时会根据这些数据创建VirtualDom,然后将dom节点创建指令发送到native端。
发送时机有两种选择
- 一是jsframework一边创建VitrualDom的同时,将dom节点的指令发送到native端,这种方式的好处是会减少白屏时间,因为native端可以更早的开始创建dom,但是缺点是native端频繁的重绘会影响性能
- 第二种方式是等jsframework层的virtdom创建完毕后,在将dom节点的指令传递到native层,这样会减轻native层的性能消耗,但是会增加页面白屏时间,如果jsframework创建vdom耗费的时间太久,native的ui表现上就会一直白屏。
这两种方式我们要根据具体的页面,均衡的选择。
vdom创建
vdom创建是一个耗时的操作,jsframework层需要不断的优化,采用更搞笑的diff算法,优化dom创建流程等方式需要对这个创建过程不断的优化。但我们都知道这个过程也是有瓶颈的,但jsframework创建vdom到达瓶颈后怎么办呢?我们知道js语言的速度和性能远远要低于c++,如果将vdom的创建过程,移动到c++层,那么就可以突破瓶颈,将vdom的创建速度大大的提升。
客户端解析指令
native创建页面的过程,会收到大量的createbody,addelement等指令数据,因为指令的数量比较多,在数据的序列化与反序列上,也必定会耗费不少的性能,为了减少在数据解析上的性能消耗,我们可以采用更高效的数据协议,如二进制,protobuf等。同样,为了突破数据解析的性能瓶颈,我们也可以将数据解析移到c++层。
客户端执行组件数据指令创建VDom
此时,客户端开始根据jsframework传来的dom节点数据,开始创建Vdom了。vom的创建,一定不能阻塞主线程,这里需要采取多线程创建组件,所以weex和rn在创建组件时都是才用的多线程模型。
其次,我们如果能将vdom的数据序列化缓存至本地,那么下次打开时,也能节约vdom的创建时间,极大的提高页面打开时间。
客户端渲染VDom
渲染过程主要依赖客户端的原生ui渲染,这个过程中,我们能优化的是我们组件,如Div,Text,Video,ListView等这些我们自定义的组件,那么如何优化呢?我们知道android在渲染ui时,会经历measure,layou,draw这个三个过程,那么我们对我们的自定义组件的优化也主要围绕着三方面,我们在可以子线程创建vdom的同时,将组件的布局测量出来,那么在子线程中渲染组件是,就可以省掉测量布局的流程,直接复用我们已经测量好的布局信息。也可以优化自定义组件的层级结构,或者尽量减少ondraw方法里面的逻辑。
其他优化方案
这一类跨平台技术架构的瓶颈在于native测绘制ui的绘制依赖于js的运行,而js的运行又是比较耗时。这也是为weex,rn,快应用这类跨平台方案无法达到原生的渲染速度。
通过上图可以看到,客户端排版是强依赖js执行的。
我们知道一个页面的js文件里面既包含了逻辑数据,也包含了ui数据,如果我们能把这些ui数据直接拆分出来,交给native端直接去处理,而不是交给jsframework去处理,然后jsframework在将ui的指令数据传到native端。这样在渲染ui的时候,就可以直接跨过对jsframework的依赖,达到接近原生的渲染速度。
对于这种方案,我们可以看看weex是如何实现的。
可以看到weex将页面的js里面的ui部分拆分了出来,这样客户端就能直接拿这一部分ui数据渲染界面,不再依赖jsframework的运行,这种方案能极大的提高页面的渲染速度。
Flutter
快应用的部分讲完了,我们接着看看第三种跨平台的方案,也就是也flutter为代表的,自建生态的技术。
渲染
我们先看一下flutter的架构图
从架构图,我们可以知道,flutter的架构由上到下分别是:
- 代表Android和iOS的Material及CupertinoUI设计风格
- Flutter以Dart语言实现的Widget生态
- 渲染模块
- 动画,手势,还有基于底层绘制接口实现的绘制模块
- 公共和通用类库
- c++实现的flutter引擎,包括skia,dart虚拟机等。
- 嵌入层,基于平台差异,做一些线程,渲染等配置工作。
那Flutter是如何渲染的呢?这个流程主要涉及到三棵树
- Widget树。Widget树是开发用dart开发的,描述页面和逻辑等信息,类似于web的html
- Element树。flutter会根据widget树,生成element树和renderobject树,element类似于Dom,用来描述节点信息,还有做diff。
- RenderObject树。renderobject则会进行布局测量,进行渲染处理。
优缺点
优点
- 因为dart语言执行速度比js快,而且没有过高的通信成本,渲染速度接近原生效果
缺点
- 无法动态更新
- 内存和包大小占用
- 学习成本高,生态不足。
总结
通过横向对比,我们会发现这三种跨平台方案有很多相同点,比如都有类似于dom的树机制,都有用于render的渲染树,都需要经过测量,布局,绘制这三个流程。每一种方案也都有向其他方案借鉴经验,比如web的同层渲染,这个技术可以在浏览器中嵌入原生平台的view,起源于以weex为代表的跨平台方案,比如weex,rn中使用的Dom,为了兼容web生态,起源于Web。这三种方式各有各的使用场景,我们需要根据不同的场景和需求,选择适合我们的跨平台方案。