当前位置:网站首页>小程序技术原理分析

小程序技术原理分析

2022-08-11 05:23:00 想要成为程序媛的DUDUfine

背景

随着移动互联网的沉淀,超级APP成为新的底层平台,于是开始探索在体内构建轻应用的可能,开发新的流量入口、建立全面的用户关系。
在这里插入图片描述

轻型应用帮助平台拓宽了横线竞争的战场,由软到硬,由应用到系统,各领域的头部企业纷纷潜入轻应用底层。在自己的生态上发展轻型应用。

从2017年1月小程序正式发布以来,日活用户超4亿,全面进入了生活服务、政务、交易、内容、电商、教育等日常生活中各个领域。

我们今天就一起来了解一下微信小程序这个背靠“微信”这座巨无霸大山的小应用背后的技术。
我们会从以下几个角度去分析小程序的运行机制和它的整体技术架构。

小程序诞生的技术背景

最初在小程序没有出来之前,微信WebView逐渐成为移动Web重要入口。微信发布了一整套网页开发工具包,称之为 JS-SDK,让所有Web
开发者都可以使用到微信的原生能力。

但JS-SDK 的模式并没有解决使用移动网页遇到的体验不良的问题,比如受限于设备性能和网络速度,会出现白屏的可能。因此又设计了一个增强版JS-SDK,也就是“微信 Web 资源离线存储”,但在复杂的页面上依然会出现白屏的问题,在页面切换会有生硬和点击的迟滞感。
这个时候需要一个可以让用户体验更好的一个系统,小程序应运而生。

相比于原来的WebView,小程序的优点有:

  • 快速的加载(静态资源存放在本地)
  • 更强大的能力(依托于宿主环境提供的能力)
  • 原生的体验
  • 易用且安全的微信数据开放(调用微信获取用户信息)
  • 高效和简单的开发(开发语言都是js,html和css也基本一致)

WebView 是移动端(手机、IPad)提供的运行 JavaScript 的环境,是系统渲染 Web 网页的一个控件,可与页面 JavaScript 交互,实现 APP 与 Web 的混合开发,WebView 渲染 Web 页面需要强大的渲染内核支持,这其中 Android 与 IOS 系统的内核又有所不一样。

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

宿主环境

微信小程序宿主环境微信客户端,它是依赖于微信客户端上运行的,并且跟小程序 基础库 版本有重大关联关系。
我们可以把微信客户端 以及 小程序基础库 简称为微信小程序的宿主环境

微信小程序可以调用宿主环境提供的微信客户端的能力,完成许多普通网页无法完成的功能。这就使得小程序比普通网页拥有更多的能力。小程序会运行在不同版本(不同的微信客户端+不同基础库)的宿主环境下,因此不可以避免地有时候需要为不同版本的宿主环境做程序上的兼容。

执行环境

小程序的开发和普通的网页开发相比有很大的相似性,主要开发语言都是 JavaScript,但是二者还是有些差别的。

  • 普通网页开发,渲染线程和脚本线程是互斥的,长时间的脚本运行可能会导致页面失去响应;
    而在小程序中,二者是分开的,分别运行在不同的线程中;
  • 普通网页开发,可以使用各种浏览器提供的 DOM API,进行 DOM 操作;
    小程序的逻辑层和渲染层是分开的,视图层的界面使用 WebView 进行渲染,逻辑层运行在 JSCore中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。
  • 网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可;
    小程序的开发则有所不同,需要经过申请小程序帐号、安装小程序开发者工具、配置项目等等过程才能完成。
  • 网页开发,需要面对各厂商的浏览器,在移动端还需要面对Safari、Chrome,以及iOS、Android系统中的各种Webview;
    微信小程序运行在多种平台上:iOS(iPhone/iPad)微信客户端、Android微信客户端、PC微信客户端、Mac微信客户端和用于调试的微信开发者工具。

