ReactNative开发ReactNative前端开发那些事儿

React Native之原理浅析

2020-08-01  本文已影响0人  peaktan

原文链接

一、JavaScriptCore

讲React Native之前,了解JavaScriptCore会有帮助,也是必要的。React Native的核心驱动力就来自于JS Engine. 你写的所有JS和JSX代码都会被JS Engine来执行, 没有JS Engine的参与,你是无法享受ReactJS给原生应用开发带来的便利的。在iOS上,默认的就是JavaScriptCore, iOS 7之后的设备都支持. iOS 不允许用自己的JS Engine. JavaScriptCore来自于WebKit, 所以,安卓上默认也是用JavaScriptCore

你深入了解React Native的第一站应该是 JavaScriptCore

二、浏览器工作原理

UI的描述和呈现分离开了

  1. html文本描述了页面应该有哪些功能,css告诉浏览器该长什么样。
  2. 浏览器引擎通过解析html和css,翻译成一些列的预定义UI控件,
  3. 然后UI控件去调用操作系统绘图指令去绘制图像展现给用户。
  4. Javascript可有可无,主要用于html里面一些用户事件响应,DOM操作、异步网络请求和一些简单的计算

在react native 里面,1和2是不变的,也是用html语言描述页面有哪些功能,然后stylesheet告诉浏览器引擎每个控件应该长什么样。并且和浏览器用的是同一个引擎

在步骤3里面UI控件不再是浏览器内置的控件,而是react native自己实现的一套UI控件(两套,android一套,ios一套),这个切换是在MessageQueque中进行的,并且还可以发现,他们tag也是不一样的

Javascript在react native里面非常重要

三、React Native 架构

680.jpg

四、React Native、React和JavascriptCore的关系

React Native最重要的三个概念应该就是React NativeReactJavascriptCore

React Native它可不一样

React Native组件结构

驱动硬件的能力决定能一个软件能做多大的事情,有多大的主控性。研究过操作系统底层东西或者汇编的同学明白,我们大部分时候写的代码是受限的代码,很多特权指令我们是没法使用的,很多设备我们是不允许直接驱动的。我们现在的编程里面几乎已经没有人提中断了,没有中断,硬件的操作几乎会成为一场灾难.

在一定程度上,React Native和NodeJS有异曲同工之妙。它们都是通过扩展JavaScript Engine, 使它具备强大的本地资源和原生接口调用能力,然后结合JavaScript丰富的库和社区和及其稳定的跨平台能力,把javascript的魔力在浏览器之外的地方充分发挥出来

JavaScriptCore + ReactJS + Bridges 就成了React Native

深入 Bridge 前面有提到, RN厉害在于它能打通JS和Native Code, 让JS能够调用丰富的原生接口,充分发挥硬件的能力, 实现非常复杂的效果,同时能保证效率和跨平台性。

打通RN任督二脉的关键组件就是Bridge. 在RN中如果没有Bridge, JS还是那个JS,只能调用JS Engine提供的有限接口,绘制标准html提供的那些效果,那些摄像头,指纹,3D加速,声卡, 视频播放定制等等,JS都只能流流口水,原生的、平台相关的、设备相关的效果做不了, 除非对浏览器进行定制

五、Bridge各模块简介

5.1 RCTRootView

RCTRootView做的事情如下

一个App可以有多个RCTRootView, 初始化的时候需要手动传输Bridge做为参数,全局可以有多个RCTRootView, 但是只能有一个Bridge

如果你做过React Native和原生代码混编,你会发现混编就是把AppDelegate里面那段初始化RCTRootView的代码移动到需要混编的地方,然后把RCTRootView做为一个普通的subview来加载到原生的view里面去,非常简单。不过这地方也要注意处理好单Bridge实例的问题,同时,混编里面要注意RCTRootView如果销毁过早可能会引发JS回调奔溃的问题

5.2 RCTRootContentView

5.3 RCTBridge

这是一个加载和初始化专用类,用于前期JS的初始化和原生代码的加载

5.4 RCTBatchedBridge

如果RCTBridge是总裁, 那么RCTBatchedBridge就是副总裁。前者负责发号施令,后者负责实施落地

5.5 RCTJavaScriptLoader

这是实现远程代码加载的核心。热更新,开发环境代码加载,静态jsbundle加载都离不开这个工具。

5.6 RCTContextExecutor

5.7 RCTModuleData

5.8 RCTModuleMethod

