前言
先演示一段官方最简单的demo
RCTRootView 入口
-
RCTRootView是RN应用承载页面的容器,分析就从RCTRootView开始
-
-
在需要使用到RN的页面中使用RCTRootView 初始化一个实例,入参是一个RCTBridge和moduleName
JSBridge 初始化
- RCTBridge通过传入JSBundle的地址进行初始化,会进入一系列的setup工作
在setUp中调用start方法
在start方法中调用 excuteSourceCode 方法
在excuteSourceCode中调用loadScriptFromString方法
执行到 instance的 loadBundle方法
执行到NativeToJsBridge中的loadBundle方法
继续执行到 JSIExecutor的方法
最终执行到JSCRuntime:evaluateJavaScript 方法,内部调用了 JavaScriptCore::JSEvaluateScript 方法。
整个加载JSBundle 的流程是这样子:
- 结束了解析执行JSBundle后,并执行目录下index.js,这里先不管它是如何执行到 JSBundle 中的index.js,只要知道这个index.js 是RN js 侧工程根目录底下的唯一一个叫 index.js文件,也称为入口文件
JS侧初始化
bundle 加载成功之后,会走到bundleFinishedLoading方法,接着执行 runApplication 方法,往bridge中传递module为AppRegistry,method为runApplication的 js 调用,目前暂时先不讨论如何实现的
-
Bridge会通过这个参数调用到对应js的方法,并查找出runables数组中的组件并且调用run方法
-
还记得上面 解析执行完JSBundle 会执行 index.js 中的 registerComponenet 方法么
-
component会将自身注册进runables数组中,为了后续运行
-
run 方法执行 renderApplication,传入componentProvider,即是index.js 中注册的 app() 首页组件
-
renderApplication 传入的 RootComponent 参数,也是 外层componentProvider包裹了 性能检测logger形成的组件
最终形成renderable 组件传递给Renderer/shinms/ReactNative类的 render方法
updateContainer->scheduleUpdateOnFiber->renderRootSync->workLoopSync->completeUnitOfWork->completeWork->createInstance
经过较长的调用链之后来到了createInstance的方法,最终会调用原生的UIManager.createView的桥接方法
这里回到原生代码 createView 方法
原生的类如何提供给JS调用
-
这里切换到原生的类如何提供出来给JS调用
-
所有需要给js调用的类都会使用 RCT_EXPORT_MODULE()宏定义
-
iOS会在启动加载二进制时调用load方法
-
所有使用RCT_EXPORT_MODULE()标识的类,会在启动的时候将自身加入到RCTModuleClasses中
同样导出方法会使用RCT_EXPORT_METHOD宏定义导出
宏替换经过层层嵌套最终是这样子:
以上的宏替换会被替换为:
+(const RCTMethodInfo *)__rct_export__(__LINE__, __COUNTER__) {
static RCTMethodInfo config = {"", "test:(NSString *)name", NO};
return &config;
}
(LINE, COUNTER) 这里是当前的代码行数(__LINE__)加上预编译的次数(__COUNTER__),
__COUNTER__ 是一个计数器,每次执行都会加1
eg.
+(const RCTMethodInfo *)__rct_export__9424 {
static RCTMethodInfo config = {"", "test:(NSString *)name", NO};
return &config;
}
RN会遍历所有以__rct_export__为前缀的方法并执行以导出曝露给 JS 的接口,细节在后续会讲解
已经大致知道了类导出和方法导出,我们回到刚才的视图创建:
这里使用了RCTShadowView,RCTShadowView是一个布局的虚拟视图,内部使用yoga进行布局,yoga是一个布局工具,不同于iOS自动布局AutoLayout
接下来使用setProps:对shadowView 进行属性设置,这里又要讲到区别类导出,方法导出的第三种导出:属性导出, 同样属性导出也使用宏替换:
eg.
+ (NSArray<NSString *> *)propConfig_backgroundColor {
return @[@"UIColor"];
}
最终通过遍历所有propConfig开头的方法作为方法选择子,将json转为原生相应的类型并通过方法调用设置值
完成了视图的初始化,需要将视图加入到视图树中,同样到这里会调用原生方法UIManager.setChildren方法
调用来到原生端:
- 就如APP.js 中这个视图结构
- 所有遵循RCTComponent协议的视图组件,会按照树形结构将视图加入到yoga视图树进行布局
最终页面呈现出来是这样:
- 因为布局用的是yoga,所以没有像AutoLayout一样的约束线条
JS 和 native 通讯
获取RCTModuleClasses数组
还记得启动的时候会将通过EXPORT_MODULE宏定义导出的类注册进RCTModuleClasses数组,会在bridge初始化之后,通过_initializeModules->_registerModulesForClasses:,增加一些必要的参数,糅合成RCTModuleData结构的数组成为NativeModules
生成Instance
- 在初始化RCTCxxBridge时,start->_initializeBridge->_initializeBridgeLocked->_buildModuleRegistryUnlocked的调用链
会将原生的RCTModuleData结构的数组转成NativeModule结构的数组
包装进c++ ModuleRegistry类中
并且实例化出c++ Instance类
Native调用JS
Native 可以通过 这种方式对JS发起调用 enqueueJSCall()
Native调用JS可以使用RCTCxxBridge:enqueueJSCall:method:args: 来调用,内部调用了reactInstance的callJSFunction
nativeToJsBridge
nativeToJsBridge的初始化是在Instance初始化的时候调用initializeRuntime
这里会往js上下文中设置几个全局的属性nativeFlushQueueImmediate,nativeModuleProxy,nativeCallSyncHook,可以在js侧使用global.nativeFlushQueueImmediate来调用
同样在调用callFunction时,判断如果flushQueue不存在的时候,会去执行bindBridge()
bindBridge中会获取js上下文的全局属性callFunctionReturnFlushedQueue函数,这是在js侧声明的属性方法
调用callFunctionReturnFlushedQueue_->call,最终会调用到JavaScriptCore::JSObjectCallAsFunction方法,并且调用到MessageQueue.js的调用callFunctionReturnFlushedQueue->__callFunction()调用方法
流程图
JS调用Native
JS 可以通过 这种方式对Native发起调用 NativeModules.customModule.customFunction()
NativeModules.js在初始化的时候会获取global.nativeModuleProxy
还记得JSIExecutor 在初始化的时候,会在js上下文注入一个全局属性,nativeModuleProxy
每次访问NativeModules.xxx,就会访问到getter方法
接着调用JSINativeModules::getModule,通过模块名,找到模块对象
createModule会获取js侧global中的__fbGenNativeModule并且执行call方法
即调用genModule方法
在内部调用genMethod方法,生成js侧方法,并且在promise中执行enqueueNativeCall
BatchedBridge就是MessageQueue.js 的实例,在方法中,有两种模式,如果超过一定时间阈值会直接调用global.nativeFlushQueueImediate,否则会放入this.queue队列,等待原生调用
直接调用
先看看直接调用会是什么样子
同样是预先注入js 上下文中的原生属性,这时候会调用callNativeModules,通过moduleId,methodId,arguments,callId,最终通过原生的方法调用
原生拉取
再看看原生拉取是什么样子
- 原生拉取是通过runloop的事件接收触发,手势,timer
到JSIExecutor::callFunction->JSContext.callFunctionReturnFlushedQueue_->call
JS 会先根据 moduleId 和 methodId 完成对 JS 侧业务的调用,然后会执行 flushedQueue 函数将队列清空,再将队列数组返回给 Native 侧
Native 侧拿到队列就会继续执行 JSIExecutor->callNativeModules->ModuleRegistry
流程图
参数传递和回调
在调用enqueuNativeCall时,传递的方法是moduleID,methodID,onFail,onSuceess
由于global.__fbGenNativeModule = genModule;原生调用__fbGenNativeModule传递ModuleConfig
js侧触发genModule方法,这是ModuleConfig的信息
在调用是传递的参数即使ModuleConfig中对应的ID
回调
会生成一个callbackID并使用map,将js的回调和callbackID关联
原生在触发调用时,NativeToJsBridge::callNativeModules -> ModuleRegistry::callNativeMethod -> RCTNativeModule::invoke -> MethodCallResult::invokeInner,会处理原生的方法签名,当发现回调是reposenSenderBlock时,会在内部调用主动调用 enqueueCallback 方法
原生通过传递回callbackID,以及参数,完成回调