小程序在不同平台脚本执行环境以及用于渲染非原生组件的环境是各不相同的,具体区别如下:

运行环境逻辑层渲染层
AndroidV8自研Xweb引擎,基于Mobile Chrome
IOSJSCoreWKWebView
开发工具NW.jsChromium Webview
PC端(window)Chrome内核Chrome内核
MacJSCoreWKWebView

平台差异
尽管各运行环境是十分相似的,但是还是有些许区别:

  • JavaScript 语法和 API 支持不一致:语法上开发者可以通过开启 ES6 转 ES5 的功能来规避;此外,小程序基础库内置了必要的Polyfill,来弥补API的差异。
  • WXSS 渲染表现不一致:虽然可以通过开启样式补全来规避大部分的问题,开发者还是需要在 iOS 和 Android 上分别检查小程序的真实表现,以最终的表现以客户端为准。

小程序架构

一、技术选型

一般来说,渲染界面的技术有三种:

  • Native:用纯客户端原生技术来渲染
  • Web: 用纯 Web 技术来渲染
  • Hybrid:用客户端原生技术与 Web 技术结合的混合技术(简称 Hybrid 技术)来渲染

通过以下几个方面分析,小程序采用哪种技术方案:

  • 开发门槛:Web 门槛低,Native 也有像 RN 这样的框架支持
  • 体验:Native 体验比 Web 要好太多,Hybrid 在一定程度上比 Web 接近原生体验
  • 版本更新:Web 支持在线更新,Native 则需要打包到微信一起审核发布
  • 管控和安全:Web 可跳转或是改变页面内容,存在一些不可控因素和安全风险

纯客户端原生
由于小程序的宿主环境是微信,如果用纯客户端原生技术来编写小程序,那么小程序代码每次都需要与微信代码一起发版,需要像web技术那样,有一份随时可更新的资源包放在云端,通过下载到本地,动态执行后即可渲染出界面。所以纯原生的方式是不行的。

纯Web技术
如果用纯Web技术来渲染小程序,在一些复杂的交互上可能会面临一些性能问题。因为在web技术中,UI渲染跟JavaScript的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。所以纯Web技术也是不行的。

最终采用的是原生和Web技术结合起来的Hybrid 技术来渲染小程序,这样可以用一种近似Web的方式来开发,并且可以实现在线更新代码,同时引入原生组件也有以下好处:

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

二、双线程模型

小程序的系统架构主要有两个部分:视图层(WebView)和逻辑层(App Service),这两个部分分别由两个独立的线程管理。

  • 视图层:也称为渲染层。渲染层用来渲染页面结构,主要由Webview进行渲染。一个小程序可以存在多个界面,所以渲染层可能存在多个WebView线程。
  • 逻辑层:逻辑层采用JSCore线程运行JS脚本。逻辑层主要用来逻辑处理、数据请求、接口调用等。

在这里插入图片描述

那么为什么要这样设计呢?前面也提到了管控和安全,为了解决这些问题,需要阻止开发者使用一些,例如浏览器的window对象、跳转页面、操作DOM、动态执行脚本的等开放性接口。

不同客户端使用的 JavaScript 引擎不同,iOS 下使用 JavaScriptCore 框架,安卓下使用腾讯 x5 内核提供的 JsCore 环境。
这个沙箱环境只提供纯 JavaScript 的解释执行环境,没有任何浏览器相关接口。

三、双线程通信

这样把开发者的 JS 逻辑代码放到单独的线程去运行,在 Webview 线程里,开发者就没法直接操作 DOM。那怎么去实现动态地更改界面呢?

在这里插入图片描述

如上图所示,在视图层和逻辑层的中间还有一个系统层(WeixinJsBridge),一般简称为JSBridge,它起到了一个中间桥梁的作用。它不仅让视图层与逻辑层两个独立的线程可以进行通信,而且架起了上层开发与系统底层功能(Native)的桥梁,使得小程序可以通过调用API使用原生功能,而且部分的组件使用了原生组件实现,从而有了良好的体验。
逻辑层还有一个重要的操作——发送网络请求,它也是由系统层转发的。