记录所有原生代码的导出函数地址(JS里面是不能直接持有原生对象的),同时生成对应的字符串映射到该函数地址。JS调用原生函数的时候会通过message的形式调用过来

  • 如果是原生方法的调用则直接通过方法名调用,MessageQueue会帮忙把Method翻译成MethodID, 然后转发消息给原生代码,传递函数签名和参数给原生MessageQueue, 最终给RCTModuleMethod解析调用最终的方法
  • 如果JS调用的是一个回调block,MessageQueue会把回调对象转化成一个一次性的block id, 然后传递给RCTModuleMethod, 最终由RCTModuleMethod解析调用。基本上和方法调用一样,只不过生命周期会不一样,block是动态生成的,要及时销毁,要不然会导致内存泄漏

实际上是不存在原生MessageQueue对象模块的,JS的MessageQueue对应到原生层就是RCTModuleData & RCTModuleMethod的组合, MessageQueue的到原生层的调用先经过RCTModuleData和RCTModuleMethod翻译成原生代码调用,然后执行

5.9 MessageQueue

六、React Native 初始化

React Native的初始化从RootView开始,默认在AppDelegate.m:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 里面会有RootViewd的初始化逻辑,调试的时候可以从这里入手

React Native的初始化分为几个步骤

这里需要提一下的是

这里的导出是没有对象的,只有方法和模块。JS不是一个标准的面向对象语言,刚从Java转JavaScript的同学都会在面向对象这个概念上栽跟头,这里特别提醒一下

6.1 原生代码初始化

这里讨论的主要是RN相关的原生代码和用户自定义的RN模块的原生代码的加载和初始化。原生代码初始化主要分两步

6.2 Javascript环境初始化

JS Engine不直接管理UI的绘制

6.3 NativeModules加载

|

<pre style="box-sizing: border-box; overflow: auto hidden; font-family: PingFangSC-Regular, Roboto, Verdana, "Open Sans", "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans CN", "WenQuanYi Micro Hei", Arial, sans-serif; font-size: 1em; line-height: 1.5em; white-space: pre-wrap; overflow-wrap: break-word; margin: 0px; background-color: transparent;">- (NSDictionary *)constantsToExport
{
return @{ @"firstDayOfTheWeek": @"Monday" };// JS里面可以直接调用 ModuleName.firstDayOfTheWeek获取这个常量
}
</pre>

|

6.4 三个线程

React Native有三个重要的线程:

可以看到Shadow queue是queue而不是thread, 在iOS里面queue是thread之上的一层抽象,GCD里面的一个概念,创建queue的时候可以指定是并行的还是串行的。也就是说,一个queue可能对应多个thread

七、内部机制

内部机制

681.jpg

JS用时序

682.png

八、总结

8.1 React Native 框架分析

683.png

8.2 层次架构

注:JSCore,即JavaScriptCore,JS解析的核心部分,IOS使用的是内置的JavaScriptCore,Androis上使用的是 https://webkit.org 家的jsc.so。

Java层核心类及原理,如下所示

ReactContext

ReactInstanceManager

ReactRootView

CatalystInstance

JavaScriptModule

NativeModule

JavascriptModuleRegistry

NativeModuleRegistry

CoreModulePackage

8.3 启动过程的解析

  1. ReactInstanceManager创建时会配置应用所需的java模块与js模块,通过ReactRootView的startReactApplication启动APP。
  2. 在创建ReactInstanceManager同时会创建用于加载JsBundle的JSBundlerLoader,并传递给CatalystInstance。
  3. CatalystInstance会创建Java模块注册表及Javascript模块注册表,并遍历实例化模块。
  4. CatalystInstance通过JSBundlerLoader向Node Server请求Js Bundle,并传递给JSCJavaScriptExectutor,最后传递给javascriptCore,再通过ReactBridge通知ReactRootView完成渲染

8.4 Js与Java通信机制

Java与Js之间的调用,是以两边存在两边存在同一份模块配置表,最终均是将调用转化为{moduleID,methodID,callbackID,args},处理端在模块配置表里查找注册的模块与方法并调用。

Java 调用Js

Java通过注册表调用到CatalystInstance实例,透过ReactBridge的jni,调用到Onload.cpp中的callFunction,最后通过javascriptCore,调用BatchedBridge.js,根据参数{moduleID,methodID}require相应Js模块执行。流程如下图:

684.png

Js 调用Java

如果消息队列中有等待Java 处理的逻辑,而且 Java 超过 5ms 都没有来取走,那么 JavaScript 就会主动调用 Java 的方法,在需要调用调Java模块方法时,会把参数{moduleID,methodID}等数据存在MessageQueue中,等待Java的事件触发,把MessageQueue中的{moduleID,methodID}返回给Java,再根据模块注册表找到相应模块处理。流程如下图:

WechatIMG1 1.png

九、更多参考

上一篇 下一篇

猜你喜欢

热点阅读