带着下面问题进行阅读:
Examples/Movies里为什么有 iOS 和 Android 两套入口。Libraries为什么是 JS 公共层,但两端 bundle 不完全相同。<View />为什么能在 iOS 上变成RCTView,在 Android 上变成ReactViewGroup。- Android 默认 bridge 和 Android cxxbridge 为什么是两条路径。
ReactCommon/cxxreact在 0.28 中到底参与哪条路径。
先看启动入口
text
你启动 iOS App
-> 操作系统进入 iOS 宿主代码
-> iOS 宿主加载 iOS bundle
text
你启动 Android App
-> 操作系统进入 Android 宿主代码
-> Android 宿主加载 Android bundle
一个跨端 RN 项目维护两套 Native 宿主;运行时只启动当前平台那一套。
一屏源码地图
先把第 1 章的地图放在这里,后面逐段解释:
text
Examples/Movies
|
|-- iOS Native 宿主
| AppDelegate.m
| -> RCTRootView
| -> RCTBridge
| -> RCTBatchedBridge
|
|-- Android Native 宿主
MoviesActivity.java
-> ReactActivity
-> ReactInstanceManagerImpl
Native 宿主指定 JS 入口和 platform
|
v
packager
|
|-- platform=ios -> 选择 .ios.js / 共享 .js
|-- platform=android -> 选择 .android.js / 共享 .js
v
bundle
|
v
Libraries
|
|-- AppRegistry
|-- View / Text / StyleSheet
|-- UIManager
|-- NativeModules
v
Bridge 协议
|
|-- iOS: React
| RCTBridge / RCTBatchedBridge / RCTUIManager / RCTViewManager
|
|-- Android 默认 bridge: ReactAndroid
| ReactInstanceManagerImpl / bridge.CatalystInstanceImpl / ReactBridge / react/jni
|
|-- Android cxxbridge: ReactAndroid + ReactCommon
XReactInstanceManagerImpl / cxxbridge.CatalystInstanceImpl / xreact/jni / ReactCommon/cxxreact
这一章要记住的不是每个类,而是几条分界线:
text
Libraries 是 JS 公共层
React 是 iOS Native 实现
ReactAndroid 是 Android Native 实现
ReactCommon/cxxreact 只接入 Android cxxbridge
packager 负责按平台构建 bundle
1. Movies 里有两套 Native 宿主入口
iOS 入口:AppDelegate.m
关键代码:
objc
jsCodeLocation = [NSURL URLWithString:
@"http://localhost:8081/Examples/Movies/MoviesApp.ios.bundle?platform=ios&dev=true"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"MoviesApp"
initialProperties:nil
launchOptions:launchOptions];
这一段做了两件事。
- 指定要加载哪份 JS bundle:
- 告诉 packager 当前目标平台是 iOS:
platform=ios
dev=true 不是平台选择,它只是开发模式配置。
moduleName:@"MoviesApp" 也不是入口文件。它表示 bundle 执行完后,要从 AppRegistry 中运行哪个注册应用。
对应 JS 注册在:
text
Examples/Movies/MoviesApp.ios.js
js
AppRegistry.registerComponent('MoviesApp', () => MoviesApp);
所以 iOS 这边有两个名字:
text
MoviesApp.ios.bundle
-> 加载哪份 JS bundle
"MoviesApp"
-> 运行 bundle 中注册的哪个应用
Android 入口:MoviesActivity.java
关键代码:
java
public class MoviesActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "MoviesApp";
}
@Override
protected @Nullable String getBundleAssetName() {
return "MoviesApp.android.bundle";
};
@Override
protected String getJSMainModuleName() {
return "Examples/Movies/MoviesApp.android";
}
}
这三个方法分别解决三个问题。
getMainComponentName():
text
返回 "MoviesApp"
它对应 iOS 的:
objc
moduleName:@"MoviesApp"
意思是:bundle 加载完后,运行 AppRegistry 里注册名为 "MoviesApp" 的应用。
getJSMainModuleName():
text
返回 "Examples/Movies/MoviesApp.android"
它对应 iOS 的 jsCodeLocation 中的 JS 入口部分。开发模式下,Android 会据此向 packager 请求 Android bundle。
getBundleAssetName():
text
返回 "MoviesApp.android.bundle"
它用于预打包 bundle。可以把它理解成生产环境或离线场景中的构建产物名。
iOS 与 Android 配置项对照
| 作用 | iOS | Android |
|---|---|---|
| 指定 JS 入口 / bundle 来源 | jsCodeLocation |
getJSMainModuleName() / getBundleAssetName() |
| 指定运行哪个注册应用 | moduleName:@"MoviesApp" |
getMainComponentName() |
| 平台信息 | platform=ios |
Android dev server URL 中的 platform=android |
| Root View | RCTRootView |
ReactRootView |
记法:
text
JS Main Module = 加载哪份 JS
Main Component = 运行哪个注册应用
2. packager 为什么会生成不同 bundle
packager 不创建 Native View,也不执行 Bridge 调用。它负责把 JS 源码整理成当前平台可执行的 bundle。
关键点是:
text
packager 构建 bundle 时知道 platform
iOS 请求中有:
text
platform=ios
Android 请求中有:
text
platform=android
所以,两端 bundle 不完全相同,不是因为 JS 运行后才临时挑文件,而是 packager 在构建依赖图时就已经按平台选文件
3. Libraries 为什么是 JS 公共层
Libraries 是 RN 向业务 JS 暴露公共能力的地方。业务代码写:
js
var ReactNative = require('react-native');
var {
AppRegistry,
View,
Text,
StyleSheet,
} = ReactNative;
这些能力来自 Libraries。
但"公共层"不等于"所有文件两端完全一样"。Libraries 里既有共享文件,也有平台专属文件。
一个具体例子是网络模块。
共享调用方:
text
Libraries/Network/XMLHttpRequest.js
它只写:
js
const RCTNetworking = require('RCTNetworking');
它不关心当前是 iOS 还是 Android。
但真正的 RCTNetworking 有两个平台实现:
text
Libraries/Network/RCTNetworking.ios.js
Libraries/Network/RCTNetworking.android.js
两个文件都声明同一个模块名:
js
@providesModule RCTNetworking
但内部调用 Native 的参数形态不同。
iOS 把请求整理成一个对象:
js
RCTNetworkingNative.sendRequest({
method,
url,
data,
headers,
incrementalUpdates,
timeout
}, callback);
Android 在 JS 包装层生成 requestId,并把 headers 转成数组:
js
RCTNetworkingNative.sendRequest(
method,
url,
requestId,
convertHeadersMapToArray(headers),
data,
incrementalUpdates,
timeout
);
这里的结构很典型:
text
共享调用方
-> 依赖统一模块名
-> packager 按平台选择具体 JS 包装层
-> JS 包装层再调用各自 Native 模块
所以 Libraries 的公共性体现在统一 API,而不是每个文件都相同。
4. <View /> 如何落到不同平台的真实 View
业务代码中写:
jsx
<View />
在 0.28 中,View 来自:
text
Libraries/Components/View/View.js
关键代码是:
js
return <RCTView {...this.props} />;
const RCTView = requireNativeComponent('RCTView', View, {
nativeOnly: {
nativeBackgroundAndroid: true,
}
});
这里出现的 "RCTView" 很重要。
它不是说 Android 一定有一个 Java 类叫 RCTView。它是 JS 与 Native 之间约定的组件注册名。
可以把它理解成:
text
JS 说:请创建一个类型名为 "RCTView" 的 Native 组件。
当前平台再决定由谁来创建。
iOS 映射
iOS 路径:
text
"RCTView"
-> RCTViewManager
-> RCTView
关键文件:
text
React/Views/RCTViewManager.m
React/Views/RCTView.m
React/Modules/RCTUIManager.m
React/Views/RCTComponentData.m
iOS 的映射规则是:
text
RCTViewManager
-> 去掉类名末尾的 Manager
-> 得到 "RCTView"
RCTComponentData 做了这件事:
objc
_name = RCTBridgeModuleNameForClass(_managerClass);
if ([_name hasSuffix:@"Manager"]) {
_name = [_name substringToIndex:_name.length - @"Manager".length];
}
创建真实 View 时:
objc
- (UIView *)view
{
return [RCTView new];
}
所以 iOS 是:
text
JS 注册名 "RCTView"
-> 找到 RCTViewManager
-> 创建 RCTView
Android 映射
Android 路径:
text
"RCTView"
-> ReactViewManager
-> ReactViewGroup
关键文件:
text
ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java
ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
Android 的映射规则是:
java
viewManager.getName()
ReactViewManager.getName() 返回:
java
return REACT_CLASS;
而 REACT_CLASS 是:
java
ViewProps.VIEW_CLASS_NAME // "RCTView"
启动时,ViewManagerRegistry 建立映射:
java
mViewManagers.put(viewManager.getName(), viewManager);
创建 View 时:
java
ViewManager viewManager = mViewManagers.get(className);
View view = viewManager.createView(...);
当 className 是 "RCTView",就会找到 ReactViewManager。
ReactViewManager 最终创建:
java
return new ReactViewGroup(context);
为什么 Android 真实 View 不叫 RCTView
因为注册名和平台实现类名不是同一个层次。
text
"RCTView"
-> 跨 Bridge 注册名
ReactViewManager
-> Android 上负责这种组件的 View Manager
ReactViewGroup
-> Android 上真正显示并承载子 View 的对象
ReactViewGroup 继承 Android 的 ViewGroup,它可以包含子 View,适合实现 RN 的 <View> 容器。
Manager 可以理解为某类 Native View 的工厂兼适配器:
text
它声明自己管理哪个组件名
它创建真实 Native View
它把 JS props 设置到真实 View 上
它处理这类 View 的命令和子 View 关系
它不是整个应用的渲染管理者。
5. 三条 Bridge 路径
第 1 章只要求建立地图,不要求读完 Bridge 内部实现。这里先记住三条路径和边界。
iOS Bridge 路径
Movies iOS 路径:
text
Examples/Movies/Movies/AppDelegate.m
-> RCTRootView
-> RCTBridge
-> RCTBatchedBridge
-> RCTJSCExecutor
-> 不进入 ReactCommon/cxxreact
对应目录:
text
React/Base
React/Executors
关键文件:
text
React/Base/RCTRootView.m
React/Base/RCTBridge.m
React/Base/RCTBatchedBridge.m
React/Executors/RCTJSCExecutor.mm
iOS 0.28 使用 Objective-C Bridge。React/ 目录中没有引用 ReactCommon/cxxreact。
所以这一章的结论是:
text
iOS 不进入 ReactCommon/cxxreact
Android 默认 bridge 路径
Movies Android 默认路径:
text
Examples/Movies/android/.../MoviesActivity.java
-> ReactActivity
-> ReactInstanceManager.builder()
-> ReactInstanceManagerImpl
-> com.facebook.react.bridge.CatalystInstanceImpl
-> ReactBridge
-> ReactAndroid/src/main/jni/react
-> 不进入 ReactCommon/cxxreact
关键判断点:
java
import com.facebook.react.bridge.CatalystInstanceImpl;
这个包名中的 bridge 表示默认 bridge。
ReactBridge.java 中有很多 native 方法:
java
public native void callFunction(...);
public native void invokeCallback(...);
public native void loadScriptFromAssets(...);
这些 Java 方法通过 JNI 绑定到 C++ 实现。
绑定位置在:
text
ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
关键代码:
cpp
registerNatives("com/facebook/react/bridge/ReactBridge", {
makeNativeMethod("initialize", ..., bridge::create),
makeNativeMethod("callFunction", bridge::callFunction),
...
});
这证明:
text
com.facebook.react.bridge.ReactBridge
-> 绑定到默认 react/jni
Android cxxbridge 路径
cxxbridge 是另一条 Android 路径,不是 Movies 默认路径。
它从:
text
XReactInstanceManager.builder()
开始。
完整路径:
text
XReactInstanceManager.builder()
-> XReactInstanceManagerImpl
-> com.facebook.react.cxxbridge.CatalystInstanceImpl
-> ReactAndroid/src/main/jni/xreact
-> ReactCommon/cxxreact
关键判断点:
java
import com.facebook.react.cxxbridge.CatalystInstanceImpl;
这个包名中的 cxxbridge 表示 cxxbridge。
进入 C++ 后,在:
text
ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.cpp
可以看到:
cpp
#include <cxxreact/Instance.h>
#include <cxxreact/MethodCall.h>
#include <cxxreact/ModuleRegistry.h>
这说明 xreact/jni 接入了:
text
ReactCommon/cxxreact
6. ReactCommon/cxxreact 的真实边界
ReactCommon/cxxreact 是 0.26 引入、到 0.28 仍在演进的共享 C++ Bridge 核心。
但在 0.28 中,不能把它说成"iOS 和 Android 共用的 Native 核心"。
准确说法是:
text
ReactCommon/cxxreact
-> 当前由 Android cxxbridge 路径使用
-> iOS 不使用
-> Android 默认 bridge 不使用
三条路径的结论:
text
iOS
-> RCTRootView
-> RCTBridge
-> RCTBatchedBridge
-> RCTJSCExecutor
-> 不进入 ReactCommon/cxxreact
text
Android 默认 bridge
-> ReactInstanceManagerImpl
-> bridge.CatalystInstanceImpl
-> ReactBridge
-> react/jni
-> 不进入 ReactCommon/cxxreact
text
Android cxxbridge
-> XReactInstanceManagerImpl
-> cxxbridge.CatalystInstanceImpl
-> xreact/jni
-> ReactCommon/cxxreact
一句话记:
text
iOS 走 RCT;
Android 默认走 bridge + react/jni;
Android X 走 cxxbridge + xreact + ReactCommon。
7. JNI 在这里是什么意思
JNI 是 Java Native Interface。
在 Android 里,Java 方法可以声明为:
java
public native void callFunction(...);
这表示方法不是 Java 实现的,真正实现由 C 或 C++ 提供。
RN 默认 Android bridge 中:
text
Java
-> ReactBridge.java
JNI 注册
-> ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
C++ 实现
-> ReactAndroid/src/main/jni/react
所以 JNI 在这里的作用是:
text
把 Java 的 native 方法接到 C++ 实现上
registerNatives("com/facebook/react/bridge/ReactBridge", ...) 的意思就是:
text
Java ReactBridge.initialize()
-> C++ bridge::create()
Java ReactBridge.callFunction()
-> C++ bridge::callFunction()
这不是普通 JS Bridge 的概念,而是 Android Java 到 C++ 的语言边界。