React Native原理之跨端通信机制
Posted 网易云音乐技术团队
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Native原理之跨端通信机制相关的知识,希望对你有一定的参考价值。
和图片来源:https://unsplash.com/photos/gy08FXeM2L4
MethodId
能定位到具体调用的原生方法,params
参数作为原生方法调用的参数,最后通过 CallbackId
回调 JS 的回调函数,H5 就能从回调函数中拿到调用结果。该流程中主要使用了 Webview 容器中拦截请求
和客户端调用 JS 函数
的能力,比如安卓中通常使用的是 WebChromeClient.onJsPrompt
方法来拦截 H5 的请求,evaluatejavascript
方法用来执行回调。但是 React Native 中没有引入 Webview 来实现这些调用的能力,它采用了完全不同的方式来处理。另外,在云音乐团队的 APP 中, 会同时存在 H5 和 RN 页面,也就是同一个 APP 中两种跨端通信方式并存,但它们最后调用的原生方法却是来自同一个原生模块。本文主要从 android 系统的 RN 实现来介绍 RN 的通信机制和桥接能力(以下简称 Bridge),并结合以上通信场景中会碰到的问题来讲解如何实现一个业务中可用的 Bridge。大体由三部分组成,首先介绍 RN 中不同的组成模块和它们各自的角色;第二部分是各个模块之间的调用方式和具体的示例;最后一部分探讨业务中的 Bridge 的实现。作为 JS 的执行引擎,而 JSCore
的对外接口是用 C 和 C++ 编写的。因此平台层的 Java 代码 / OC 代码想要通过 JSCore
拿到 JS 的模块和回调函数,只能通过 C++ 提供的接口获取,再加上 C++ 在 ios 和安卓系统上也有良好的跨端运行的功能,选它作为桥接层是不错的选择。在 JSCore 环境中执行一段 JS 代码,也可以通过 JSContextGetGlobalObject
拿到 JS 上下文的 Global 变量,然后把它转化成 C++ 可以使用的数据结构并且操作它,注入 API。而 JSObjectSetProperty
和 JSContextGetGlobalObject
也是比较重要的两个 API ,稍后会在通信流程中发挥作用。目录下,可以给 RN 页面开放原生系统的能力,如计时器的实现模块 Timing
,给 JS 代码提供计时器的能力。/Libraries/ReactNative/
目录下,如 App 启动模块 AppRegistery
,对 Java 环境来说,作用是提供操作 JS 环境的 API,如回调,广播等。Java 的调用方法是通过 JS 暴露出来的 callFunctionReturnFlushedQueue
API。JS 环境中会维护一份所有 Native 模块的 moduleID 和 methodID 的映射 NativeModules
,用来调用 Native 模块的时候查找对应 ID;Java 环境中也会维护一份 JavaScript 模块的映射 JSModuleRegistry
,用来调用 JS 代码。而在实际的代码中,Native 模块和 JS 模块的通信需要通过中间层也就是 C++ 层的过渡,也就是说 Native 模块和 JS 模块实际上都只是在和 C++ 模块进行通信。
callFunctionReturnFlushedQueue // 让 C++ 调用 JS 模块
invokeCallbackAndReturnFlushedQueue // 让 C++ 调用 JS 回调
flushedQueue // 清空 JS 任务队列
callFunctionReturnResultAndFlushedQueue // 让 C++ 调用 JS 模块并返回结果
__fbGenNativeModule
方法,用来给 C++ 调用后在 JS 环境生成 Java 模块的映射对象,也就是 NativeModules
模块。它的数据结构类似于(跟实际的数据结构有偏差):
"Timing":
"moduleID": "1001",
"method":
"createTimer":
"methodID": "10001"
NativeModules
的映射,开发者能拿到调用模块和方法的 moduleID
和 methodID
,在调用过程中会映射到具体的 Native 的方法。同样的,C++ 通过 JSCore 的 JSObjectSetProperty
方法在 global 对象中塞入了几个 Native API,让 JS 能通过它们来调用 C++ 模块。主要 API 有:
nativeFlushQueueImmediate // 立即清空 JS 任务队列
nativeCallSyncHook // 同步调用 Native 方法
nativeRequire // 加载 Native 模块
MessageQueue
中,然后等待 Native 的调用。调用时机一般是在触发事件的时候,事件会触发 Native 回调 JS 的回调函数,Native 模块需要通过 __fbBatchedBridge
的四个 API 回调 JS 代码,而这四个 API,都有 flushedQueue
功能:清空任务队列并执行所有的任务,借此来消费队列中的 Native 调用任务。但是如果某一次调用距离上一次的 flushedQueue
行为有点久(一般是大于 5 ms),就会触发立即调用的逻辑,JS 调用 nativeFlushQueueImmediate
API,主动触发任务消费。jniCallJSFunction
会调用 callFunctionReturnFlushedQueue
。jniCallJSCallback
会调用 invokeCallbackAndReturnFlushedQueue
。由此,三个模块的调用链路就连接了起来。// 源代码位置:/Libraries/Core/Timers/JSTimers.jsconst Timing = require(\'../../BatchedBridge/NativeModules\');
function setTimeout(func: Function, duration: number, ...args: any): number
// 创建回调函数
const id = _allocateCallback(
() => func.apply(undefined, args),
\'setTimeout\',
);
Timing.createTimer(id, duration || 0, Date.now(), /* recurring */ false);
return id;
,
setTimeout 的调用过程
当 setTimeout 在 JSTimer.js 被调用,通过 NativeModules 找到 Timing Class 的 moduleID 和 methodID,放进任务队列 MessageQueue
中;
Native 通过事件或者主动触发清空 MessageQueue
队列,C++ 层把 moduleID ,methodID 和其他调用参数交给 ModuleRegistry
,由它来找到 Native 模块的代码,Timing 类;
Timing 调用 createTimer
方法,调用系统计时功能实现延迟调用;
计时结束,Timing 类需要回调 JS 函数
// timerToCall 是回调函数的 ID 数组
getReactApplicationContext().getJSModule(JSTimers.class)
.callTimers(timerToCall);
getJSModule
方法会通过 JSModuleRegistry
找到需要调用的 JS 模块,并调用对应的方法,该流程中调用 JSTimers
模块的 callTimers
方法。
Java 代码通过 JNI 接口 jniCallJSFunction
通过 C++ 调用 JS 模块,并传入 module:JSTimers
和 method:callTimers
;
C++ 调用 JS 暴露出来的 callFunctionReturnFlushedQueue
API,带上 module 和 method,回到 JS 的调用环境;
JS 执行 callFunctionReturnFlushedQueue
方法找到 RN 初始化阶段注册好的 JSTimer 模块的 callTimers
函数,进行调用。调用完毕后清空一下任务队列 MessageQueue
。
RCTDeviceEventEmitter
是一个纯 JS 实现的事件订阅分发模块,Native 模块通过 getJSModule
可以拿到它的方法,因此可以在 Native 端发出一个 JS 事件并带上回调的参数和映射 ID 等,而不用走 JSCallback。回到之前的问题:如何实现 RN 的 Bridge,能让一个 Bridge 的 API 同时支持 H5 和 RN 的调用。因为 H5 和 RN 大多数的业务场景都是相同的,比如获取用户信息 user.info,设备信息 device.info 类似的接口,在 H5 和 RN 中都是会用到的。除了跨端调用的协议要保持一致外,具体的实现模块,协议解析模块都是可以复用的。其中不一样的就是调用链路。RN 链路中的主要模块包括:
RNRPCNativeModule
;RNRPCNativeModule
使用 RCTDeviceEventEmitter
生成一个事件回调给 JS 代码,并带上执行结果。除了以上两个不一样的模块外,其他模块都是可以复用的,如协议解析和任务分发模块,解析协议的调用模块,方法,参数等,并把它分发给具体的 Native 模块;还有 Native 具体的功能实现模块,都可以保持一致。
结合前面介绍的调用流程,开发者如果调用 User.info
这个 JSBridge 来获取用户信息,调用流程如下:
这样的处理,能保证 H5 和 RN 能用同一份 moduleID 和 methodID 来调用 Native 的功能,而且保证在同一个模块进行处理。从开发者的角度来看,就是一个 Bridge 的 API 可以同时支持 H5 和 RN 的调用。
以上。
相关资料React Native 源代码[1]
React Native 原生模块和 JS 模块交互[2]
Handler 与 Looper,MessageQueue 的关系[3]
React Native 通信机制详解[4]
React Native 源码解析[5]
How React Native constructs app layouts[6]
React Native 源代码: https://github.com/facebook/react-native
[2]React Native 原生模块和 JS 模块交互: https://danke77.github.io/2016/12/07/react-native-native-modules-android/
[3]Handler 与 Looper,MessageQueue 的关系: https://www.cnblogs.com/fuly550871915/p/4889838.html
[4]React Native 通信机制详解: http://blog.cnbang.net/tech/2698/
[5]React Native 源码解析: https://github.com/sucese/react-native/tree/master/doc/ReactNative%E6%BA%90%E7%A0%81%E7%AF%87
[6]How React Native constructs app layouts: https://www.freecodecamp.org/news/how-react-native-constructs-app-layouts-and-how-fabric-is-about-to-change-it-dd4cb510d055/
React Native 源码分析——通信机制
本篇来分析一下,RN js和java的通信机制,在上一篇启动流程 看完后,通信的过程,你应该也能猜出个大概。具体过程,也是很简单
1、React Native 源码分析(一)—— 启动流程
2、React Native 源码分析(二)—— 通信机制
3、React Native 源码分析(三)—— UI渲染流程
4、React Native 源码分析(四)—— 任务调度
5、React Native 源码分析(五)—— 事件分发
一、 Java->JS 通信
先来一张图,看一下通信的整体过程
图片来源
这里接着上一篇的代码1.9 reactRoot.runApplication();
继续分析,从java启动react的过程
1.1 runApplication
@Override
public void runApplication()
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.runApplication");
try
if (mReactInstanceManager == null || !mIsAttachedToInstance)
return;
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext == null)
return;
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
//在自己的activity中,指定的mainComponentName名称
String jsAppModuleName = getJSModuleName();
//onMeasure执行后,该变量会设为true,如果再次调用到runApplication,需要重新绘制
if (mWasMeasured)
//本质是会通过FabricUIManager或UIManagerModule 来更新界面
updateRootLayoutSpecs(true, mWidthMeasureSpec, mHeightMeasureSpec);
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", getRootViewTag());
@Nullable Bundle appProperties = getAppProperties();
if (appProperties != null)
appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
mShouldLogContentAppeared = true;
//启动js中的 AppRegistry register的Component,并传入参数appParams
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
finally
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
getJSModule的调用流程catalystInstance.getJSModule ->mJSModuleRegistry.getJavaScriptModule
,调用到了JavaScriptModuleRegistry中,这里是通过动态代理的方式,让 继承了JavaScriptModule的Interface中的方法的 调用,都被代理到 mCatalystInstance.callFunction
2、JavaScriptModuleRegistry # getJavaScriptModule
public final class JavaScriptModuleRegistry
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
public JavaScriptModuleRegistry()
mModuleInstances = new HashMap<>();
//catalystInstance.getJSModule 调到这里
public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
CatalystInstance instance, Class<T> moduleInterface)
JavaScriptModule module = mModuleInstances.get(moduleInterface);
if (module != null)
return (T) module;
JavaScriptModule interfaceProxy =
(JavaScriptModule)
Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
//此时指的是AppRegistry.class
new Class[] moduleInterface,
new JavaScriptModuleInvocationHandler(instance, moduleInterface));
mModuleInstances.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
private static class JavaScriptModuleInvocationHandler implements InvocationHandler
private final CatalystInstance mCatalystInstance;
private final Class<? extends JavaScriptModule> mModuleInterface;
private @Nullable String mName;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance, Class<? extends JavaScriptModule> moduleInterface)
mCatalystInstance = catalystInstance;
mModuleInterface = moduleInterface;
...
....
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable
NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
//getJSModuleName 获取js module的类名,method.getName() 调用的方法名,jsArgs 传入的参数
//下面分析该函数
mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs);
return null;
....
1.3 CatalystInstance的函数 callFunction 和 runJSBundle
public void callFunction(PendingJSCall function)
if (mDestroyed)
final String call = function.toString();
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed: " + call);
return;
//如果js bundle还未加载,就把任务加入到mJSCallsPendingInit,直接返回
if (!mAcceptCalls)
// Most of the time the instance is initialized and we don't need to acquire the lock
synchronized (mJSCallsPendingInitLock)
if (!mAcceptCalls)
mJSCallsPendingInit.add(function);
return;
function.call(this);
mAcceptCalls 的初始值是false,callFunction
和 runJSBundle
都是从React Native 源码分析(一)—— 启动流程的代码1.4的 runCreateReactContextOnNewThread,callFunction 是在nativeModule线程运行,由于并发的原因,为了保证js bundle加载后,再去调用js的函数。引入mAcceptCalls 来控制,
@Override
public void runJSBundle()
//上篇文章代码1.4分析的,mJSBundleLoader 有几种类型,由js bundle的位置来决定
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
synchronized (mJSCallsPendingInitLock)
// Loading the bundle is queued on the JS thread, but may not have
// run yet. It's safe to set this here, though, since any work it
// gates will be queued on the JS thread behind the load.
mAcceptCalls = true;
for (PendingJSCall function : mJSCallsPendingInit)
function.call(this);
mJSCallsPendingInit.clear();
mJSBundleHasLoaded = true;
最终都是调用了function.call(this);
,下面来分析该函数
1.4 PendingJSCall # call
void call(CatalystInstanceImpl catalystInstance)
NativeArray arguments = mArguments != null ? mArguments : new WritableNativeArray();
//这里调用到c++ 层
catalystInstance.jniCallJSFunction(mModule, mMethod, arguments);
1.5 CatalystInstanceImpl::jniCallJSFunction
void CatalystInstanceImpl::jniCallJSFunction(
std::string module,
std::string method,
NativeArray *arguments)
// We want to share the C++ code, and on iOS, modules pass module/method
// names as strings all the way through to JS, and there's no way to do
// string -> id mapping on the objc side. So on Android, we convert the
// number to a string, here which gets passed as-is to JS. There, they they
// used as ids if isFinite(), which handles this case, and looked up as
// strings otherwise. Eventually, we'll probably want to modify the stack
// from the JS proxy through here to use strings, too.
instance_->callJSFunction(
std::move(module), std::move(method), arguments->consume());
1.6 Instance::callJSFunction
void Instance::callJSFunction(
std::string &&module,
std::string &&method,
folly::dynamic &¶ms)
//该回调是在上篇文章的代码1.7 的new BridgeCallback(this) 创建的
callback_->incrementPendingJSCalls();
//该变量 在上篇文章,也有介绍,下面直接看callFunction
nativeToJsBridge_->callFunction(
std::move(module), std::move(method), std::move(params));
1.7 NativeToJsBridge::callFunction
void NativeToJsBridge::callFunction(
std::string &&module,
std::string &&method,
folly::dynamic &&arguments)
...
//把lambda代码发送到js线程队列,所以java->js的通信是异步的
runOnExecutorQueue([this,
module = std::move(module),
method = std::move(method),
arguments = std::move(arguments),
systraceCookie](JSExecutor *executor)
if (m_applicationScriptHasFailure)
...
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
//executor 这里作为lambda的parameter,argument 传的是m_executor(上篇文章代码2.5 被赋值)
executor->callFunction(module, method, arguments);
);
最终调用m_executor ,(本例)也就是JSIExecutor 的callFunction,(也可能是其他的jsexecutor 的callFunction)
1.8 JSIExecutor:: callFunction
void JSIExecutor::callFunction(
const std::string &moduleId,
const std::string &methodId,
const folly::dynamic &arguments)
if (!callFunctionReturnFlushedQueue_)
//把js 中的函数映射到c++层,例如这里的callFunctionReturnFlushedQueue_
bindBridge();
...
try
scopedTimeoutInvoker_(
[&]
//调用js 的同名函数
ret = callFunctionReturnFlushedQueue_->call(
*runtime_,
moduleId,
methodId,
valueFromDynamic(*runtime_, arguments));
,
std::move(errorProducer));
catch (...)
std::throw_with_nested(
std::runtime_error("Error calling " + moduleId + "." + methodId));
//返回值,传到java
callNativeModules(ret, true);
下面到js看看,callFunctionReturnFlushedQueue_
的实现
1.9 MessageQueue & callFunctionReturnFlushedQueue
MessageQueue 类的位置在 Libraries/BatchedBridge/MessageQueue.js
callFunctionReturnFlushedQueue(
module: string,
method: string,
args: mixed[],
): null | [Array<number>, Array<number>, Array<mixed>, number]
this.__guard(() =>
//找到对应的module,调用方法
this.__callFunction(module, method, args);
);
//这个会调用 JSTimer 的callImmediates
return this.flushedQueue();
__callFunction(module: string, method: string, args: mixed[]): void
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
...
//从_lazyCallableModules 中取出对应的module
const moduleMethods = this.getCallableModule(module);
...
//moduleMethods[method] 方法名,用 apply 来调用
moduleMethods[method].apply(moduleMethods, args);
至此,java-> js 的通信流程就分析完了,下面来看看js->java的
二、 JS->Java 通信
在开始分析之前,首先要知道,JS->Java 通信是两种的,表现在通信中的差异,下面会介绍一些。具体原因是传递的 JavaScriptExecutorFactory不同(可在ReactNativeHost中设置),但是 如果你本地跑了metro,那么会自动使用 ProxyJavaScriptExecutor(可自己翻阅代码 ReactInstanceManager 的onReloadWithJSDebugger函数)
我们在JS中调用Java的方法,是通过这样的形式
- 继承 ReactContextBaseJavaModule,使用@ReactMethod 注解 暴露给js的方法,
- 把 上一步创建的类 添加到ReactPackage
- 把ReactPackage添加到ReactNativeHost 的getPackages() 的返回值中
- 在js侧,通过
NativeModules.xxxModuleName.xxxMethodName()
调用到原生
下面我们就从 NativeModules.xxxModuleName
这样的代码开始分析:
先来看张图,对整体过程,有个感觉。图片来源
首先需要搞清楚,这个NativeModules 是什么,在js侧,代码路径 Libraries/BatchedBridge/NativeModules.js
2.1、 NativeModules.js
let NativeModules: [moduleName: string]: $FlowFixMe, ... = ;
if (global.nativeModuleProxy)
//nativeModuleProxy 的赋值,是在 JSIExecutor::initializeRuntime() ,详见上篇文章
//如果执行到这里,那么JavaScriptExecutor 使用的是JSIExecutor
NativeModules = global.nativeModuleProxy;
else if (!global.nativeExtensions)
//如果你是debug,本地跑metro服务,会运行到这里
const bridgeConfig = global.__fbBatchedBridgeConfig;
const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
(bridgeConfig.remoteModuleConfig || []).forEach(
(config: ModuleConfig, moduleID: number) =>
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (!info)
return;
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),
);
,
);
module.exports = NativeModules;
不同的JavaScriptExecutor 最终都会执行到genModule,下面我们分析JSIExecutor 分支 的流程。
简单回忆一下,在 JSIExecutor::initializeRuntime()
中设置了 global.nativeModuleProxy
的值,
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));
可以看到global.nativeModuleProxy
是NativeModuleProxy 类型,在获取对应的module,会执行NativeModuleProxy的get方法,下面来到c++层:
2.2、 JSIExecutor::NativeModuleProxy
class JSIExecutor::NativeModuleProxy : public jsi::HostObject
public:
NativeModuleProxy(std::shared_ptr<JSINativeModules> nativeModules)
: weakNativeModules_(nativeModules)
Value get(Runtime &rt, const PropNameID &name) override
...
auto nativeModules = weakNativeModules_.lock();
if (!nativeModules)
return nullptr;
//调用 JSIExecutor的getModule
return nativeModules->getModule(rt, name);
...
private:
std::weak_ptr<JSINativeModules> weakNativeModules_;
;
2.3、 JSIExecutor # getModule
Value JSINativeModules::getModule(Runtime &rt, const PropNameID &name)
if (!m_moduleRegistry)
return nullptr;
std::string moduleName = name.utf8(rt);
//先从缓存中查找,如果已经被加载过,直接返回
const auto it = m_objects.find(moduleName);
if (it != m_objects.end())
...
return Value(rt, it->second);
// 缓存没有命中,创建module。
// 创建module,解析module对应的method,为每个method创建对应的js方法(用于把对应的调用信息,发送到js线程队列)
auto module = createModule(rt, moduleName);
...省略判空逻辑...
// 把创建的module缓存起来
auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first;
Value ret = Value(rt, result->second);
//直接返回给js了
return ret;
createModule 是核心,下面来分析
2.4 JSINativeModules::createModule
folly::Optional<Object> JSINativeModules::createModule(
Runtime &rt,
const std::string &name)
//获取到js层的__fbGenNativeModule ,在NativeModule.js中有这句 global.__fbGenNativeModule = genModule;
if (!m_genNativeModuleJS)
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
//获取对应name的module,代码2.5 分析
auto result = m_moduleRegistry->g以上是关于React Native原理之跨端通信机制的主要内容,如果未能解决你的问题,请参考以下文章