h5 hybrid 方案
浏览器本就是一个跨端实现方案,因为你只需要输入网址,就能在任何端的浏览器上打开你的网页。那么,如果我们把浏览器嵌入 app
中,再将地址栏等内容隐藏掉,是不是就能将我们的网页嵌入原生 app
了。而这个嵌入 app
的浏览器,我们把它称之为 webview
,所以只要某个端支持 webview
,那么它就能使用这种方案跨端。
WebView
WebView简单点说就是Android和IOS/MacOS中用来展示网页的 view
组件,不同平台实现不一样。WebView的作用就类似于APP里内置了一款浏览器,可用于承载H5页面。
浏览器引擎
浏览器引擎俗称浏览器内核。负责对网页语法的解释并渲染网页,将网页的代码转换为最终可见的页面形式,并且决定浏览器如何显示网页的内容以及页面的格式信息。
JSCore
JSCore是WebKit默认内嵌的JS引擎。
它大概提供了两种能力:
- 在原生代码里面执行 JavaScript,而不用通过浏览器
- 把原生对象注入到 JavaScript 环境里面去
提供了 JS 代码与原生代码交互的能力,通过 JSCore 可以更好的进行两端的对象暴露,这使得代码可以不断地在 JS 环境和原生环境穿梭。jscore其实就是给APP提供了一个js可以解释执行的运行环境与资源,让JS可以执行Native方法,并让Native回调JS,反之亦然
JSBridge原理
既然我们使用了 WebView
来承载 H5
,那么便少不了与 Native
之间发生交互,WebView
所承载的页面,通过 JS
与 Native
进行通信,我们将这个通信的”桥梁“为 JSBridge
。
JSBridge
简单来讲,主要是 给 JavaScript 提供调用 Native 功能的接口
,让混合开发中的『前端部分』可以方便地使用地址位置、摄像头甚至支付等 Native
功能。
JSBridge核心是 构建 Native 和非 Native 间消息通信的通道
,而且是 双向通信的通道
。
所谓 双向通信的通道
:
- JS 向 Native 发送消息 : 调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息 : 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
JavaScript 是运行在一个单独的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于这些 Context 与原生运行环境的天然隔离,我们可以将这种情况与 RPC(Remote Procedure Call,远程过程调用)通信进行类比,将 Native 与 JavaScript 的每次互相调用看做一次 RPC 调用。如此一来我们可以按照通常的 RPC 方式来进行设计和实现。
在 JSBridge 的设计中,可以把前端看做 RPC 的客户端,把 Native 端看做 RPC 的服务器端,从而 JSBridge 要实现的主要逻辑就出现了:
通信调用(Native 与 JS 通信
和 句柄解析调用
。
前端调用原生能力主要有两种方式
- 注入 APIs;
- 拦截 URL Scheme。
注入 APIs 其实就是原生平台通过 WebView 提供的接口,向 JavaScript Context 中(一般使用 Window 对象),注入相关方案和数据;另一种拦截 URL Scheme 就更加简单了,前端通过发送定义好的 URL Scheme 请求,并将相关数据放在请求体中,该请求被原生平台拦截后,由原生平台做出响应。
Native调用JS 因为 Native 实际上是 WebView 的宿主,因此 Native 具有更大权限,故而原生平台可以通过 WebView APIs 直接执行 JavaScript 代码。
React Native
在 RN 中,主要有三个重要的组成模块:平台层( Android 或者 OC 环境),桥接层( C++ )和JS 层。
- 平台层负责原生组件的渲染和提供各式各样的原生能力,由原生语言实现;
- 桥接模块负责解析 JS 代码,JS 和 Java/OC 代码互调,由 C++ 语言实现;
- JS 层负责跨端页面具体的业务逻辑。
相比起 Webview 的结构来说,RN 的结构多了一层桥接层,也就是 C++ 层。
桥接层(C++ 层)
桥接层(C++ 层)包括JSCore、Native模块、JavaScript模块
- JSCore
React Native 和 H5 一样,使用了 JS 作为跨端页面的开发语言,因此它必须要有一个 JS 执行引擎,而在使用 H5 的情况下,Webview 是 JS 的执行引擎,同时 Webview 还是页面的渲染引擎。RN 不一样的地方在于,已经有了自己的渲染层,这个功能交给了 原生组件,因为 RN 的 JS 组件代码最后都会渲染成原生组件。因此 RN 只需要一个 JS 执行引擎来跑 React 代码。
- Native 模块和 JavaScript 模块
说起通信的话,整个过程肯定存在信源和信宿,也就是消息的发送者和接收者,在 RN 的通信中,它们是 Native 和 JS 的模块,它们向对方提供能力都是以模块为功能单位的,类似 JSBridge 协议中的 ModuleID 的概念。
-
Native 模块在 Android 系统下是 Java 模块,由平台代码实现,JS 通过模块 ID(moduleID) 和方法 ID(methodID) 来进行调用,可以给 RN 页面开放原生系统的能力,如计时器的实现模块 Timing,给 JS 代码提供计时器的能力。
-
JavaScript 模块是由 JS 实现,对 Java 环境来说,作用是提供操作 JS 环境的 API,如回调,广播等。
JS 环境中会维护一份所有 Native 模块的 moduleID 和 methodID 的映射 NativeModules
,用来调用 Native 模块的时候查找对应 ID;Java 环境中也会维护一份 JavaScript 模块的映射 JSModuleRegistry
,用来调用 JS 代码。而在实际的代码中,Native 模块和 JS 模块的通信需要通过中间层也就是桥接层的过渡,也就是说 Native 模块和 JS 模块实际上都只是在和 桥接 模块进行通信。
如何通信
JS层如何和桥接层通信
JS 代码会在 global 对象中注入一些原生模块需要的 API,而JSCore 可以让 桥接层 拿到 JS 运行环境的 global 对象并能操作它的属性。通过 NativeModules 的映射,开发者能拿到调用模块和方法的 moduleID 和 methodID ,在调用过程中会映射到具体的 Native 的方法。
桥接层如何和原生层通信(以安卓为例)
Java 跟 桥接层的互相调用通过 JNI(Java Native Interface),通过 JNI,桥接 层会暴露出来一些 API 来给 Java 层调用,来让 Java 能跟 JS 层进行通信。
简单来说,RN初始化的时候就已经形成了映射表,并且往JS环境注入了一些API,Java 模块和 JS 模块可以通过 NativeModules 和 JS 回调函数互相调用,来达成一次跨端调用。