ReactNative到底做了什么?

React Native 是什么

react.js是一个流行的前端开发框架,相信大家都有了解,它用了flexbox布局技术、jsx等语法糖的技术,这让前端同学能象喝茶一样轻松地开发H5页面。React本来是用来开发h5页面,facebook的工程师们,希望做一套能同时运行在iOS和Android的高性能开发框架,就将React render()方法渲染出来的js代码通过JSbridge加载到native,native再通过JSCore和V8这两个js解析引擎,解析为native的页面。

React Native 的组成

ReactNative功能结构可以分为两部分,JS模块和Native模块。JS模块和Native模块都有Bridge组件,两端的Bridge遵守一套通信协议、通信机制,从而实现了js和native的无缝连接。

JS模块

JS模块主要由React.js框架和JSBridge两部分组成,React.js是开发UI逻辑的框架,而JSBridge包含管理NativeModel配置的NativeModules和管理js、native双向通信的MessageQueue。

JSBridge如何匹配和调用Native提供的能力

Nativebridge初始化时会把Native能提供给JS的所有能力Module按约定好的方式序列化为一个对象‘__fbBatchedBridgeConfig’,设置到JS的全局对象global里。

JSBridge封装了NativeModules对象,NativeModules会读取__fbBatchedBridgeConfig的bridgep配置,组装成方便js调用的映射对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let NativeModules : {[moduleName: string]: Object} = {};
const bridgeConfig = global.__fbBatchedBridgeConfig;
(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (info.module) {
NativeModules[info.name] = info.module;
}
// If there's no module config, define a lazy getter
else {
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID)
});
}
});

js组件通过methodName可以调用Native提供的各项能力。

1
NativeModules.DialogManagerAndroid.showAlert();

MessageQueue实现了Native call JS的Api和js call native的机制

MessageQueue里有4个暴露给Native调用的API

1
2
3
4
flushedQueue()
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[])
callFunctionReturnFlushedQueue(module: string, method: string, args: any[])
callFunctionReturnResultAndFlushedQueue(module: string,method: string,args: any[],)

Native想要调用JS的方法
JS也会通过Module封装提供给Native调用的组件和方法,当native需要主动调用时就会call”callFunctionReturnFlushedQueue”和“callFunctionReturnResultAndFlushedQueue”,并传递module、method做为找到对应方法的钥匙。

JS想要调用Native的方法
事实上JS不能直接调用Native的方法,RN里设置了被动调用的机制来实现相同的功能。
当JS要调用Native的方法时,会通过enqueueNativeCall把moduleID和methodID放到队列this._queue里,调用成功和失败的Callback放到this._successCallbacks和this._failureCallbacks里。

1
enqueueNativeCall(moduleID: number,methodID: number,params: any[],onFail: ?Function,onSucc: ?Function,)

每次Native调用JS时,都会把this._queue的消息通过返回值传递给Native,Native拿到消息队列后就会执行MessageQueue里对应的Native方法。

TODO: js要调用Native时方法注入queue的时机?

调用Native方法执行后的回调
native暴露给js调用的方法,可以使用RCTResponseSenderBlock对象做为参数实现回调的功能。RCTResponseSenderBlock执行最后会调用到invokeCallbackAndReturnFlushedQueue方法,回调MessageQueue里成功或失败的回调。

JS消息队列超过5ms还未被调用
当JS通过enqueueNativeCall注入需要被Native调用的方法时,需要等待native来拉去消息队列。如果native一直不过来拉消息怎么办呢?在enqueueNativeCall注入时有一个保障机制,如果距离上次消息队列被拉去超过5ms,就会调用‘global.nativeFlushQueueImmediate(queue)’方法,启用JS强制让Native拉去的机制。

1
2
3
4
5
6
7
if (global.nativeFlushQueueImmediate && (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS || this._inCall === 0)
) {
var queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}

Native部分(OC)

native部分可分为OC bridge和一系列基础Module和自定义的扩展Module.

Module
每一个Module都会根据约定的协议去注册,Bridge初始化时就会读取Navtive所有的Module,序列化为配置表,并将这部分配置信息传递到JS模块,JS也会生成一一对应配置表,这样无论是js想调用native的api,还是native想调用js的api都可以找到。

NativeBridge
Nativebridge需要做很多事情,它包含了JSbundle加载、初始化缓存Module组件、管控js和oc之间消息传递的分发。

