阅读 394

画图来讲一下 Weex 核心原理

背景

从前,如果我们打算实现某个需求,通常需要三种程序员(IOS, 安卓,前端)写三份代码。这就带来了非常大的开发成本,所以业界也一直在探索跨平台方案——从最早的H5, Hybrid 到现在的weex, React Native。这些方案的本质目的都是,一套代码,多端运行。

H5和Hybrid的发展

早期H5和Hybrid方案的本质是,利用客户端App的内置浏览器(也就是webview)功能,通过开发前端的H5页面满足跨平台需求。

该方案提升开发效率,同时也满足了跨端的需求。但有一个问题就是,前端H5的性能和客户端的性能相差甚远。

weex的发展

于是后来, 业界继续探索可以媲美原生体验app的方案,比如说WEEX 。

WEEX依旧采取前端H5页面进行开发,同时app在终端的运行体验不输native app。即可以保证快速响应需求,又可以保证用户体验。

那么WEEX是如何实现的?

本质来说,WEEX是用客户端Native 的能力,去做了部分浏览器(webview)的工作。

在2016年2月, WeexSDK 发布了v0.10.0版本,在这个版本里面,集成了v2 版本的Vue。

为啥是Vue 2.x 版本呢?

Vue 2.x加入了 Virtual-DOM 和预编译器的设计,使得该框架在运行时能够脱离 HTML 和 CSS 解析,只依赖 JavaScript;同时 Virtual-DOM 也使得 Vue 2.x 渲染成原生 UI 成为了可能。

weex 原理探究

我们先来看一下 weex 的整体框架。

weex 整体架构

从上图中可以看到weex的大致工作流程:

  1. 前端开发可以写熟悉vue语法的单文件,然后打包成出来一份dist —— JS Bundle,然后部署到服务器上
  2. 客户端打开某一个页面,通过网络下载JS Bundle,然后在客户端本地执行该JS Bundle
  3. 客户端提供了JS的执行引擎(JSCore)用于执行远程加载到JS Bundle
  4. JS执行引擎执行JS Bundle,和浏览器的过程类似,JS Bundle 的代码被执行,生成VNode 树进行patch,找出最小操作DOM节点的操作,把对DOM节点的操作转变为Native DOM API, 调用WXBridge 进行通信
  5. WXBridge将渲染指令分发到native(Andorid、iOS)渲染引擎,由native渲染引擎完成最终的页面渲染

看完上述整体流程后,可以大致理解为何WEEX可以达到媲美原生的体验,因为其页面渲染并不是像H5方案一样使用浏览器的渲染能力,而是原生渲染,所以本质上渲染出来的页面就是一个native页面。

下面会具体的介绍下面几个过程:

第一步: 生成 JS bundle

JS bundle 是前端同学写好代码后打包出来的dist.

前端同学可以在.vue 的单文件中,写<template>,<style><script>标签,然后把这些标签转换为JS Bundle用于部署在服务端,之后客户端会去请求这些JS Bundle

比如说,下图中左边是vue源代码,右边是打包出来到JS Bundle

熟悉Vue 原理的同学清楚,上面右边其实就是Vue打包生成的render 函数。

JS代码比较简单,逻辑就不介绍了。接下来重点介绍,当客户端获取到如上图右侧的js bundle后,如何进行加载、渲染以及后续的相关逻辑执行。

第二步: WEEX SDK初始化

weex在真正打开一个页面之前,会先做一些准备的初始化工作,这一点有一点像微信小程序。在初始化阶段,WEEX SDK 会初始化好下面几样东西:

  • 初始化 js的执行环境——js Core 或者是 v8
  • 加载weex-vue-framework 的代码
  • 初始化WXBridge

如下图所示:

js的执行环境

在初始化阶段, WEEX SDK 会准备好一个js的执行环境。因为我们是要在客户端跑js 代码的,所以需要一个js执行环境,这个执行环境类似于浏览器的v8 引擎, 在IOS 上,则是客户端自带的 js core

这个js执行环境,可以看成是一个在客户端上的沙盒,或者是一个虚拟机。

为了提升性能,js 执行环境只用在初始化的时候初始化一次,之后每个页面都无须再初始化了。也就是说不管客户端打开多少个weex页面,多个页面的 JS 都是跑在同一个js执行环境中的。

weex-vue-famework 框架

weex-vue-framework 框架 是什么呢?

你可以把 weex-vue-framework 框架当成被改造的Vue.js。语法和内部机制都是一样的,只不过Vue.js最终创建的是 DOM 元素,而weex-vue-framework则是向原生端发送渲染指令,最终渲染生成的是原生组件。

同时,Weex为了提高Native的极致性能,做了很多优化的工作。前端优化性能时,会把业务代码和 vue.js 这类的依赖包分开打包,一个份是业务代码,一份是打包的框架依赖。