视图层和逻辑层之间的数据传递是线程间的通信,就会有一定的延时。这不像传统Web一样,当页面要更新时调用相关的API就能同步渲染出来,在小程序的架构里面,渲染就变成了异步操作。

异步会使得各部分的运行时序变得复杂一些。比如在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工作,但是渲染层需要有逻辑层的数据才能把界面渲染出来,如果渲染层初始化工作较快完成,就要等逻辑层的指令才能进行下一步工作。因此逻辑层与渲染层需要有一定的机制来保证时序正确。在每个小程序页面的生命周期中,存在着若干次页面数据通信。

双线程交互的生命周期图示:
在这里插入图片描述

了解视图层与逻辑层的具体通信过程后,我们也稍微来了解一下视图层和逻辑层的数据传输大致是如何的。我们知道这两者通信是借助了系统层的作用,而实际上是通过两边提供的 evaluateJavascript 所实现的,即用户传输的数据。数据传输时需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

关于evaluateJavascript:
Native 调用 JS, 一般就是直接 JS 代码字符串,有点类似我们调用 JS 中的 eval 去执行一串代码。它一般有 loadUrl、evaluateJavascript 等几种方法。
这里就不做过多的介绍了,你只要记住,它是用来调用执行 JS 字符串,是一种 Native 用来识别 JS 代码的方式的就行啦。

四、小程序的基础库

基础库与客户端之间的关系

小程序的能力需要微信客户端来支撑。但是小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。
这样可以:

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

微信客户端每次发布,都会携带上一个稳定版的基础库发布,每一个基础库都只能在对应的客户端版本上运行,高版本的基础库无法兼容低版本的微信客户端。

基础库
基础库包括了:Exparser框架、内置组件、API、 自定义组件、插件等

小程序的基础库可以被注入到视图层和逻辑层运行。由于小程序的渲染层和逻辑层是两个线程管理,两个线程各自注入了基础库。主要用于以下几个方面:

  • 提供 VD 渲染机制相关基础代码(Exparser 框架)
  • 提供封装后的内置组件
  • 提供逻辑层的 API
  • 提供其他补充能力(自定义组件和插件等)的基础代码。
    在这里插入图片描述

Exparser 框架

Exparser是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持,例如蓝牙、直播能力、微信运动等。小程序内的所有组件,包括内置组件和自定义组件,都由Exparser组织管理。

Exparser 会维护整个页面的节点树相关信息,包括节点的属性、事件绑定等,相当于一个简化版的 Shadow DOM 实现。Exparser的主要特点包括以下几点:

  1. 基于ShadowDOM模型:模型上与WebComponents的ShadowDOM高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他API以支持小程序组件编程。
  2. 可在纯JS环境中运行:这意味着逻辑层也具有一定的组件树组织能力。
  3. 高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。

小程序中,所有节点树相关的操作都依赖于Exparser,包括WXML到页面最终节点树的构建、createSelectorQuery调用和自定义组件特性等。

内置组件

基于Exparser框架,小程序内置了一套组件,提供了视图容器类、表单类、导航类、媒体类、开放类等几十种组件。

在小程序架构里无法实现或者实现不好某类功能,就会把这个功能作为一个基础的组件内置到小程序框架中。
比如像:

  • 开放类组件:如 open-data 组件提供展示群名称、用户信息等微信体系下的隐私信息,有 button 组件里 open-type 属性所提供分享、跳转 App 等敏感操作的能力
  • 视图容器类组件:如 movable-view 这种因双线程模型导致手势识别不好实现的组件(在双线程模型中,触摸事件从渲染层发出,派发到逻辑层,这中间是有一定的延时而导致视图跟随手指运动这类交互变得有些卡顿)