下面是NativeBridge初始化的过程

  • RCTRootView 根视图
    • RCTBridge 桥的抽象类。持有BatchBridge,将核心逻辑转发给BatchBridge实现
      • RCTBatchBridge 负责主要核心功能的初始化
        • RCTJavaScriptLoader 加载JSBundle
        • RCTDisplayLink 提供屏幕渲染频率的回调,为timer和桢动画等组件提供支持
        • RCTModuleData 所有的RN组件UI或Api,都是RCTModuleData
          • RCTJSCExecutor 一个特殊的RCTModuleData,维护一个独立线程处理JS代码执行和js回调,是bridge的核心通道
          • RCTEventDispatcher UIModule触发点击事件后先通过EventDispatcher的api进行事件分发,再传到JSCExecutor找到JS模块的回调函数,最后通知js进行处理

Module配置表生成流程解析

RCTModuleData是组装native的功能模块,提供给JS调用

native导出注册的Modules给JS

RCTRootView初始化流程

  • 初始化Module
  • 生成Module配置表
  • 将Module配置表注入到JS里
  • JS读取Module配置

初始化Module
从RCTModuleClasses拿到所有Moduled的Class,各自创建RCTModuleData,并将创建后的module保存到moduleClassesByID、moduleDataByID、moduleDataByName三个集合进行缓存

生成Module配置表
RCTBatchBridge会循环moduleDataByID数组表,把每一个APIModule的name都写进数组,然后写进key为remoteModuleConfig的字典,最后序列化成JS

1
2
3
4
{"remoteModuleConfig":[["VKAlertModule"],
["RCTFileRequestHandler"],
["RCTDataRequestHandler"],
...]}

moduleConfigInject

将所有module名注入到__fbBatchedBridgeConfig

1
{"remoteModuleConfig":[["JSCExecutor"],["AccessibilityManager"],["ViewManager"],["ActivityIndicatorViewManager"],["AlertManager"],["AppState"],["AsyncLocalStorage"],["BlobModule"],["Clipboard"],["DataRequestHandler"],["DatePickerManager"],["DeviceInfo"],["DevLoadingView"],["DevMenu"],["DevSettings"],["EventDispatcher"],["ExceptionsManager"],["FileRequestHandler"],["HTTPRequestHandler"],["I18nManager"],["JSCSamplingProfiler"],["KeyboardObserver"],["MaskedViewManager"],["ModalHostViewManager"],["ModalManager"],["NavigatorManager"],["NavItemManager"],["NetInfo"],["Networking"],["PerfMonitor"],["PickerManager"],["PlatformConstants"],["ProgressViewManager"],["RawTextManager"],["RedBox"],["RefreshControlManager"],["SafeAreaViewManager"],["ScrollContentViewManager"],["ScrollViewManager"],["SegmentedControlManager"],["SliderManager"],["SourceCode"],["StatusBarManager"],["SwitchManager"],["TabBarItemManager"],["TabBarManager"],["TextFieldManager"],["TextManager"],["TextViewManager"],["Timing"],["TVNavigationEventEmitter"],["UIManager"],["WebSocketExecutor"],["WebSocketModule"],["WebViewManager"]]}

遍历所有Module,将每个Module被注册的method配置,注入到JSContext的nativeRequireModuleConfig里。的block注入到js的global全局对象里

1
2
3
4
5
context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
RCTJSCExecutor *strongSelf = weakSelf;
NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
return RCTNullIfNil(result);
};

JS读取Module配置

JS加载时NativeModules.js会执行一段脚本,先通过global.__fbBatchedBridgeConfig获取Native所有Module的列表,再通过’global.nativeRequireModuleConfig’方法拿到Native所有Module的配置。

1
2
3
4
5
6
7
8
9
10
const bridgeConfig = global.__fbBatchedBridgeConfig;
invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules');
const defineLazyObjectProperty = require('defineLazyObjectProperty');
(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {
const info = genModule(config, moduleID);
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID)
});
});

注册Module

RCT_EXPORT_MODULE()

导出方法

RCT_EXPORT_METHOD()

ReactNativeUI渲染解析

React开发的页面,最终都会解析回原生的JS标签,最终组装成JSBundle,Native初始化后会加载JSBundle,并将UI逻辑一一解析成Native的原生控件,整个页面会拼装为一个Native的RootView。
这个过程需要解析JS的UI逻辑,于是RN定一个一个Module专门用来做UI解析,它就是UIManage,在JS是UIManager.js,在Native时RCTUIManager和RCTComponentData。
RCTComponentData组装Native各种视图控件,将所有视图组件的配置信息提供给JS,js根据native提供的配置信息,创建Native视图。

UIModule的组成
UIModule包含RCTView和RCTViewManage,每个UIModule还对应一个RCTComponentData。RCTViewManage相当于Controller了,决定如何绘制RCTView。

管理UI组件生命周期的RCTUIManager和RCTComponentData
RCTUIManager是一个ApiModule,所以他是被RCTModuleData管理,并被RCTBatchBridge持有。

