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调用的映射对象.
|
|
js组件通过methodName可以调用Native提供的各项能力。
|
|
MessageQueue实现了Native call JS的Api和js call native的机制
MessageQueue里有4个暴露给Native调用的API
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里。
|
|
每次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拉去的机制。
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进行处理
- RCTBatchBridge 负责主要核心功能的初始化
- RCTBridge 桥的抽象类。持有BatchBridge,将核心逻辑转发给BatchBridge实现
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
|
|
moduleConfigInject
将所有module名注入到__fbBatchedBridgeConfig
遍历所有Module,将每个Module被注册的method配置,注入到JSContext的nativeRequireModuleConfig里。的block注入到js的global全局对象里
|
|
JS读取Module配置
JS加载时NativeModules.js会执行一段脚本,先通过global.__fbBatchedBridgeConfig获取Native所有Module的列表,再通过’global.nativeRequireModuleConfig’方法拿到Native所有Module的配置。
|
|
注册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准备好可以开始渲染
|
|
2.由JS调用createView方法创建每一个Native的View,这个方法里会根据viewName找到对应的RCTComponentData。
调用RCTComponentData的createShadowViewWithTag创建shadow view,缓存到_shadowViewRegistry里。
调用RCTComponentData的createViewWithTag创建view,缓存到_viewRegistry里
|
|
3.调用_layoutAndMount对RCTRootView及其子视图进行布局。
在RCTUIManage->uiBlockWithLayoutUpdateForRootView->collectViewsWithUpdatedFrames,将flexbox布局协议解析为绝对布局的Frame,将每个视图的frame设置到对应的shaowView里。
4.根据组件视图关系,由JS调用“setChildren”给每个View设置子Views。
其中创建一个View都会对应创建一个RCTShadowView,RCTShadowView保存View的布局关系和属性,管理View的加载
5.通过RCTShadowView的processUpdatedProperties,调用每个View的didUpdateReactSubviews方法,添加各自的子视图。
|
|
布局计算核心算法
我们写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 算法解析