API
宿主环境提供了丰富的API,可以很方便调起微信提供的能力。
小程序提供的 API 按照功能主要分为几大类:网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息还有一些特殊的开放接口。
需要注意 API 调用大多都是异步的。

自定义组件
自定义组件是开发者可以自行扩充的组件。开发者可以将常用的节点树结构提取成自定义组件,实现代码复用。

在使用自定义组件的小程序页面中,Exparser 将接管所有的自定义组件注册与实例化。以 Component 为例:

在小程序启动时,构造器会将开发者设置的 properties、data、methods 等定义段,写入 Exparser 的组件注册表中。
这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例。
Page 构造器的大体运行流程与之相仿,只是参数形式不一样。这样每个页面就有一个与之对应的组件,称为“页面根组件”。

在初始化页面时,Exparser 会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。

插件
插件是对一组 js 接口、自定义组件或页面的封装,用于嵌入到小程序中使用。

插件不能独立运行,必须嵌入在其他小程序中才能被用户使用;而第三方小程序在使用插件时,也无法看到插件的代码。因此,插件适合用来封装自己的功能或服务,提供给第三方小程序进行展示和使用。

插件开发者可以像开发小程序一样编写一个插件并上传代码,在插件发布之后,其他小程序方可调用。小程序平台会托管插件代码,其他小程序调用时,上传的插件代码会随小程序一起下载运行。

五、小程序运行时

运行机制

小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。

  • 冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。
  • 热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。
    从小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台。

当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是30分钟)会被微信主动销毁。
当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。
在这里插入图片描述
小程序启动前,微信会提前准备好一个页面层级用于展示小程序的首页,包括了逻辑层和渲染层分别的初始化以及公共库的注入。

更新机制

未启动时更新

微信运行时,会定期检查最近使用的小程序是否有更新。如果有更新,下次小程序启动时会同步进行更新,更新到最新版本后再打开小程序,尽可能保证用户能够尽快使用小程序的最新版本。

总的来说,开发者在后台发布新版本之后,无法立刻影响到所有现网用户,但最差情况下,也在发布之后 24 小时之内覆盖绝大多数用户。

启动时更新
即使启动前未发现更新,小程序每次冷启动时,都会异步检查是否有更新版本。如果发现有新版本,将会异步下载新版本的代码包。但当次启动仍会使用客户端本地的旧版本代码,即新版本的小程序需要等下一次冷启动才会应用上。

如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。

在这里插入图片描述

六、性能优化

在小程序启动流程中,代码包准备、小程序代码注入和首页渲染的耗时是与小程序本身相关的,开发者可以进行一定的优化工作。其他部分的耗时由小程序框架侧负责进行持续的优化。

开发者可以从以下方面着手进行启动性能的优化:

  • 代码包体积优化 ,精简代码,降低WXML结构和JS代码的复杂性;
  • 代码注入优化,必要时使用分包优化
  • 页面渲染优化,数据通信机制中我们讲过小程序是基于双线程的,那就意味着任何在视图层和逻辑层之间的数据传递都是线程间的通信,频繁的去调用 setData(),或者传递大量的数据,就会通信耗时上升,不能够及时渲染页面。合理使用setData调用,减少setData次数和传递数据量。
  • 简化页面结构,当一个页面 DOM 结构复杂并且非常多的时候,由于 DOM 绘制、计算都是需要时间的,都是需要占用线程和更多客户端内存,导致页面卡顿。所以简化页面结构,可以加快页面的渲染,优化用户体验。

总结

大致从以上几个角度分析了小程序的底层架构,从小程序的由来、到双线程的出现、设计、通信、到基础库、Exparser 框架、再到运行机制、性能优化等等,都是一个个相关而又相互影响的选择。关于小程序的底层框架设计,其实涉及到的还有很多,有兴趣的话大家后续自己了解。

原网站

版权声明
本文为[想要成为程序媛的DUDUfine]所创,转载请带上原文链接,感谢
https://blog.csdn.net/DUDUfine/article/details/121857327