回复 刷新

暂无评论

详解原生小程序架构及同构方案

​最近实习中参与了H5项目向小程序迁移的工作,在微信官方文档和一些帖子上学习了小程序运行机制和底层原理,以及与Web页面的区别,在此基础上又看了一些关于小程序同构方案的内容。以下是我个人的一些学习总结。本文内容参考

  1. 微信开放文档

1、小程序是什么?

在小程序诞生前,微信团队开发的JS-SDK使web开发者可以通过暴露的API使用微信原生能力去完成一些事,如调用接口打开微信支付等。针对移动端设备网络状态不稳定导致的白屏问题,微信又推出增强版JS-SDK,也就是“微信 Web 资源离线存储”,但在复杂的页面上依然会出现白屏的问题,原因表现在页面切换的生硬和点击的迟滞感。这个时候需要一个JS-SDK处理不了的,使用户体验更好的一个系统,即小程序。

小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。

其本质是运行在webview上的H5应用,但与H5又有着本质上的不同。H5可以理解为运行在移动端的web页面,本质还是由HTML+CSS+JS构成的web应用。小程序和H5的区别也就是小程序和网页的区别。

2、小程序与普通网页开发的区别

小程序与普通网页开发是有很大差别的,这就要从它的技术架构底层去剖析了。还有比如习惯Vue,react开发的开发者会吐槽小程序新建页面的繁琐,page必须由多个文件组成、组件化支持不完善、每次更改 data 里的数据都得setData、没有像Vue方便的watch监听、不能操作Dom,对于复杂性场景不太好,之前不支持npm,不支持sass,less预编译处理语言。

小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是有些许区别的。

网页开发的渲染和脚本执行是在同一个线程上执行的,这也是网页脚本长时间运行有可能会导致页面失去响应的原因;而小程序的视图层和逻辑层是完全分离在两个不同的线程上执行

开发网页时我们可以在JS代码中通过Dom API对节点进行各种操作,通过window对象实现原生事件响应、页面跳转;由于小程序的JS代码运行在JSCore,与渲染层分离,所以在逻辑层中无法获得Dom和Bom,从而无法使用各种Dom API

网页开发者需要面对的环境是各式各样的浏览器,PC端需要面对IE、Chrome、QQ浏览器等,在移动端需要面对Safari、Chrome以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具

3、小程序架构

渲染机制

处于性能和实现的考虑,小程序采用Hybrid渲染机制,这样做有几点好处:

  1. 扩展 Web 的能力。比如像输入框组件(input, textarea)有更好地控制键盘的能力
  2. 体验更好,同时也减轻 WebView的渲染工作
  3. 绕过 setData、数据通信和重渲染流程,使渲染性能更好
  4. 用客户端原生渲染内置一些复杂组件,可以提供更好的性能

架构

如下图所示,原生小程序框架采用双线程模型:视图层和逻辑层完全分离为两个不同的线程。每个页面的渲染在一个webview线程上执行,视图层包含多个webview线程,而逻辑层则统一在JSCore上执行。

在这里插入图片描述

这样做的目的是防止逻辑层对Dom和window的操作(如跳转到外部页面),使整个应用变得安全可控。

  1. 逻辑层:创建一个单独的线程去执行 JavaScript,在这里执行的都是有关小程序业务逻辑的代码,负责逻辑处理、数据请求、接口调用等
  2. 视图层:界面渲染相关的任务全都在 WebView线程里执行,通过逻辑层代码去控制渲染哪些界面。一个小程序存在多个界面,所以视图层存在多个 WebView 线程
  3. JSBridge起到架起上层开发Native(微信系统)的桥梁,使得小程序可通过API使用原生的功能,且部分组件为原生组件实现,从而有良好体验

视图层和逻辑层的通信

双线程模型下,逻辑层代码无法直接操作Dom,逻辑层和视图层的数据传输(setData)是通过两边的evaluateJavaScript实现的。用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

小程序的基础库

小程序的基础库可以被注入到视图层和逻辑层运行,主要用于以下几个方面:

  1. 在视图层,提供各类组件来组建界面的元素
  2. 在逻辑层,提供各类 API 来处理各种逻辑
  3. 处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑

由于小程序的渲染层和逻辑层是两个线程管理,两个线程各自注入了基础库。

小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。

这样可以:

  1. 降低业务小程序的代码包大小
  2. 可以单独修复基础库中的 Bug,无需修改到业务小程序的代码包

4、运行机制

小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台状态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。

小程序没有重启的概念

当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁

当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁。

在这里插入图片描述

小程序多端同构方案

很多企业都有自己的小程序平台,如微信、支付宝、头条等,如今市面上很多产品都是基于React、Vue等框架开发的web应用,但web端代码是不可能运行在小程序平台上,而开发几套代码的时间和维护成本又太高,为了节省学习和开发成本,各大公司都推出了自己的多端小程序方案,使开发者可以用React、Vue框架来开发小程序。类似框架有微信的Kbone、阿里的Remax、京东的Taro等。

Taro是在编译时将代码适配到小程序平台,而Kbone和Remax则是在运行时完成这个工作。

5、以下重点解释Kbone和Remax。

Kbone

参照微信官方文档,Kbone在适配层里模拟出了浏览器环境,让 Web 端的代码可以不做什么改动便可运行在小程序里。

因为 kbone 是通过提供适配器的方式来实现同构,所以它的优势很明显:

  1. 大部分流行的前端框架都能够在 kbone 上运行,比如 Vue、React、Preact 等。
  2. 支持更为完整的前端框架特性,因为kbone 不会对框架底层进行删改(比如 Vue 中的 v-html 指令、Vue-router 插件)。
  3. 提供了常用的 dom/bom接口,让用户代码无需做太大改动便可从 Web 端迁移到小程序端。
  4. 在小程序端运行时,仍然可以使用小程序本身的特性(比如像live-player 内置组件、分包功能)。
  5. 提供了一些 Dom 扩展接口,让一些无法完美兼容到小程序端的接口也有替代使用方案。

由于Kbone是通过牺牲部分性能来实现适配的,所以在性能要求极高的场景如多节点、多数据页面,还是建议用原生开发。

kbone实现原理是在worker线程适配了一套JS Dom API,上层不管是哪种前端框架(react、vue)或原生JS最终都需要调用JS Dom API操作 dom,适配的 JS Dom API则接管了所有的Dom操作,并在内存中维护了一棵Dom tree,所有上层最终调用的Dom操作都会更新到这棵Dom tree中,每次操作(有节流)后会把Dom tree同步到webview线程中,通过wxml自定义组件进行 render。

Remax

与Kbone上层支持多种框架(React、Vue、Angular)不同,Remax专门实现React应用向小程序的适配。

Remax实现原理是在worker线程维护一棵虚拟Dom tree,这棵虚拟Dom tree会通过小程序原生的setData方法映射到render线程,render层再把虚拟Dom tree进行遍历然后渲染。

Remax和kbone类似,都是在 worker 线程维护一棵Dom tree,再把这棵 Dom tree传到render线程进行渲染,唯一的区别是remax dom tree发生变化时,会计算差异,而不需要把整棵树都传到render线程,此功能是react提供的,就是在 diff 完后找出差异,则把差异传到render线程。
在这里插入图片描述

6、总结

Kbone和Remax实现原理基本相同,都在worker线程维护虚拟Dom tree,再把虚拟Dom tree传到render线程渲染

二者主要有两点不同:Kbone上层支持多种框架,而Remax仅支持React;Remax的Dom tree发生变化时会计算diff,把diff映射到render线程,而Kbone是将整个Dom tree传过去。

  • 19
  • 0
  • 0