RCTUIManager在初始化的时候setBridge方法会被调用,在setBridge里会遍历所有的Module,找到所有继承自RCTViewModule的对象,然后以ModuleName为key缓存到_componentDataByName里

UI的创建和修改
React.js里UI组件由各个component组成,component使用flexbox进行布局,最后会转换成绝对的位置、样式。
Native里会对React的每个组件都实现对应的一个View,每一个View会继承RCTViewManager,并通过RCT_EXPORT_MODULE注册成为一个module,我们可以称他们为UIModule。
当一个JS Component对象需要创建/改变自己的样式时,React会把需要渲染的所有component生成对应的JS Component配置,Native根据配置找到对应的RCTView,根据配置信息初始化Native的UI组件。

RN一个页面的渲染流程

1.[RCTRootView runApplication:bridge]
通知JS准备好可以开始渲染

1
2
3
4
5
6
7
8
9
10
11
12
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _appProperties ?: @{},
};
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}

2.由JS调用createView方法创建每一个Native的View,这个方法里会根据viewName找到对应的RCTComponentData。
调用RCTComponentData的createShadowViewWithTag创建shadow view,缓存到_shadowViewRegistry里。
调用RCTComponentData的createViewWithTag创建view,缓存到_viewRegistry里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
viewName:(NSString *)viewName
rootTag:(nonnull NSNumber *)rootTag
props:(NSDictionary *)props){
RCTComponentData *componentData = _componentDataByName[viewName];
// Register shadow view
RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
if (shadowView) {
[componentData setProps:props forShadowView:shadowView];
_shadowViewRegistry[reactTag] = shadowView;
RCTShadowView *rootView = _shadowViewRegistry[rootTag];
shadowView.rootView = (RCTRootShadowView *)rootView;
}
__weak RCTUIManager *weakManager = self;
RCTExecuteOnMainQueue(^{
RCTUIManager *uiManager = weakManager;
UIView *view = [componentData createViewWithTag:reactTag];
if (view) {
[componentData setProps:props forView:view]; // Must be done before bgColor to prevent wrong default
uiManager->_viewRegistry[reactTag] = view;
}
}

3.调用_layoutAndMount对RCTRootView及其子视图进行布局。
在RCTUIManage->uiBlockWithLayoutUpdateForRootView->collectViewsWithUpdatedFrames,将flexbox布局协议解析为绝对布局的Frame,将每个视图的frame设置到对应的shaowView里。

4.根据组件视图关系,由JS调用“setChildren”给每个View设置子Views。
其中创建一个View都会对应创建一个RCTShadowView,RCTShadowView保存View的布局关系和属性,管理View的加载

1
2
RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag
reactTags:(NSArray<NSNumber *> *)reactTags)

5.通过RCTShadowView的processUpdatedProperties,调用每个View的didUpdateReactSubviews方法,添加各自的子视图。

1
2
3
4
5
6
- (void)didUpdateReactSubviews
{
for (UIView *subview in self.reactSubviews) {
[self addSubview:subview];
}
}

布局计算核心算法
我们写React时使用的是flex布局或绝对布局,这部分布局代码会随着jsbundle通过Native的JS引擎加载。但Native是识别不了Flex布局协议,所以需要通过facebook的yoga开源库来解析flex布局协议成为绝对Native可以识别的绝对布局。

  • YGNodeCalculateLayout
    • YGLayoutNodeInternal
    • YGNodelayoutImpl
      • YGNodeWithMeasureFuncSetMeasuredDimensions 计算size
      • YGNodeComputeFlexBasisForChild 计算child的布局

UI组件事件响应
当RCTView组件触发了点击、滑动等触摸事件时,会通过bridge找倒自己的JSComponent,根据预先缓存的js callback函数,将参数传递给对应的React组件进行响应。

自定义UIModule组件
1 创建RCTViewManager子类
a注册Module,使用RCT_EXPORT_MODULE注册宏
b实现视图创建的方法,’-(UIView *)view’
cJS层导入Native原生组件。在js模块的requireNativeComponent.js里操作UIManage导入。
2 创建属性
注册属性,使用RCT_EXPORT_VIEW_PROPERTY或RCT_CUSTOM_VIEW_PROPERTY
3 创建事件
注册事件,也是通过RCT_EXPORT_VIEW_PROPERTY,注册type为RCTBubblingEventBlock的事件属性
4 创建常量
a.native通过constantsToExport方法return一个NSDictionary注册常量
b.js通过UIManager.XXUIModule.Constants拿到注册的常量

Ref

react-native
React Native通信机制详解
yoga
ReactNative源码分析从源码一步一步解析它的实现原理
W3C 标准的 Flexbox 模型
CSS Flex Layout 算法解析

Blacktea wechat
ex. subscribe to my blog by scanning my public wechat account
记录生活于感悟,您的支持将鼓励我继续创作!