小程序的介绍以及底层渲染原理
本文是今年年初做的公司级技术分享,这里先把自己准备的演讲稿发出来。之后进行一下文章的优化。
说起来也挺不好意思,虽然分享的次数挺多,但个人每次分享都还是比较紧张,会提前写好讲稿,保障分享质量。
正文:
今天由我来带大家一起探索一下微信小程序的技术架构。当然,虽然说是技术架构,但也会给大家详细介绍一下小程序到底是个什么东西,为什么会有小程序的诞生。
技术架构方面,会向大家介绍小程序的小程序的页面渲染机制、底层架构和运行原理、小程序渲染层的底层逻辑等等。
通过今天的分享,我希望能够让大家都有所收获:团队小程序开发同学能够巩固相关知识,了解小程序为什么是如此设计的,设计理念是什么,为之后进一步发力小程序开发做好准备;开发其他端的同学呢比如咱们有兴趣的后端、数据同学们则可以扩宽视野,了解小程序的一些基本情况和原理。毕竟我们公司目前的核心产品还是小程序,大家对小程序有一定的了解,能够更好的开展后续的工作协同。
小程序介绍
首先说到小程序,小程序现在已经是大家在工作生活中不可缺少的一部分了,这一块我们的确要认同并称赞微信,将小程序推广并普及到各方各面。但回到最开始,为什么要有小程序这个东西呢?或者说是什么理念让微信决定推出小程序这个功能?以及什么是小程序,小程序和移动应用的区别是什么。
在微信推出小程序之前,公众号如果想要推出自己的相关服务应用或者互联网企业想要通过微信传播产品,最常常用到的方案就是通过webview接入web网页。我们小年糕就是如此。大家如果了解过小年糕的历史可以知道,我们最初的第一个产品就是在公众号嵌入的webapp。
对于这类web网页来说,开发者一共经历了几个阶段:
最初的就是原生js+html+css。但是这种开发方案的扩展能力不足,我们希望能够吧网页分享到朋友圈?希望隐藏微信浏览器的一些按钮都无法做到。
- 原生js+html开发+微信内部API:WeixinJSBridge
WeixinJSBridge是最初微信内部提供的js api,可以调用微信的一些原生能力。比如隐藏微信中网页右上角按钮、隐藏微信中网页底部导航栏、调用微信原生组件浏览图片等功能。实际上,微信官方是没有对外暴露过如此调用的,此类 API 最初是提供给腾讯内部一些业务使用,很多外部开发者发现了之后,依葫芦画瓢地使用了,逐渐成为微信中网页的事实标准。
但是WeixinJSBridge提供的能力有限,微信内网页的能力依然不足,因此微信基于WeixinJSBridge推出正式的SDK,也是目前微信网页的开发方案:JS-SDK。
- JS-SDK
JS-SDK是对之前的 WeixinJSBridge 的一个包装,以及新能力的释放,并且由对内开放转为了对所有开发者开放。
JS-SDK 解决了移动网页能力不足的问题,通过暴露微信的接口使得 Web 开发者能够拥有更多的能力。开放了拍摄、录音、语音识别、二维码、地图、支付、分享、卡券等几十个API。给所有的 Web 开发者打开了一扇全新的窗户,让所有开发者都可以使用到微信的原生能力,去完成一些之前做不到或者难以做到的事情了。
但是web网页也面临着好几点问题,是微信开发者的头疼之处。第一个就是性能问题。
大家应该都曾经非常频繁的去浏览别人转发的网页或者是公众号引导的一些网页,那么大家有没有发现,我们在微信里浏览这些网页时经常会出现加载比较缓慢的情况,甚至有些页面还会出现短暂的页面白屏的情况,并且在操作的体验上也有许多不足,主要表现在两个方面:页面切换的生硬和点击的迟滞感。和使用纯正浏览器访问体感有较大差别。
当然,从纯前端角度这些也有相关解法,比如在一个 WebView 中去模拟多个页面,通过 CSS 处理,加之精细化的脚本代码做到点击反馈和页面切换,获得较好的体验。或者使用单页面应用框架。然而并不是所有的开发者或者说需求方都有足够的时间和精力来使得页面的体验变得出色。很多小商家、小公司,他们只需要有这么一个功能即可,他们的网页大多可能都是外包,没有能力去优化体验。
对微信而言,需要提供一个全新的系统,它需要使得所有的开发者都能做到基于微信生态简单开发出一个体验优秀的应用。
第二个原因则是帮助微信更好的规范和管理各方开发者的应用。
当微信内部充斥并传播了大量的web应用,对微信来说其实是失去了控制权的。因为web开发者是根据web开发标准来进行开发的,能够比较轻易的去绕过微信平台的一些审核和管控。微信需要花费很大的精力去检查页面是否违规。 同时大家对web应用的写法也各有千秋,微信还是希望能够去规范控制微信内部的开发标准。另外我也看到了一个说法:微信之父张小龙是一个有洁癖的人,他不希望这些不受控制的东西大量充斥在微信平台上。也有几分道理。
那么基于这两点,微信推出了小程序。
当然这只是核心的两点,还有其他的原因比如使得这些应用获得更多更强大的能力、体验更偏向于原生app、易用且安全的微信数据开放等等。
那么我们再到核心的定义问题上:什么是小程序?我们用张小龙的描述来介绍一下:
小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想
用户扫一扫或者搜一下即可打开应用,也体现了“用完即走”的理念
用户不用关心是否安装太多应用的问题,应用将无处不在,随时可用,但又“无需安装卸载”
那么红字标注出的三个特点也是小程序最重要的三个特点。概括上来讲,也就是小程序可以让我们通过更好的用户体验,在微信内来使用各种应用程序的功能。
那么小程序和传统APP直接有什么区别呢?我这边简单列举了几项。
如果我们要开发一款APP,大家都知道,一款APP至少需要满足运行在两个平台上,分别是IOS和安卓。如果我们要开发ios的app的话,我们需要掌握objective-c这样的开发语言,如果要开发安卓的APP,我们需要掌握java这样的开发语言。这样相对来讲,一款APP的开发成本就会相对来说比较高。但小程序则是一次开发即可跨终端跨平台的,开发成本相对来说更低,而且只需要掌握基本的前端技术即可。同时开发周期也会更短。在升级维护上面,小程序的升级和维护也更简单一些,只需要通过微信提供的后台发版管理工具操作即可。在推广成本上,如果我们想要用户去下载一款app的话,需要用户去应用商城上面下载。但应用商城非常多,如果想要我们的APP在各大应用商城上面都排在前面让用户容易下载的话,是需要一个很大的推广成本的。小程序相对来说就会更低一点。微信大家都有,不会没有国内的智能机没有装微信吧?不会吧不会吧?而且微信小程序的推广其实成本非常低,简单、迅速的微信分享带来的推广效果已经是许多小型app望尘莫及的了。
当然他们的差异性不仅于此,目前小程序的存在并不是说要取代app。许多小程序不能做到的事情还是需要app来承担。毕竟小程序的能力是有限的。
OK。关于小程序的前置介绍就到这里,大家已经初步了解了小程序的一些背景和来源的情况,接下来我们步入第二个重点。微信小程序的技术架构和运行原理。
小程序的页面渲染机制
问一下大家一个问题,在手机上渲染界面的方式有几种?当我们要做一个应用,我们需要画一个UI出来,有哪几种方式来画呢?这一块可能开发app的同学会非常熟悉,前端同学则会比较陌生。
第一种方式,我们可以交给webview来做渲染。WebView是移动端应用中一个非常重要的控件,它的作用是用来展示一个web页面。我们可以直接把它理解成一个浏览器。它使用的内核是webkit引擎,4.4版本之后,直接使用Chrome作为内置网页浏览器。
第二种方式,就是交给native做原生的渲染;也就是纯客户端原生技术来渲染。
第三种方式就是Hybird混合开发,webview和native来结合渲染。
下边给大家列举出了几种涉及到前端开发移动应用的常用方案。
第一个是Cordova,前身是phoneGap(最早期可以把js跑在手机端的框架),是我们最早期支持通过采用HTML,CSS和JavaScript的技术,创建移动跨平台应用程序的快速开发平台。另外一个ionic也是类似的。而这两个的核心就是webview渲染,它不会把你的前端页面变成 ios 原生的 objective-c 或者 android 的 java 代码,你的界面还是网页呈现的,渲染在 android 应用的 android.webkit.WebView 或 iOS 应用的 UIWebView 中。
而这种方案的弊端是当我们把这个应用做到体积非常大的时候会出现性能的问题,每次渲染的白屏问题出现比较频繁。能访问的硬件功能也有限。
第二种渲染方式:native原生渲染方式。直接开发使用安卓的 Java/Kotlin 和 IOS 的 Objective-C/Swift 和原生组件。而对于前端开发人员来说,这种方案的代表作就是react native,我们的小年糕app应用最初就是使用的这个方案。最终是渲染生成app的原生组件。比如我们写的一个
我们可以通过这张图片快速理解两者的区别:可以看到,你在 Cordova 应用的UI 中看到的所有,包括按钮、菜单和动画,都是在浏览器的网页中运行的。以模拟的角度来看,Cordova 应用的 UI 就是运行在 Web 浏览器中的模拟世界,而浏览器又是运行在原生框架里的另一个模拟世界。
相比之下,React Native 的 UI 直接运行在原生框架里。
而微信小程序的原理就是属于Hybird混合渲染的。为什么?
由于小程序的宿主是微信,所以不太可能用纯客户端原生技术来编写小程序。如果这么做,那小程序代码需要与微信代码一起编包,跟随微信发版本,这种方式跟开发节奏必然都是不对的。
小程序应该像 Web 技术那样,有一份随时可更新的资源包放在云端,通过下载到本地,动态执行后即可渲染出界面。但是,如果用纯 Web 技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在 Web 技术中,UI 渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占 UI 渲染的资源。
最终,小程序采用类似于微信 JSSDK 这样的 Hybrid 技术,并基于hybrid方法去推出了后面我们要讲到的底层双线程机制。即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的 WebView 去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个 WebView 的任务过于繁重。
既然采用Hybrid方式渲染,那么页面的渲染可能会用到原生native来渲染,什么情况会用到原生渲染呢?
答案是使用到小程序提供的map、video、canvas、textarea等组件,这些组件被称之为原生组件。在web的基础上,部分内容通过微信客户端去做原生渲染。这些我们稍后会讲到。现在,大家先了解到,小程序的页面渲染核心是通过webview+原生渲染即可。接下来我们先了解一下小程序的底层架构。
小程序的底层架构
小程序为了解决体验+管控,最终得出的结论是双线程模式。
程序总体分为逻辑层和渲染层,也就是说小程序框架本身就将逻辑层和渲染层分开来了,而不是纯web开发,渲染层和逻辑层混合,Vue和React之类的框架实际上也是做的这样的事情,渲染层只需要去关心UI视图,逻辑层去处理数据以及用户的操作状态等等。
逻辑层负责运行js脚本,产生、处理数据,因为我们的程序逻辑不论多复杂,其实核心都是围绕数据在做事。渲染层则通过预先写好的UI以及逻辑层的数据去渲染并更新实际用户看到的界面。
渲染层和逻辑层分别由两个线程管理。和web的区别在于,web项目的js引擎线程和ui的渲染线程是互斥的,一次只能执行一个。
在微信小程序中,我们要定义一个页面,那么一定要写四个文件:wxml、wxss、js、json。
渲染层使用 WebView 进行渲染,一个小程序存在多个界面,所以渲染层存在多个 WebView。具体到代码上面的体现,是每个页面的wxml文件和wxss文件。写法是这样的,我们可以看到它没有div这样的html标签,而是用一个view并不是html的标签来作为标签。我们可以直接把这个理解成微信端的html和css,他们的写法用法相差不大。WXML 全称是 WeiXin Markup Language,是小程序框架设计的一套标签语言,结合小程序的基础组件、事件系统,可以构建出页面的结构。WXSS则和CSS基本没有什么大的区别。至于为什么是view、button,后续再提。
逻辑层采用 JSCore 线程运行 JavaScript 脚本。对于着每个页面的js文件。比如这里,就在js中定义了一个name为小年糕,一个按钮点击方法,用于修改name。
这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转。反过来也是,渲染层触发了点击事件,也由客户端通知逻辑层。
客户端不仅是做两个线程间的中转,也会做很多其他的事:逻辑层发送网络请求也经由Native转发。
那么一个简单的循环就有了。
这就是我们常说的小程序双线程机制。这个设计主要是解决web技术中的两个痛点:
web页面开发渲染线程和脚本线程是互斥的,长时间的脚本运行可能会导致页面失去响应或者白屏,体验糟糕。
使用webview的原因已经提出,为什么js也要单独通过一个线程运行?如此设计的初衷是为了管控和安全,我们在讲小程序的背景时也讲到了这一目的。微信小程序阻止开发者使用一些浏览器提供的,诸如跳转页面、操作 DOM、动态执行脚本的开放性接口。比如跳转页面,小程序页面直接就跳转到其他的页面,那我们一开始所讲的微信的管控就没意义了。将逻辑层与渲染层进行分离,js代码执行在一个沙箱环境中,渲染层和逻辑层之间只有数据的通信,可以防止开发者随意操作界面,更好的保证了对于小程序的管控。
小程序渲染层的底层逻辑
我们刚才介绍了微信小程序的底层架构,主要是偏向于小程序的底层架构及运行原理。那么接下来我们步入分享的最后一个内容:小程序渲染层的底层逻辑。
在讲什么是渲染层的时候,我有提到,微信小程序的渲染层UI是由WXML和WXSS两个文件来编写的。那么为什么微信要新定义这两种文件呢?既然小程序的视图是在WebView里渲染的,那为什么搭建视图的方式不用到原生的HTML语言呢?
核心原因是因为HTML语言标签众多,增加了理解成本,而且直接使用HTML语言,开发者可以利用<a>
标签实现跳转到其他在线网页,也可以动态执行JAVAScript,前面所提到的为解决管控与安全而建立的双线程模型就成摆设了。
因此,小程序设计一套框架——Exparser。
Exparser核心负责几件事情:第一个就是推出了WXML和WXSS。
WXSS (WeiXin Style Sheets) 是一套样式语言,用来决定 WXML 的组件应该怎么显示。为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。与 CSS 相比,WXSS 扩展的一些特性,包括rpx尺寸单位和样式导入语法,这些特性都是WebView无法直接理解的。
wxml则内置了一套组件,以收敛web标签,并涵盖了小程序的基础功能,便于开发者快速搭建出任何界面。同时也提供了自定义组件的能力,开发者可以自行扩展更多的组件,以实现代码复用。
而内置组件则由两部分构成:由HTML5语言编写的web组件,我们称之为h5组件,是由webview进行渲染的。占了内置组件的大部分;由Android、ios 等NA客户端开发的控件,我们称之为原生组件。微信小程序的页面就由这两种组件来构成。
比如之前我们提到的view,属于h5组件。在小程序里view组件的作用就是视图容器,可以把它理解成html中的div。而view到最后渲染出来的也就是div标签。
而原生组件,比如video视频播放组件、map地图组件,都是由微信客户端原生去参与组件的渲染。他不是直接用webview去进行渲染的。
为什么会有原生组件?webview本身的h5组件不能满足需求吗?
引入「原生组件」主要有 3 个好处:
- 扩展 Web 的能力。比如像输入框组件 input, textarea ,原生渲染的相比于h5的输入框组件有更好地控制键盘的能力。
- 体验更好,同时也减轻 WebView 的渲染工作。比如像地图组件 map 这类较复杂的组件,其渲染工作不占用 WebView 线程,而交给更高效的客户端原生处理。
- 复杂的组件通过原生渲染,绕过数据通信和重渲染流程,渲染性能比webview更好。比如像画布组件 canvas 可直接用一套丰富的绘图接口进行绘制。页面展示更快
那么h5组件和原生组件在小程序上是如何渲染的呢?这也是框架需要做的事情。H5组件如我之前所述,是在webview中直接渲染。渲染的过程分为两种:初始渲染和重渲染。
初始渲染时,将初始数据套用在对应的WXML片段上生成节点树。节点树也就是在开发者工具WXML面板中看到的页面树结构,它包含页面内所有组件节点的名称、属性值和事件回调函数等信息。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。
重渲染时,初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将data和setData数据套用在WXML片段上,得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。最后,将setData数据合并到data中,并用新节点树替换旧节点树,用于下一次重渲染。
但原生组件则有所不同。它会经历以下几个过程:
- 组件被创建,包括组件属性会依次赋值。
- 组件被插入到 DOM 树里,浏览器内核会立即计算布局,此时可以读取出组件相对页面的位置(x, y 坐标)、宽高。
- 组件通知客户端,客户端在相同的位置上,根据宽高插入一块原生区域,之后客户端就在这块区域渲染界面。
当位置或宽高发生变化时,组件会通知客户端做相应的调整。