weex 把weex-vue-framework 这类框架依赖内置到了SDK中,客户端访问Weex页面时,只会网络请求JS Bundle。由于JSFramework在本地,所以就减少了JS Bundle的体积,每个JS Bundle都可以减少一部分体积,从而提升了性能。

WXBridge 通信

WXBridge 是 weex 实现的一种 js 和 客户端通信的机制。

js 执行环境和客户端是隔离的,为了和外界客户端的世界通信,需要有一个通信的桥梁。weex 实现了 WXBrigde, 主要通过 callJScallNative 两个核心的方法,实现 js 代码和客户端代码双向通信。

在完成了上面的初始化之后,weex已经做好了准备,只等着下载 JS bundle 就可开始渲染页面了。

第三步:创建 weex 实例

实际上当WEEX SDK获取到JS Bundle后,第一时间并不是立马渲染页面,而是先创建WEEX的实例。

每一个JS bundle对应一个实例,同时每一个实例都有一个instance id

我们上文中说过,由于所有的js bundle都是放入到同一个JS执行引擎中执行,那么当js执行引擎通过WXBridge将相关渲染指令传出的时候,需要通过instance id才能知道该指定要传递给哪个weex实例

在创建实例完成后,接下来才是真正将js bundle交给js执行引擎执行。

第四步 执行 JS bundle

在实例创建完成后,接下来就是执行JS bundle 了。JS bundle 的结果是生成Virtual DOM ,然后去patch 新旧 Vnode 树,根据diff 算法找出最佳的DOM操作,唯一和浏览器不同的是,调用的是 Native app api ,而不是浏览器里面对DOM节点增删改查的操作。

VNode

bundle.js会执行new Vue()创建一个vue组件,并通过其render函数创建VNode节点,即virtual dom节点 , 像下面这样:

{
  tag: 'div',
  data: {
    staticStyle: { justifyContent: 'center' }
  },
  children: [{
    tag: 'text',
    data: {
      staticClass: 'freestyle'
    },
    context: {
      $options: {
        style: {
          freestyle: {
            textAlign: 'center',
            fontSize: 200
          }
        }
      }
    },
    children: [{
      tag: '',
      text: 'Hello World!'
    }]
  }]
}
复制代码

patch

生成了VNode之后,接下来需要将VNode同步到真实的Dom之上,该过程在Vue.js中被称为patchpatch会比较新旧VNode之间的差异,最小化操作集。最后再将Virual Dom整体更新到真实Dom之上。

在执行 patch 之前的过程都是 Web 和 Weex 通用的,后面的流程就不一样了,因为客户端没有对 DOM 增删改查的API,所以这些更新的操作,需要经过weex-vue-framework的处理,统统映射为客户端的Native DOM API

第五步 发送渲染指令

weex终端的执行引擎在执行到Native DOM API后,WXBridgeNative DOM API转化为Platform API

Platform API 是 Weex SDK 中原生模块提供的,不是 js 中方法,也不是浏览器中的接口,是 Weex 封装的一系列方法。

客户端 和 前端h5的不同

本人是一枚前端同学,不太了解客户端的页面是如何写出来的,问了客户端的大佬后,了解如下:

对于前端同学来说,写一个类似上面的框内带文字的效果非常简单:

.text {
    // css 样式
}

 哈哈哈哈 
复制代码

只需要 html + css 就可以实现。

对于客户端的同学,则需要写非常多的代码来实现:

  • 用逻辑代码写一个框(样式,大小,位置……)
  • 用逻辑代码写一行文字(样式,大小,位置……)
  • 用逻辑代码把两个合起来

所以,weex 会把上面一些系列复杂的代码封装好一个个现成的方法。

第六步 渲染引擎

原生渲染器接收上层传来的渲染指令,并且逐步将其渲染成原生组件。

这样,我们在js中的<div>, <p> 标签,就一一对应到了客户端的原生标签。

这个过程不是分阶段一个一个执行的,而是可以实现“流式”渲染的,有可能第一个<div>的原生组件还没渲染好,<text>的渲染指令又发过来了。当一个页面特别大时,能看到一块一块的内容逐渐渲染出来的过。

总结

通过前文的介绍,相信大家对WEEX有了一个初步的系统认识。简单来说,WEEX放弃了传统的Webview,而是搭建了一个native化的浏览器,因为用native的方式实现了一个浏览器的大部分核心组成成分:

  • JS 执行引擎
  • 渲染引擎
  • DOM树管理
  • 网络请求,持久层存储
  • 等等能力.

另外为了保证整个SDK的运行效率,SDK维护了三个线程:

  • bridge线程:完成js到native之间的通信
  • dom线程:完成dom结构的构建
  • 渲染线程:完成UI渲染,也就是UI线

以上就是WEEX SDK的大致框架和核心逻辑

关注下面的标签,发现更多相似文章
评论