上图为微信小程序的项目结构,pages下面包含了小程序中的每个页面,每个页面由页面结构,页面样式,页面配置和逻辑代码四部分组成。web
页面结构文件为index.wxml,经过微信自定义的标签来写。编程
页面逻辑经过JavaScript来书写。小程序
相似CSS文件,来定义页面内元素的样式。微信小程序
页面内的权限等配置信息。浏览器
小程序的定位特色是轻,快,针对这两个特色,在技术选型上,微信进行了一些考量。缓存
缺点:没法动态打包,动态下发。服务器
缺点:若是咱们用纯 Web 技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是由于在 Web 技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易致使一些逻辑任务抢占UI渲染的资源。微信
从渲染底层来看,PhoneGap与微信 JS-SDK 是相似的,它们最终都仍是使用浏览器内核来渲染界面。而 RN 则不一样,虽然是用 Web 相关技术来编写,一样是利用了 JavaScript 解释执行的特性,但 RN 在渲染底层是用客户端原生渲染的。咱们选择相似于微信 JSSDK 这样的 Hybrid 技术,即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每一个小程序页面都是用不一样的WebView去渲染,这样能够提供更好的交互体验,更贴近原生体验,也避免了单个WebView的任务过于繁重。网络
RN 所支持的样式是 CSS 的子集,会知足不了 Web 开发者日渐增加的需求,而对 RN 的改造具备不小的成本和风险。架构
RN 现有能力下还存在的一些不稳定问题,好比性能、Bug等。RN 是把渲染工做全都交由客户端原生渲染,实际上一些简单的界面元素使用 Web 技术渲染彻底能胜任,而且很是稳定。
RN 存在一些不可预期的因素,好比以前出现的许可协议问题
在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。开发者插入一个原生组件,通常而言,组件运行的时候被插入到 DOM 树中,会调用客户端接口,通知客户端在哪一个位置渲染一块原生界面。在后续开发者更新组件属性时,一样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。
因为JavaScript的灵活性和浏览器的功能丰富,会致使不少不可控的隐私,所以,微信提供了一个单纯的JS执行环境,经过对于其中的控件也进行了自定义。所以彻底采用这个沙箱环境不能有任何浏览器相关接口,只提供纯JavaScript 的解释执行环境,那么像HTML5中的ServiceWorker、WebWorker特性就符合这样的条件,这二者都是启用另外一线程来执行 JavaScript。可是考虑到小程序是一个多 WebView 的架构,每个小程序页面都是不一样的WebView 渲染后显示的,在这个架构下咱们很差去用某个WebView中的ServiceWorker去管理全部的小程序页面。得益于客户端系统有JavaScript 的解释引擎(在iOS下是用内置的 JavaScriptCore框架,在安卓则是用腾讯x5内核提供的JsCore环境),咱们能够建立一个单独的线程去执行 JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码,也就是咱们前面一直提到的逻辑层。而界面渲染相关的任务全都在WebView线程里执行,经过逻辑层代码去控制渲染哪些界面,那么这一层固然就是所谓的渲染层。这就是小程序双线程模型的由来。
为了防止标签订义带来的一些问题,微信自定义了一套标签语言,WXML,这套标签语言通过编译以后,最终会生成Html。
上面是小程序的渲染技术的选型,在选型以后,因为渲染和逻辑再也不同一个浏览器执行,一个在纯JS环境中,一个经过WebView渲染,所以小程序的运行环境分红渲染层和逻辑层,WXML 模板和 WXSS 样式工做在渲染层,JS 脚本工做在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,因此渲染层存在多个WebView线程,这两个线程的通讯会经由微信客户端作中转,逻辑层发送网络请求也经由Native转发,小程序的通讯模型如图所示。
在开发UI界面过程当中,程序须要维护不少变量状态,同时要操做对应的UI元素。随着界面愈来愈复杂,咱们须要维护不少变量状态,同时要处理不少界面上的交互事件,整个程序变得愈来愈复杂。一般界面视图和变量状态是相关联的,若是有某种“方法”可让状态和视图绑定在一块儿(状态变动时,视图也能自动变动),那咱们就能够省去手动修改视图的工做。
小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变动的时候,咱们须要经过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再通过对比先后差别,把差别应用在原来的Dom树上,渲染出正确的UI界面。
经过setData把msg数据从“Hello World”变成“Goodbye”,产生的JS对象对应的节点就会发生变化,此时能够对比先后两个JS对象获得变化的部分,而后把这个差别应用到原来的Dom树上,从而达到更新UI的目的,这就是“数据驱动”的原理。
UI界面的程序须要和用户互动,例如用户可能会点击你界面上某个按钮,又或者长按某个区域,这类反馈应该通知给开发者的逻辑层,须要将对应的处理状态呈现给用户。因为WebView如今具有的功能只是进行渲染,所以对于事件的分发处理,微信进行了特殊的处理,将全部的事件拦截后,丢到逻辑层交给JavaScript进行处理。
事件的派发处理,具有事件捕获和冒泡两种机制。经过native传递给JSCore,经过JS来响应响应的事件以后,对Dom进行修改,改动会体如今虚拟Dom上,而后再进行真实的渲染。
小程序是基于双线程模型,那就意味着任何数据传递都是线程间的通讯,也就是都会有必定的延时。这不像传统Web那样,当界面须要更新时,经过调用更新接口UI就会同步地渲染出来。在小程序架构里,这一切都会变成异步。
异步会使得各部分的运行时序变得复杂一些。好比在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工做,可是渲染层须要有逻辑层的数据才能把界面渲染出来,若是渲染层初始化工做较快完成,就要等逻辑层的指令才能进行下一步工做。所以逻辑层与渲染层须要有必定的机制保证时序正确,
在每一个小程序页面的生命周期中,存在着若干次页面数据通讯。逻辑层向视图层发送页面数据(data和setData的内容),视图层向逻辑层反馈用户事件。
经过Json的方式进行数据的传递,提升性能的方式就是减小交互的数据量。
小程序宿主环境会管理不一样小程序的数据缓存,不一样小程序的本地缓存空间是分开的,每一个小程序的缓存空间上限为10MB,若是当前缓存已经达到10MB,再经过wx.setStorage写入缓存会触发fail回调。
小程序的本地缓存不只仅经过小程序这个维度来隔离空间,考虑到同一个设备能够登陆不一样微信用户,宿主环境还对不一样用户的缓存进行了隔离,避免用户间的数据隐私泄露。
因为本地缓存是存放在当前设备,用户换设备以后没法从另外一个设备读取到当前设备数据,所以用户的关键信息不建议只存在本地缓存,应该把数据放到服务器端进行持久化存储。
支付宝小程序的实现和微信小程序的实现方式大体是相同的,所以这里主要针对二者的差别性的地方。
支付宝小程序目录结构
支付宝小程序业务架构图
在渲染引擎上面,支付宝小程序不只提供 JavaScript+Webview 的方式,还提供 JavaScript+Native 的方式,在对性能要求较高的场景,能够选择 Native 的渲染模式,给用户更好的体验。
小程序编程模型是分为多个页面,每一个页面有本身的 template、CSS 和 JS,实际在运行的时候,业务逻辑的 JS 代码是运行在独立的 JavaScript 引擎中,每一个页面的 template 和 CSS 是运行在各自独立的 webview 里面,页面之间是经过函数 navigateTo 进行页面的切换。
每一个 webview 里面的页面和公共的 JavaScript 引擎里面的逻辑的交互方式是经过消息服务,页面的一些事件都会经过这个消息通道传给 JavaScript 引擎运行环境,这个运行环境会响应这个事件,作一些 API 调用,可调到客户端支付宝小程序提供的一些能力,处理以后会把这个数据再从新发送给对应的页面渲染容器来处理,把数据和模板结合在一块儿来,在产生最终的用户界面。
一般的作法是在 WebView 里面运行 render 的代码,而后另起一个线程运行 serviceworker,当 serviceworker 须要更新 dom 的时候把事件和数据经过 messagechannel 发送给 render 线程来执行,当业务须要传递到 render 层数据量较大,对象较复杂时,交互的性能就会比较差,所以针对这种状况咱们提出一个优化的解决方案。
该方案将原始的 JS 虚拟机实例 (即 Isolate) 从新设计成了两个部分:Global Runtime 和 Local Runtime。
Global Runtime 部分是存放共享的装置和数据,全局一个实例。
Local Runtime 是存放实例自身相关的模块和私有数据,这些不会被共享。
在新的隔离模型下,webview 里面的 v8 实例就是一个 Local Runtime,worker 线程里面的 v8 实例也是一个 Local Runtime,在 worker 层和 render 层交互时,setData 对象的会直接建立在 Shared Heap 里面,所以 render 层的 Local Runtime 能够直接读到该对象,而且用于 render 层的渲染,减小了对象的序列化和网络传输,极大的提高了启动性能和渲染性能。
因为小程序启动是受到生命周期的控制,从 onLaunch -> onLoad -> onShow -> onReady -> 用户操做 -> 离开首页这个流程,在这个过程当中的任意一个环节都有可能被客观或者主观的缘由打断,也就有可能致使保存的离线页面不许确,在启动的时候给用户呈现错误的页面。
因此对于首页离线缓存渲染的效果,保存页面的时机很重要,咱们提供让开发者能够配置的时机,配置的时机有两个:渲染完成和离开首页前。对于渲染完成就是首页渲染完成,用户还未执行任何的操做前把页面保存下来做为离线缓存的页面。离开首页前就是指用户在首页执行了一系列的操做后,跳转到其余页面前用户看到的页面保存下来做为离线缓存的页面。
对于闪屏问题发生的场景是由于缓存页面和真实渲染的页面是分离的,是两个独立的页面,缓存页面是静态的页面,真实的页面是经过 js 动态建立的页面,因此常规的作法就是当真实页面建立完成后替换缓存的页面,这样的状况下就会发生闪屏。
针对这个问题,咱们是采用虚拟 dom 来解决,在加载缓存页面的时候把缓存页面放入初始的虚拟 dom 里面,真实页面建立后产生的虚拟 dom 跟缓存页面的虚拟 dom 进行 dom diff,把变化的内容经过 patch 传给浏览器内核,渲染对应的页面,这样就能够只更新局部有变化的页面内容,避免了整个页面的更新,也保证内容的准确性和实时性。
1.图片内存:针对低端机,作了更严格的图片缓存限制,在保持性能体验的状况下,进一步限制图片缓存的使用;多个 webview 共用图片缓存池;全面支持 webp、apng 这种更节省内存和 size 的图片格式。
2.渲染内存:Webview 在不可见的状态下,原生的内存管理没有特殊处理,UC 内核会将不可见 webview 的渲染内存释放;渲染内存的合理设置与调优,避免滚动性能的降低和占用过多内存。
3.JS 内存:更合理地处理 v8 内存 gc,在启动时延时执行 full gc,避免影响启动的耗时。
4.峰值内存管理:系统在内存紧张时,会通知内核,UC 内核可以在系统低内存时释放非关键内存占用的模块,避免出现 oom,也避免过分释放带来的渲染黑块;在部分 oom 的状况,规避原生内核主动崩溃的逻辑,在内存极低的状况,部分功能不可用,而不是崩溃。