React Native之Android端Fabric 架构源码分析(下)
由于单章字数限制,这里将本文拆成上下两篇,上篇《React Native之Android端Fabric 架构源码分析(上)》
Fabric 组件
声明组件
要开发一个Fabric 组件,首先需要在TS代码中声明规范。我们使用npx create-react-native-library@latest创建一个Fabric组件库,然后参考根据官方文档的示例添加如下内容:
import type {
CodegenTypes,
HostComponent,
ViewProps,
} from 'react-native';
import {codegenNativeComponent} from 'react-native';
type WebViewScriptLoadedEvent = {
result: 'success' | 'error';
};
export interface NativeProps extends ViewProps {
sourceURL?: string;
onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}
export default codegenNativeComponent<NativeProps>(
'CustomWebView',
) as HostComponent<NativeProps>;
这里主要有三部分:
-
WebViewScriptLoadedEvent 是一种支持的数据类型,用于存储事件需要从原生代码传递到 JavaScript 的数据。
-
NativeProps 定义了可以设置的组件属性。
-
codegenNativeComponent 允许我们为自定义组件生成代码,并为组件定义一个与原生实现相匹配的名称。
其中,对codegenNativeComponent函数进行分析,源码react-native/packages/react-native/Libraries/Utilities/codegenNativeComponent.js:
// 如果这个函数运行,说明视图配置没有在构建时通过 `GenerateViewConfigJs.js` 生成。
// 因此我们需要使用 `requireNativeComponent` 从视图管理器获取视图配置。
// `requireNativeComponent` 在 Bridgeless 模式下不可用。
// 例如:如果 `codegenNativeComponent` 不是从以 NativeComponent.js 结尾的文件中调用,
// 这个函数就会在运行时执行。
function codegenNativeComponent<Props: {...}>(
componentName: string,
options?: NativeComponentOptions,
): NativeComponentType<Props> {
if (global.RN$Bridgeless === true && __DEV__) {
console.warn(
`Codegen didn't run for ${componentName}. This will be an error in the future. Make sure you are using @react-native/babel-preset when building your JavaScript code.`,
);
}
// 确定基础组件名称
let componentNameInUse =
options && options.paperComponentName != null
? options.paperComponentName
: componentName;
// 省略部分代码......
return (requireNativeComponent<Props>(
// $FlowFixMe[incompatible-type]
componentNameInUse,
): HostComponent<Props>);
}
根据注释可知,在新架构中,这个函数几乎没有实际意义,因为新架构是通过GenerateViewConfigJs.js 来生成实际的视图配置。
我们通过搜索codegenNativeComponent字符串,很容易定位到react-native/packages/babel-plugin-codegen/index.js文件,这里babel-plugin-codegen包就是检测codegenNativeComponent调用,解析TypeScript/Flow类型定义的工具包:
module.exports = function ({parse, types: t}) {
return {
pre(state) {
this.code = state.code;
this.filename = state.opts.filename;
this.defaultExport = null;
this.commandsExport = null;
this.codeInserted = false;
},
// 省略部分代码.....
ExportDefaultDeclaration(path, state) {
if (isCodegenDeclaration(path.node.declaration)) {
this.defaultExport = path;
}
},
// 程序退出时进行替换
Program: {
exit(path) {
if (this.defaultExport) {
// 1. 生成ViewConfig代码
const viewConfig = generateViewConfig(this.filename, this.code);
// 2. 解析为AST
const ast = parse(viewConfig, {
babelrc: false,
browserslistConfigFile: false,
configFile: false,
});
// 3. 完全替换原始导出
this.defaultExport.replaceWithMultiple(ast.program.body);
if (this.commandsExport != null) {
this.commandsExport.remove();
}
this.codeInserted = true;
}
},
},
},
};
};
function generateViewConfig(filename /*: string */, code /*: string */) {
// 解析TypeScript/Flow类型
const schema = parseFile(filename, code);
// 提取组件信息
const libraryName = basename(filename).replace(
/NativeComponent\.(js|ts)$/,
'',
);
// 调用Codegen生成器
return RNCodegen.generateViewConfig({
libraryName,
schema,
});
}
function isCodegenDeclaration(declaration) {
if (!declaration) {
return false;
}
if (
declaration.left &&
declaration.left.left &&
declaration.left.left.name === 'codegenNativeComponent'
) {
return true;
} else if (
declaration.callee &&
declaration.callee.name &&
declaration.callee.name === 'codegenNativeComponent'
) {
return true;
}
// 省略......
return false;
}
继续跟踪RNCodegen.generateViewConfig的实现,源码react-native/packages/react-native-codegen/src/generators/RNCodegen.js:
const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');
module.exports = {
allGenerators: ALL_GENERATORS,
// 省略部分代码......
generateViewConfig({
libraryName,
schema,
}: Pick<LibraryOptions, 'libraryName' | 'schema'>): string {
schemaValidator.validate(schema);
const result = generateViewConfigJs
.generate(libraryName, schema)
.values()
.next();
if (typeof result.value !== 'string') {
throw new Error(`Failed to generate view config for ${libraryName}`);
}
return result.value;
},
};
最终是调用的GenerateViewConfigJs.js中的generate,源码react-native/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js:
module.exports = {
generate(libraryName: string, schema: SchemaType): FilesOutput {
try {
const fileName = `${libraryName}NativeViewConfig.js`;
const imports: Set<string> = new Set();
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
return Object.keys(components)
.map((componentName: string) => {
const component = components[componentName];
if (component.paperComponentNameDeprecated) {
imports.add(UIMANAGER_IMPORT);
}
const replacedTemplate = ComponentTemplate({
componentName,
paperComponentName: component.paperComponentName,
paperComponentNameDeprecated:
component.paperComponentNameDeprecated,
});
// 省略部分代码......
return new Map([[fileName, replacedTemplate]]);
} catch (error) {
console.error(`\nError parsing schema for ${libraryName}\n`);
console.error(JSON.stringify(schema));
throw error;
}
},
};
这里我们根据ComponentTemplate中的模板,大概就能还原出生成的代码是什么样子:
// 输入:
// libraryName: "MyComponent"
// schema: { 解析后的TypeScript/Flow类型信息 }
// 输出:完整的JavaScript代码字符串
'use strict';
const {UIManager} = require("react-native")
let nativeComponentName = 'MyComponent';
export const __INTERNAL_VIEW_CONFIG = {
uiViewClassName: 'MyComponent',
bubblingEventTypes: {},
directEventTypes: {},
validAttributes: {
opacity: true,
backgroundColor: { process: require('react-native/Libraries/StyleSheet/processColor').default },
// ... 其他属性
}
};
export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);
这里最关键的就是最后一行,它将开发者编写的Fabric 组件规范进行了代码替换:
// 开发者编写
const CustomView = codegenNativeComponent<Props>('CustomView');
// Babel插件替换后
const CustomView = NativeComponentRegistry.get('CustomView', () => __INTERNAL_VIEW_CONFIG);
需要注意,babel-plugin-codegen工具并不像Codegen工具,会生成实际的代码文件。它是对AST语法树进行的动态修改和替换,也就是说它修改的是内存中的语法树,并不会写文件。
现在来重点追踪NativeComponentRegistry.get的实现,首先是导出位置react-native/packages/react-native/index.js:
get NativeComponentRegistry() {
return require('./Libraries/NativeComponent/NativeComponentRegistry');
},
定位到方法实现react-native/packages/react-native/Libraries/NativeComponent/NativeComponentRegistry.js:
/**
* 获取一个可以被 React Native 渲染的 `NativeComponent`。
*
* 提供的 `viewConfigProvider` 可能会被调用和使用,也可能不会,
* 这取决于 `setRuntimeConfigProvider` 是如何配置的。
*/
export function get<Config: {...}>(
name: string,
viewConfigProvider: () => PartialViewConfig,
): HostComponent<Config> {
// 注册ViewConfig到全局注册表
ReactNativeViewConfigRegistry.register(name, () => {
// 这里的回调函数会在React需要组件配置时被调用
const {native, verify} = getRuntimeConfig?.(name) ?? {
native: !global.RN$Bridgeless, // 关键:新架构检测
verify: false,
};
let viewConfig: ViewConfig;
if (native) {
// 旧架构:原生ViewManager
viewConfig =
getNativeComponentAttributes(name) ??
createViewConfig(viewConfigProvider());
} else {
// 新架构:优先静态ViewConfig
viewConfig =
createViewConfig(viewConfigProvider()) ??
getNativeComponentAttributes(name);
}
// 省略部分代码......
return viewConfig;
});
// $FlowFixMe[incompatible-type] `NativeComponent` 实际上是字符串!
return name;
}
继续跟踪ReactNativeViewConfigRegistry.register实现。源码react-native/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js:
const viewConfigCallbacks = new Map<string, ?() => ViewConfig>();
export function register(name: string, callback: () => ViewConfig): string {
// 省略部分代码......
viewConfigCallbacks.set(name, callback);
return name;
}
这里基本上就是将返回ViewConfig的闭包给存了起来。
查找组件
JS层
在前面启动渲染一节我们知道了启动渲染的最终调用是JS层的AppRegistry.runApplication方法。沿着这条线,我们来分析一下JS层的组件加载与处理流程。源码react-native/packages/react-native/Libraries/ReactNative/AppRegistry.js:
import * as AppRegistry from './AppRegistryImpl';
// 省略部分代码......
global.RN$AppRegistry = AppRegistry;
registerCallableModule('AppRegistry', AppRegistry);
export {AppRegistry};
继续跟踪源码react-native/packages/react-native/Libraries/ReactNative/AppRegistryImpl.js:
const runnables: Runnables = {};
/**
* Loads the JavaScript bundle and runs the app.
*
* See https://reactnative.dev/docs/appregistry#runapplication
*/
export function runApplication(
appKey: string,
appParameters: AppParameters,
displayMode?: number,
): void {
if (appKey !== 'LogBox') {
const logParams = __DEV__ ? ` with ${JSON.stringify(appParameters)}` : '';
const msg = `Running "${appKey}"${logParams}`;
console.log(msg);
}
SceneTracker.setActiveScene({name: appKey});
runnables[appKey](appParameters, coerceDisplayMode(displayMode));
}
/**
* Registers an app's root component.
*
* See https://reactnative.dev/docs/appregistry#registercomponent
*/
export function registerComponent(
appKey: string,
componentProvider: ComponentProvider,
section?: boolean,
): string {
const scopedPerformanceLogger = createPerformanceLogger();
runnables[appKey] = (appParameters, displayMode) => {
const renderApplication = require('./renderApplication').default;
renderApplication(
componentProviderInstrumentationHook(
componentProvider,
scopedPerformanceLogger,
),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
rootViewStyleProvider && rootViewStyleProvider(appParameters),
appParameters.fabric,
scopedPerformanceLogger,
appKey === 'LogBox', // is logbox
appKey,
displayMode,
);
};
if (section) {
sections[appKey] = runnables[appKey];
}
return appKey;
}
可以看到,runApplication调用的是runnables对象中注册的闭包。而在我们React Native JS层的Bundle包中,首先就需要调用AppRegistry.registerComponent(appName, () => App)完成最根组件的注册。所以runApplication方法中调用的闭包,就是在registerComponent中注册的闭包。
继续跟踪renderApplication方法的实现,源码react-native/packages/react-native/Libraries/ReactNative/renderApplication.js:
export default function renderApplication<Props: Object>(
RootComponent: React.ComponentType<Props>,
initialProps: Props,
rootTag: any,
WrapperComponent?: ?React.ComponentType<any>,
rootViewStyle?: ?ViewStyleProp,
fabric?: boolean,
scopedPerformanceLogger?: IPerformanceLogger,
isLogBox?: boolean,
debugName?: string,
displayMode?: ?DisplayModeType,
useOffscreen?: boolean,
) {
const performanceLogger = scopedPerformanceLogger ?? GlobalPerformanceLogger;
// 构建React元素树
// 外层:PerformanceLoggerContext.Provider - 提供性能监控上下文
// 中层:AppContainer - React Native的根容器组件
// 内层:RootComponent - 用户注册的应用组件(如App.js)
let renderable: React.MixedElement = (
<PerformanceLoggerContext.Provider value={performanceLogger}>
<AppContainer
rootTag={rootTag}
fabric={fabric}
WrapperComponent={WrapperComponent}
rootViewStyle={rootViewStyle}
initialProps={initialProps ?? Object.freeze({})}
internal_excludeLogBox={isLogBox}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
</PerformanceLoggerContext.Provider>
);
// 开发模式调试包装
if (__DEV__ && debugName) {
const RootComponentWithMeaningfulName = getCachedComponentWithDebugName(
`${debugName}(RootComponent)`,
);
renderable = (
<RootComponentWithMeaningfulName>
{renderable}
</RootComponentWithMeaningfulName>
);
}
// 实验性离屏渲染支持
if (useOffscreen && displayMode != null) {
// $FlowFixMe[incompatible-type]
// $FlowFixMe[prop-missing]
// $FlowFixMe[missing-export]
const Activity: ActivityType = React.unstable_Activity;
renderable = (
<Activity
mode={displayMode === DisplayMode.VISIBLE ? 'visible' : 'hidden'}>
{renderable}
</Activity>
);
}
// 我们希望在使用 Fabric 时始终启用 concurrentRoot 功能
const useConcurrentRoot = Boolean(fabric);
// 省略部分性能日志打印......
// 进入React渲染系统
Renderer.renderElement({
element: renderable,
rootTag,
useFabric: Boolean(fabric),
useConcurrentRoot,
});
}
继续跟踪Renderer.renderElement方法实现。源码react-native/packages/react-native/Libraries/ReactNative/RendererImplementation.js:
export function renderElement({
element,
rootTag,
useFabric,
useConcurrentRoot,
}: {
element: React.MixedElement,
rootTag: number,
useFabric: boolean,
useConcurrentRoot: boolean,
}): void {
if (useFabric) {
if (cachedFabricRender == null) {
// 获取实际渲染器
cachedFabricRender = getFabricRenderer().render;
}
cachedFabricRender(element, rootTag, null, useConcurrentRoot, {
onCaughtError,
onUncaughtError,
onRecoverableError,
});
} else {
// 省略旧架构......
}
}
function getFabricRenderer(): ReactFabricType {
if (cachedFabricRenderer == null) {
cachedFabricRenderer = require('../Renderer/shims/ReactFabric').default;
}
return cachedFabricRenderer;
}
继续跟踪react-native/packages/react-native/Libraries/Renderer/shims/ReactFabric.js:
import {BatchedBridge} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
import type {ReactFabricType} from './ReactNativeTypes';
let ReactFabric: ReactFabricType;
if (__DEV__) {
ReactFabric = require('../implementations/ReactFabric-dev');
} else {
ReactFabric = require('../implementations/ReactFabric-prod');
}
global.RN$stopSurface = ReactFabric.stopSurface;
if (global.RN$Bridgeless !== true) {
BatchedBridge.registerCallableModule('ReactFabric', ReactFabric);
}
export default ReactFabric;
这里根据开发环境选择了不同的渲染器。开发环境的渲染器体积更大,包含了许多调试、性能日志等信息,而生产环境的移除了注释和空白,变量名被压缩代码经过优化,减少包体积。
继续跟踪生产环境的渲染器,因为代码更加简洁,可更好的聚焦于调用的链路和流程。源码react-native/packages/react-native/Libraries/Renderer/implementations/ReactFabric-prod.js:
exports.render = function (
element,
containerTag,
callback,
concurrentRoot,
options
) {
var root = roots.get(containerTag);
if (!root) {
// 创建新的FiberRootNode
// 省略部分代码......
initializeUpdateQueue(concurrentRoot);
roots.set(containerTag, root);
}
// 启动渲染
updateContainer(element, root, null, callback);
// 返回渲染后的公共实例引用
a: if (((element = root.current), element.child))
switch (element.child.tag) {
case 27:
case 5:
element = getPublicInstance(element.child.stateNode);
break a;
default:
element = element.child.stateNode;
}
else element = null;
return element;
};
function updateContainer(element, container, parentComponent, callback) {
parentComponent = container.current;
// / 获取更新优先级
var lane = requestUpdateLane(parentComponent);
null === container.context
? (container.context = emptyContextObject)
: (container.pendingContext = emptyContextObject);
// 创建更新对象
container = createUpdate(lane);
container.payload = { element: element };
callback = void 0 === callback ? null : callback;
null !== callback && (container.callback = callback);
// 将更新加入队列
element = enqueueUpdate(parentComponent, container, lane);
// 调度更新
null !== element &&
(scheduleUpdateOnFiber(element, parentComponent, lane),
entangleTransitions(element, parentComponent, lane));
return lane;
}
以上调用都比较简单,这里的核心是scheduleUpdateOnFiber方法,它负责调度更新。由于代码量较大,我们后面只追求核心逻辑,将省略大部分代码:
function scheduleUpdateOnFiber(root, fiber, lane) {
// 省略......
// 标记更新并触发调度
markRootUpdated$1(root, lane);
if (/* 不在渲染中 */) {
ensureRootIsScheduled(root); // 确保根节点被调度
if (/* 同步更新 */) {
flushSyncWorkAcrossRoots_impl(0, !0); // 立即执行同步工作
}
}
}
function flushSyncWorkAcrossRoots_impl(syncTransitionLanes, onlyLegacy) {
if (!isFlushingWork && mightHavePendingSyncWork) {
isFlushingWork = !0;
do {
var didPerformSomeWork = !1;
// 遍历所有根节点执行同步工作
for (var root = firstScheduledRoot; null !== root; ) {
if (!onlyLegacy || 0 === root.tag)
//省略......
// 常规同步更新
performSyncWorkOnRoot(root, JSCompiler_inline_result));
root = root.next;
}
} while (didPerformSomeWork);
isFlushingWork = !1;
}
}
function performSyncWorkOnRoot(root, lanes) {
if (flushPendingEffects()) return null;
performWorkOnRoot(root, lanes, !0);
}
function performWorkOnRoot(root$jscomp$0, lanes, forceSync) {
// 选择同步或并发渲染
var shouldTimeSlice = (!forceSync && /* 条件判断 */);
var exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes) // 并发渲染
: renderRootSync(root, lanes, !0); // 同步渲染
// 省略......
}
继续追踪同步渲染renderRootSync方法实现:
function renderRootSync(root, lanes, shouldYieldForPrerendering) {
// 省略......
// 同步工作循环
workLoopSync();
// 省略......
}
function workLoopSync() {
for (; null !== workInProgress; ) performUnitOfWork(workInProgress);
}
function performUnitOfWork(unitOfWork) {
// 处理单个Fiber节点
var next = beginWork(unitOfWork.alternate, unitOfWork, entangledRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
null === next ? completeUnitOfWork(unitOfWork) : (workInProgress = next);
}
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
// 省略......
unitOfWork = completedWork.return;
var next = completeWork(
completedWork.alternate,
completedWork,
entangledRenderLanes
);
if (null !== next) {
workInProgress = next;
return;
}
completedWork = completedWork.sibling;
if (null !== completedWork) {
workInProgress = completedWork;
return;
}
workInProgress = completedWork = unitOfWork;
} while (null !== completedWork);
0 === workInProgressRootExitStatus && (workInProgressRootExitStatus = 5);
}
最终是调用的completeWork方法完成渲染工作:
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case 28:
case 16:
case 15:
case 0:
case 11:
case 7:
case 8:
case 12:
case 9:
case 14:
return bubbleProperties(workInProgress), null;
case 1:
return bubbleProperties(workInProgress), null;
case 3:
// 省略......
// return
case 26:
case 27:
case 5:
popHostContext(workInProgress);
var type = workInProgress.type;
if (null !== current && null != workInProgress.stateNode)
// 省略......
else {
// 首次挂载
if (!newProps) {
if (null === workInProgress.stateNode)
throw Error(
"We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."
);
bubbleProperties(workInProgress);
return null;
}
current = rootInstanceStackCursor.current;
renderLanes = nextReactTag;
nextReactTag += 2;
// 获取ViewConfig
type = getViewConfigForType(type);
var updatePayload = ReactNativePrivateInterface.createAttributePayload(
newProps,
type.validAttributes
);
// 创建原生节点
current = {
node: createNode(
renderLanes,
type.uiViewClassName, // 使用ViewConfig中的原生类名
current.containerTag,
updatePayload,
workInProgress
),
canonical: {
nativeTag: renderLanes,
viewConfig: type, // 保存ViewConfig引用
currentProps: newProps,
internalInstanceHandle: workInProgress,
publicInstance: null,
publicRootInstance: current.publicInstance
}
};
workInProgress.flags |= 8;
appendAllChildren(current, workInProgress, !1, !1);
workInProgress.stateNode = current;
}
bubbleProperties(workInProgress);
workInProgress.flags &= -16777217;
return null;
// 省略部分代码......
}
}
此方法中的case都是整数,为了弄清楚其含义,我们可以查看React 仓库中的Tag定义。源码位于 ReactWorkTags.js:
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostRoot = 3; // 宿主树的根节点。它可以嵌套在另一个节点内部
export const HostPortal = 4; // 一个子树。它可以是通往不同渲染器的入口点
export const HostComponent = 5;
// 省略......
很显然,我们需要查看的是React Native的Tag类型,也就是HostComponent,它的值是5,因此对应到completeWork中的处理代码就是我截取的这部分。这段代码中有一个关键的方法,就是getViewConfigForType,查看其定义,显示为getViewConfigForType = ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get
可看到,这里就完全与前面声明组件一节最后分析到的ReactNativeViewConfigRegistry.js对应上了,我们前面分析的是ViewConfig的注册,现在来看一下get方法:
/**
* 获取指定视图的配置。如果这是第一次使用该视图,此配置将从UIManager延迟加载
*/
export function get(name: string): ViewConfig {
// 从viewConfigs Map中查找已缓存的ViewConfig
let viewConfig = viewConfigs.get(name);
if (viewConfig == null) {
// 获取该组件的配置生成回调函数
const callback = viewConfigCallbacks.get(name);
if (typeof callback !== 'function') {
// 省略日志......
}
viewConfig = callback();
invariant(viewConfig, 'View config not found for component `%s`', name);
processEventTypes(viewConfig);
viewConfigs.set(name, viewConfig);
// 配置设置后清除回调,这样我们就不会在注册过程中掩盖任何错误。
viewConfigCallbacks.set(name, null);
}
return viewConfig;
}
通过ViewConfig,可以得到uiViewClassName,也就是声明的组件名称,我们继续查看原生节点创建的方法:
var _nativeFabricUIManage = nativeFabricUIManager,
createNode = _nativeFabricUIManage.createNode,
createNode(
renderLanes,
type.uiViewClassName, // 使用ViewConfig中的原生类名
current.containerTag,
updatePayload,
workInProgress
)
createNode方法来自于全局对象nativeFabricUIManager,通过变量名就知道,这个应该是来自于JSI定义的对象,代码不在JS层。
C++ 层
继续在C++中搜索nativeFabricUIManager,定位到源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp:
void UIManagerBinding::createAndInstallIfNeeded(
jsi::Runtime& runtime,
const std::shared_ptr<UIManager>& uiManager) {
auto uiManagerModuleName = "nativeFabricUIManager";
auto uiManagerValue =
runtime.global().getProperty(runtime, uiManagerModuleName);
if (uiManagerValue.isUndefined()) {
// 全局命名空间中没有该绑定的实例;我们需要创建、安装并返回它
auto uiManagerBinding = std::make_shared<UIManagerBinding>(uiManager);
auto object = jsi::Object::createFromHostObject(runtime, uiManagerBinding);
runtime.global().setProperty(
runtime, uiManagerModuleName, std::move(object));
}
}
可见这里的nativeFabricUIManager是一个jsi::HostObject对象,我们要查找其createNode方法,直接查看UIManagerBinding的get方法:
jsi::Value UIManagerBinding::get(
jsi::Runtime& runtime,
const jsi::PropNameID& name) {
auto methodName = name.utf8(runtime);
// 将 shared_ptr<UIManager> 转换为原始指针
// 为什么这样做?原因如下:
// 1) UIManagerBinding 强引用(strongly retains)UIManager。
// JS VM 通过 JSI 强引用 UIManagerBinding。
// 这些函数是 JSI 函数,只能通过 JS VM 调用;如果 JS VM 被销毁,
// 这些函数无法执行,这些 lambda 也不会执行。
// 2) UIManager 只有在所有对它的引用都被释放后才会被析构,包括
// UIManagerBinding。这只有在 JS VM 被析构时才会发生。因此,原始指针是安全的。
//
// 即使这样做是安全的,为什么不直接使用 shared_ptr 作为额外的保险呢?
// 1) 在不需要的情况下使用 shared_ptr 或 weak_ptr
// 是一种性能劣化(pessimisation)。
// 在这种情况下,它会执行更多指令,但不会带来任何额外价值。
// 2) 这些 lambda 的确切释放时机和方式很复杂。向它们添加 shared_ptr 会导致
// UIManager 可能存活更长时间,这是不必要的、复杂的认知负担。
// 3) 有强烈怀疑认为,从这些 C++ lambda 中保留 UIManager(这些 lambda 被
// JSI 持有的对象所保留),在 Scheduler 和 JS VM 析构时导致了一些崩溃。
// 如果 C++ 语义导致这些 lambda 在 JS VM 被析构后一个 CPU
// 时钟周期(或更久) 才被释放,就可能发生这种情况。
UIManager* uiManager = uiManager_.get();
// Semantic: Creates a new node with given pieces.
if (methodName == "createNode") {
auto paramCount = 5;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
try {
validateArgumentCount(runtime, methodName, paramCount, count);
auto instanceHandle =
instanceHandleFromValue(runtime, arguments[4], arguments[0]);
if (!instanceHandle) {
react_native_assert(false);
return jsi::Value::undefined();
}
return valueFromShadowNode(
runtime,
uiManager->createNode(
tagFromValue(arguments[0]),
stringFromValue(runtime, arguments[1]),
surfaceIdFromValue(runtime, arguments[2]),
RawProps(runtime, arguments[3]),
std::move(instanceHandle)),
true);
} catch (const std::logic_error& ex) {
LOG(FATAL) << "logic_error in createNode: " << ex.what();
}
});
}
// 省略部分代码......
return jsi::Value::undefined();
}
继续跟踪react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp中的createNode实现:
std::shared_ptr<ShadowNode> UIManager::createNode(
Tag tag, // 节点标签
const std::string& name, // 组件名称
SurfaceId surfaceId,
RawProps rawProps, // 原始属性
InstanceHandle::Shared instanceHandle) const {
TraceSection s("UIManager::createNode", "componentName", name);
// 根据组件名称获取对应的ComponentDescriptor
auto& componentDescriptor = componentDescriptorRegistry_->at(name);
auto fallbackDescriptor =
componentDescriptorRegistry_->getFallbackComponentDescriptor();
PropsParserContext propsParserContext{surfaceId, *contextContainer_};
// 创建ShadowNodeFamily,用于管理同一组件的不同实例
auto family = componentDescriptor.createFamily(
{.tag = tag,
.surfaceId = surfaceId,
.instanceHandle = std::move(instanceHandle)});
// 解析和克隆属性
const auto props = componentDescriptor.cloneProps(
propsParserContext, nullptr, std::move(rawProps));
// 创建初始状态
const auto state = componentDescriptor.createInitialState(props, family);
// 创建ShadowNode
auto shadowNode = componentDescriptor.createShadowNode(
ShadowNodeFragment{
.props = fallbackDescriptor != nullptr &&
fallbackDescriptor->getComponentHandle() ==
componentDescriptor.getComponentHandle()
? componentDescriptor.cloneProps(
propsParserContext,
props,
RawProps(folly::dynamic::object("name", name)))
: props,
.children = ShadowNodeFragment::childrenPlaceholder(),
.state = state,
},
family);
if (delegate_ != nullptr) {
delegate_->uiManagerDidCreateShadowNode(*shadowNode);
}
if (leakChecker_) {
leakChecker_->uiManagerDidCreateShadowNodeFamily(family);
}
return shadowNode;
}
可以看到,这里通过componentDescriptorRegistry_来查找Fabric 组件描述对象,至于注册的地方,下一节注册组件会专门分析。
这里还有一点要注意,createShadowNode方法只是创建了虚拟的ShadowNode,并没有创建真正的原生视图。ShadowNode是Fabric中的虚拟DOM节点,用于布局计算。也就是说当完成布局和diff计算后,会生成MountItem指令。
Kotlin层
在前面启动渲染一节中,我们有分析到createViewUnsafe方法,回顾一下安卓原生组件的查找:
ViewManager viewManager = mViewManagerRegistry.get(componentName);
跟踪源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.kt:
@Synchronized
public fun get(className: String): ViewManager<*, *> {
// 1. Try to get the manager without the prefix.
viewManagersMap[className]?.let {
return it
}
// 2. Try to get the manager with the RCT prefix.
val rctViewManagerName = "RCT$className"
viewManagersMap[rctViewManagerName]?.let {
return it
}
if (viewManagerResolver != null) {
// 1. Try to get the manager without the prefix.
val resolvedManager = getViewManagerFromResolver(className)
if (resolvedManager != null) {
return resolvedManager
}
// 2. Try to get the manager with the RCT prefix.
val rctResolvedManager = getViewManagerFromResolver(rctViewManagerName)
if (rctResolvedManager != null) {
return rctResolvedManager
}
throw IllegalViewOperationException(
"Can't find ViewManager '$className' nor '$rctViewManagerName' in ViewManagerRegistry, " +
"existing names are: ${viewManagerResolver.getViewManagerNames()}"
)
}
throw IllegalViewOperationException("No ViewManager found for class $className")
}
private fun getViewManagerFromResolver(className: String): ViewManager<*, *>? {
val viewManager = viewManagerResolver?.getViewManager(className)
if (viewManager != null) {
viewManagersMap[className] = viewManager
}
return viewManager
}
新架构的情况下,是通过getViewManagerFromResolver方法来查找,其中viewManagerResolver的类型是BridgelessViewManagerResolver,它是一个内部类,定义在ReactInstance.kt文件中:
private class BridgelessViewManagerResolver(
private val reactPackages: List<ReactPackage>,
private val context: BridgelessReactContext,
) : ViewManagerResolver {
private val lazyViewManagerMap: MutableMap<String, ViewManager<*, *>> = HashMap()
override fun getViewManager(viewManagerName: String): ViewManager<*, *>? {
// 从懒加载包中查找
val viewManager = getLazyViewManager(viewManagerName)
if (viewManager != null) {
return viewManager
}
// 如果通过延迟加载在所有 React 包中都找不到视图管理器,则回退到默认实现:立即初始化所有视图管理器
return eagerViewManagerMap[viewManagerName]
}
private lateinit var _eagerViewManagerMap: Map<String, ViewManager<*, *>>
@get:Synchronized
val eagerViewManagerMap: Map<String, ViewManager<*, *>>
get() {
if (::_eagerViewManagerMap.isInitialized) {
return _eagerViewManagerMap
}
val viewManagerMap: MutableMap<String, ViewManager<*, *>> = HashMap()
for (reactPackage in reactPackages) {
if (reactPackage is ViewManagerOnDemandReactPackage) {
continue
}
val viewManagersInPackage = reactPackage.createViewManagers(context)
for (viewManager in viewManagersInPackage) {
// TODO(T173624687): Should we throw/warn when the same view manager name is registered
// twice?
viewManagerMap[viewManager.name] = viewManager
}
}
_eagerViewManagerMap = viewManagerMap
return viewManagerMap
}
@Synchronized
fun getLazyViewManager(viewManagerName: String): ViewManager<*, *>? {
// 先查缓存
if (lazyViewManagerMap.containsKey(viewManagerName)) {
return lazyViewManagerMap[viewManagerName]
}
// 缓存未命中则遍历所有 reactPackages,调用 createViewManager(name) 创建
for (reactPackage in reactPackages) {
if (reactPackage is ViewManagerOnDemandReactPackage) {
val viewManager = reactPackage.createViewManager(context, viewManagerName)
if (viewManager != null) {
// TODO(T173624687): Should we throw/warn when the same view manager name is registered
// twice?
lazyViewManagerMap[viewManagerName] = viewManager
return viewManager
}
}
}
return null
}
// 省略部分代码......
}
注册组件
JS工具
React Native新架构使用了很多代码生成工具,以致于成了黑箱操作,这对于Turbo Module和Fabric组件的注册流程造成了理解上的困难。为此,我们不得不研究这些CLI工具。首先研究@react-native-community/cli工具,源码 cli。这里我们使用的版本是v20.0.2。
查看源码cli/packages/cli-config-android/src/config/index.ts:
export function dependencyConfig(
root: string,
userConfig: AndroidDependencyParams | null = {},
): AndroidDependencyConfig | null {
if (userConfig === null) {
return null;
}
const src = userConfig.sourceDir || findAndroidDir(root);
if (!src) {
return null;
}
const sourceDir = path.join(root, src);
const manifestPath = userConfig.manifestPath
? path.join(sourceDir, userConfig.manifestPath)
: findManifest(sourceDir);
const buildGradlePath = findBuildGradle(sourceDir, '');
const isPureCxxDependency =
userConfig.cxxModuleCMakeListsModuleName != null &&
userConfig.cxxModuleCMakeListsPath != null &&
userConfig.cxxModuleHeaderName != null &&
!manifestPath &&
!buildGradlePath;
if (!manifestPath && !buildGradlePath && !isPureCxxDependency) {
return null;
}
let packageImportPath = null,
packageInstance = null;
if (!isPureCxxDependency) {
const packageName =
userConfig.packageName || getPackageName(manifestPath, buildGradlePath);
const packageClassName = findPackageClassName(sourceDir);
/**
* This module has no package to export
*/
if (!packageClassName) {
return null;
}
packageImportPath =
userConfig.packageImportPath ||
`import ${packageName}.${packageClassName};`;
packageInstance = userConfig.packageInstance || `new ${packageClassName}()`;
}
const buildTypes = userConfig.buildTypes || [];
const dependencyConfiguration = userConfig.dependencyConfiguration;
const libraryName =
userConfig.libraryName || findLibraryName(root, sourceDir);
const componentDescriptors =
userConfig.componentDescriptors || findComponentDescriptors(root);
let cmakeListsPath = userConfig.cmakeListsPath
? path.join(sourceDir, userConfig.cmakeListsPath)
: path.join(sourceDir, 'build/generated/source/codegen/jni/CMakeLists.txt');
const cxxModuleCMakeListsModuleName =
userConfig.cxxModuleCMakeListsModuleName || null;
const cxxModuleHeaderName = userConfig.cxxModuleHeaderName || null;
let cxxModuleCMakeListsPath = userConfig.cxxModuleCMakeListsPath
? path.join(sourceDir, userConfig.cxxModuleCMakeListsPath)
: null;
if (process.platform === 'win32') {
cmakeListsPath = cmakeListsPath.replace(/\\/g, '/');
if (cxxModuleCMakeListsPath) {
cxxModuleCMakeListsPath = cxxModuleCMakeListsPath.replace(/\\/g, '/');
}
}
return {
sourceDir,
packageImportPath,
packageInstance,
buildTypes,
dependencyConfiguration,
libraryName,
componentDescriptors,
cmakeListsPath,
cxxModuleCMakeListsModuleName,
cxxModuleCMakeListsPath,
cxxModuleHeaderName,
isPureCxxDependency,
};
}
此dependencyConfig函数非常重要,它主要用于生成example/android/build/generated/autolinking/autolinking.json文件。autolinking.json文件我们在前面的TurboModule的文章已经介绍过了,其中的信息是指导代码生成的关键。这里重点关注componentDescriptors字段的生成,因为它是Fabric组件代码生成的起始。
组件开发者通常并不会在react-native.config.js文件中主动配置componentDescriptors字段,一般都是默认值,那么就要查看一下findComponentDescriptors函数的实现逻辑了,源码cli/packages/cli-config-android/src/config/findComponentDescriptors.ts:
export function findComponentDescriptors(packageRoot: string) {
let jsSrcsDir = null;
try {
const packageJson = fs.readFileSync(
path.join(packageRoot, 'package.json'),
'utf8',
);
jsSrcsDir = JSON.parse(packageJson).codegenConfig.jsSrcsDir;
} catch (error) {
// no jsSrcsDir, continue with default glob pattern
}
const globPattern = jsSrcsDir
? `${jsSrcsDir}/**/*{.js,.jsx,.ts,.tsx}`
: '**/*{.js,.jsx,.ts,.tsx}';
const files = glob.sync(globPattern, {
cwd: unixifyPaths(packageRoot),
onlyFiles: true,
ignore: ['**/node_modules/**'],
});
const codegenComponent = files
.map((filePath) =>
fs.readFileSync(path.join(packageRoot, filePath), 'utf8'),
)
.map(extractComponentDescriptors)
.filter(Boolean);
// Filter out duplicates as it happens that libraries contain multiple outputs due to package publishing.
// TODO: consider using "codegenConfig" to avoid this.
return Array.from(new Set(codegenComponent as string[]));
}
这里主要逻辑其实是从package.json中获取到jsSrcsDir配置,从而定位到源码所在目录。我们关注的是codegenComponent的生成,此处是通过读取每个源文件内容,然后调用 extractComponentDescriptors 提取组件描述符。继续查看cli/packages/cli-config-android/src/config/extractComponentDescriptors.ts:
const CODEGEN_NATIVE_COMPONENT_REGEX =
/codegenNativeComponent(<.*>)?\s*\(\s*["'`](\w+)["'`](,?[\s\S]+interfaceOnly:\s*(\w+))?/m;
export function extractComponentDescriptors(contents: string) {
const match = contents.match(CODEGEN_NATIVE_COMPONENT_REGEX);
if (!(match?.[4] === 'true') && match?.[2]) {
return `${match[2]}ComponentDescriptor`;
}
return null;
}
到这里就很清楚了,扫描所有源码,使用正则找到我们在声明组件一节中提到的export default codegenNativeComponent<NativeProps>('CustomviewView');这行代码,这里的正则就是匹配关键字codegenNativeComponent。这里的match?.[2]就是提前出组件名,也就是我们示例中的CustomviewView。最终返回的字符串是拼接后的,这里就是CustomviewViewComponentDescriptor。也就是说componentDescriptors字段的默认值是CustomviewViewComponentDescriptor。
接下来我们回顾前文《Android端TurboModule分析》,其中提到但略过的React Gradle脚本的configureCodegen方法:
private fun configureCodegen(
project: Project,
localExtension: ReactExtension,
rootExtension: PrivateReactExtension,
isLibrary: Boolean,
) {
// 首先,我们需要设置 Codegen 的输出目录
val generatedSrcDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/source/codegen")
// 我们为 jsRootDir(JS根目录)指定默认值(约定)。
// 对于 App 来说是根文件夹(即 Gradle 项目的 ../../)
// 对于 Library 来说是包文件夹(即 Gradle 项目的 ../)
if (isLibrary) {
localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
} else {
localExtension.jsRootDir.convention(localExtension.root)
}
// 我们创建任务以从 JS 文件生成 Schema
val generateCodegenSchemaTask =
project.tasks.register(
"generateCodegenSchemaFromJavaScript",
GenerateCodegenSchemaTask::class.java,
) { it ->
it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
it.codegenDir.set(rootExtension.codegenDir)
it.generatedSrcDir.set(generatedSrcDir)
it.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
// 我们在配置阶段读取 package.json,以便正确地填充此任务的 `jsRootDir` @Input 属性
// 以及 onlyIf 条件。因此,parsePackageJson 应该在这个 lambda 表达式内部被调用。
val packageJson = findPackageJsonFile(project, rootExtension.root)
val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
val includesGeneratedCode =
parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
if (jsSrcsDirInPackageJson != null) {
it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
} else {
it.jsRootDir.set(localExtension.jsRootDir)
}
it.jsInputFiles.set(
project.fileTree(it.jsRootDir) { tree ->
tree.include("**/*.js")
tree.include("**/*.jsx")
tree.include("**/*.ts")
tree.include("**/*.tsx")
tree.exclude("node_modules/**/*")
tree.exclude("**/*.d.ts")
// 我们希望排除 build 目录,以避免在执行规避检查时选中它们
tree.exclude("**/build/**/*")
}
)
val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
}
// 我们创建任务以从 Schema 生成 Java 代码
val generateCodegenArtifactsTask =
project.tasks.register(
"generateCodegenArtifactsFromSchema",
GenerateCodegenArtifactsTask::class.java,
) { task ->
task.dependsOn(generateCodegenSchemaTask)
task.reactNativeDir.set(rootExtension.reactNativeDir)
task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
task.generatedSrcDir.set(generatedSrcDir)
task.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
task.libraryName.set(localExtension.libraryName)
task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
// 请注意,appNeedsCodegen 会触发在配置阶段读取 package.json,
// 因为我们需要填充此任务的 onlyIf 条件。
// 因此,appNeedsCodegen 需要在这个 lambda 表达式内部被调用
val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
val packageJson = findPackageJsonFile(project, rootExtension.root)
val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
val includesGeneratedCode =
parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
task.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
}
// 我们更新 Android 配置以包含生成的源码
// 这相当于以下的 DSL:
//
// android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
if (isLibrary) {
project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext
->
ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
}
} else {
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl {
ext ->
ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
}
}
// `preBuild` 是 AGP 自动注册的基础任务之一
// 这将在编译整个项目之前调用 Codegen
project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
}
configureCodegen 是 React Native Gradle 插件的核心方法,负责配置 Fabric/TurboModule 新架构的代码生成(Codegen)流程。该方法在 Gradle 配置阶段执行,设置两个关键任务和 Android 源码集集成。
其流程可以分为五个阶段:
-
设置输出目录和 JS 根目录
-
创建 Schema 生成任务
-
创建代码生成任务
-
集成到 Android 构建系统
-
挂载到构建生命周期
这里概括一下整个流程:
1. Gradle 配置阶段
├─> 设置 generatedSrcDir = "build/generated/source/codegen"
├─> 设置 jsRootDir 默认值(Library: "../", App: root)
├─> 读取 package.json(配置阶段)
│ ├─> 检查 codegenConfig.jsSrcsDir
│ └─> 检查 codegenConfig.includesGeneratedCode
├─> 注册 generateCodegenSchemaTask
│ ├─> 配置 Node.js 环境
│ ├─> 设置 JS 输入文件(**/*.{js,jsx,ts,tsx})
│ └─> 设置 onlyIf 条件
├─> 注册 generateCodegenArtifactsTask
│ ├─> 依赖 generateCodegenSchemaTask
│ └─> 设置 onlyIf 条件
├─> 配置 Android SourceSets
│ └─> 添加 generatedSrcDir/java 到 main SourceSet
└─> 建立 preBuild 依赖关系
2. Gradle 执行阶段(运行 ./gradlew build)
├─> preBuild 任务执行
│ └─> generateCodegenArtifactsTask 执行
│ ├─> generateCodegenSchemaTask 执行
│ │ ├─> 扫描 JS/TS 文件
│ │ └─> 生成 schema.json
│ └─> 根据 schema.json 生成 Java/C++ 代码
└─> compileJava 编译生成的代码
这里的两个关键任务分别是generateCodegenSchemaFromJavaScript和generateCodegenArtifactsFromSchema。前者从 JS/TS 生成 Schema JSON文件,后者从 Schema 生成 Java/C++ 代码。
Schema JSON主要是JS/TS的接口描述,生成路径是Your Project/node_modules/Third-party Lib/android/build/generated/source/codegen/schema.json,可以自行查看,这里我们重点关注generateCodegenArtifactsFromSchema,源码react-native/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt:
abstract class GenerateCodegenArtifactsTask : Exec() {
@get:Internal abstract val reactNativeDir: DirectoryProperty
@get:Internal abstract val generatedSrcDir: DirectoryProperty
@get:InputFile abstract val packageJsonFile: RegularFileProperty // package.json 文件路径
@get:Input abstract val nodeWorkingDir: Property<String>
@get:Input abstract val nodeExecutableAndArgs: ListProperty<String>
@get:Input abstract val codegenJavaPackageName: Property<String>
@get:Input abstract val libraryName: Property<String>
@get:InputFile
val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")
@get:OutputDirectory val generatedJavaFiles: Provider<Directory> = generatedSrcDir.dir("java")
@get:OutputDirectory val generatedJniFiles: Provider<Directory> = generatedSrcDir.dir("jni")
override fun exec() {
val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters()
setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName)
super.exec()
}
internal fun resolveTaskParameters(): Pair<String, String> {
val parsedPackageJson =
if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) {
JsonUtils.fromPackageJson(packageJsonFile.get().asFile)
} else {
null
}
val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get()
val resolvedCodegenJavaPackageName =
parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get()
return resolvedLibraryName to resolvedCodegenJavaPackageName
}
internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) {
val workingDir = File(nodeWorkingDir.get())
commandLine(
windowsAwareCommandLine(
*nodeExecutableAndArgs.get().toTypedArray(),
reactNativeDir.file("scripts/generate-specs-cli.js").get().asFile.cliPath(workingDir),
"--platform",
"android",
"--schemaPath",
generatedSchemaFile.get().asFile.cliPath(workingDir),
"--outputDir",
generatedSrcDir.get().asFile.cliPath(workingDir),
"--libraryName",
libraryName,
"--javaPackageName",
codegenJavaPackageName,
)
)
}
}
该类主要用于执行外部 Node.js 命令,依据 schema.json文件,生成 Java/JNI 代码。需要注意一下,这里libraryName和codegenJavaPackageName的取值,代码中是通过resolveTaskParameters方法提取出来的。回顾一下package.json文件的配置,大概是以下结构:
{
"codegenConfig": {
"name": "MyLibrary",
"android": {
"javaPackageName": "com.example.mylibrary"
}
}
}
那么libraryName就是"MyLibrary",codegenJavaPackageName就是"com.example.mylibrary"。
现在再还原一下命令,其实就是以下形式:
node <reactNativeDir>/scripts/generate-specs-cli.js \
--platform android \
--schemaPath <generatedSrcDir>/schema.json \
--outputDir <generatedSrcDir> \
--libraryName <resolvedLibraryName> \
--javaPackageName <resolvedCodegenJavaPackageName>
我们定位到该js文件react-native/packages/react-native/scripts/generate-specs-cli.js:
const executor = require('./codegen/generate-specs-cli-executor');
// 省略......
function main() {
executor.execute(
// $FlowFixMe[prop-missing]
argv.platform,
// $FlowFixMe[prop-missing]
argv.schemaPath,
// $FlowFixMe[prop-missing]
argv.outputDir,
// $FlowFixMe[prop-missing]
argv.libraryName,
// $FlowFixMe[prop-missing]
argv.javaPackageName,
// $FlowFixMe[prop-missing]
argv.libraryType,
);
}
main();
继续跟踪react-native/packages/react-native/scripts/codegen/generate-specs-cli-executor.js
const utils = require('./codegen-utils');
const GENERATORS /*: {[string]: {[string]: $ReadOnlyArray<string>}} */ = {
all: {
android: ['componentsAndroid', 'modulesAndroid', 'modulesCxx'],
ios: ['componentsIOS', 'modulesIOS', 'modulesCxx'],
},
components: {
android: ['componentsAndroid'],
ios: ['componentsIOS'],
},
modules: {
android: ['modulesAndroid', 'modulesCxx'],
ios: ['modulesIOS', 'modulesCxx'],
},
};
function generateSpecFromInMemorySchema(
platform /*: string */,
schema /*: string */,
outputDirectory /*: string */,
libraryName /*: string */,
packageName /*: string */,
libraryType /*: string */,
useLocalIncludePaths /*: boolean */,
) {
validateLibraryType(libraryType);
createOutputDirectoryIfNeeded(outputDirectory, libraryName);
const includeGetDebugPropsImplementation =
libraryName.includes('FBReactNativeSpec'); //only generate getDebugString for React Native Core Components
utils.getCodegen().generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull: platform === 'ios',
useLocalIncludePaths,
includeGetDebugPropsImplementation,
},
{
generators: GENERATORS[libraryType][platform],
},
);
if (platform === 'android') {
// Move all components C++ files to a structured jni folder for now.
// Note: this should've been done by RNCodegen's generators, but:
// * the generators don't support platform option yet
// * this subdir structure is Android-only, not applicable to iOS
const files = fs.readdirSync(outputDirectory);
const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`;
fs.mkdirSync(jniOutputDirectory, {recursive: true});
files
.filter(f => f.endsWith('.h') || f.endsWith('.cpp'))
.forEach(f => {
fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`);
});
}
}
这里核心是调用utils.getCodegen().generate来执行代码生成逻辑,但是这里有一个传参需要注意一下,generators: GENERATORS[libraryType][platform],我们观察GENERATORS的定义就会发现,这里的参数配置正好对应package.json中的codegen配置,由于我们现在研究的是Fabric组件注册,那么这里的参数应该是componentsAndroid。
继续查找generate方法的实现,源码react-native/packages/react-native/scripts/codegen/codegen-utils.js:
/**
* 用于抽象实际代码生成过程的包装器。
* 之所以需要这个包装器,是因为在 Sandcastle 中运行测试时,并非所有环境都像通常那样设置。
* 例如,`@react-native/codegen` 库就不存在。
*
* 借助这个包装器,我们可以模拟代码生成器的 getter 方法,使其返回一个自定义对象,该对象模拟了 Codegen 接口。
*
* @return 一个可以为新架构生成代码的对象。
*/
function getCodegen() /*: $FlowFixMe */ {
let RNCodegen;
try {
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js');
} catch (e) {
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen.js');
}
if (!RNCodegen) {
throw new Error('RNCodegen not found.');
}
return RNCodegen;
}
继续跟踪react-native/packages/react-native-codegen/lib/generators/RNCodegen.js:
const LIBRARY_GENERATORS = {
descriptors: [
generateComponentDescriptorCpp.generate,
generateComponentDescriptorH.generate,
],
events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
states: [generateStateCpp.generate, generateStateH.generate],
props: [
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
// TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
componentsAndroid: [
// JNI/C++ files
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
// Java files
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
componentsIOS: [
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
modulesAndroid: [
generateModuleJniCpp.generate,
generateModuleJniH.generate,
generateModuleJavaSpec.generate,
],
modulesCxx: [generateModuleH.generate],
modulesIOS: [generateModuleObjCpp.generate],
tests: [generateTests.generate],
'shadow-nodes': [
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
};
module.exports = {
allGenerators: ALL_GENERATORS,
generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
useLocalIncludePaths,
includeGetDebugPropsImplementation = false,
libraryGenerators = LIBRARY_GENERATORS,
},
{generators, test},
) {
schemaValidator.validate(schema);
const defaultHeaderPrefix = 'react/renderer/components';
const headerPrefix =
useLocalIncludePaths === true
? ''
: `${defaultHeaderPrefix}/${libraryName}/`;
function composePath(intermediate) {
return path.join(outputDirectory, intermediate, libraryName);
}
const componentIOSOutput = composePath(
useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
);
const modulesIOSOutput = composePath('./');
const outputFoldersForGenerators = {
componentsIOS: componentIOSOutput,
modulesIOS: modulesIOSOutput,
descriptors: outputDirectory,
events: outputDirectory,
props: outputDirectory,
states: outputDirectory,
componentsAndroid: outputDirectory,
modulesAndroid: outputDirectory,
modulesCxx: outputDirectory,
tests: outputDirectory,
'shadow-nodes': outputDirectory,
};
const generatedFiles = [];
for (const name of generators) {
for (const generator of libraryGenerators[name]) {
generator(
libraryName,
schema,
packageName,
assumeNonnull,
headerPrefix,
includeGetDebugPropsImplementation,
).forEach((contents, fileName) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputFoldersForGenerators[name],
});
});
}
}
return checkOrWriteFiles(generatedFiles, test);
},
// 省略......
};
可以看到,这里for (const name of generators)遍历的generators就是我们前面强调过的GENERATORS参数处理。那么此时name的值就是componentsAndroid。
既然确定了参数,那么从libraryGenerators中遍历出来的generator就是以下这些生成器:
componentsAndroid: [
// JNI/C++ files
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
// Java files
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
这里先研究一下generateComponentDescriptorH.generate和generateComponentDescriptorCpp.generate。
源码react-native/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js:
const FileTemplate = ({
libraryName,
componentDefinitions,
headerPrefix,
}: {
libraryName: string,
componentDefinitions: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentDescriptorH.js
*/
#pragma once
${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
${componentDefinitions}
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
} // namespace facebook::react
`;
const ComponentDefinitionTemplate = ({className}: {className: string}) =>
`
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();
// 省略部分代码......
这里我们只简单看一下用于头文件生成的字符串模版。libraryName参数取值我们上面已经分析过了,来自package.json中的配置项。那么还有一个关键参数className的取值需要弄清楚。实际上这里的componentDefinitions和className都来自于schema.json。具体看一下className生成逻辑:
const componentDefinitions = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentDefinitionTemplate({className: componentName});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentDefinitions,
headerPrefix: headerPrefix ?? '',
});
可以看到,实际上是在遍历schema.json中的components字段。在声明组件一节已经创建了Demo工程,现在构建项目生成schema.json,查看相关内容:
{
"libraryName": "",
"modules": {
"CustomWebView": {
"type": "Component",
"components": {
"CustomWebView": {
"extendsProps": [
{
"type": "ReactNativeBuiltInType",
"knownTypeName": "ReactNativeCoreViewProps"
}
],
"events": [省略......],
"props": [省略......],
"commands": []
}
}
}
}
}
那么className就是componentName,也就是自定义的组件名CustomWebView。
C++ 层
弄清楚代码生成的逻辑之后,接下来我们可以直接查看生成的文件内容,主要是ComponentDescriptors.h
#pragma once
#include <react/renderer/components/CustomviewViewSpec/ShadowNodes.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
using CustomWebViewComponentDescriptor = ConcreteComponentDescriptor<CustomWebViewShadowNode>;
void CustomviewViewSpec_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
}
ComponentDescriptors.cpp
#include <react/renderer/components/CustomviewViewSpec/ComponentDescriptors.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
void CustomviewViewSpec_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
registry->add(concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>());
}
}
头文件定义了一个类型别名CustomWebViewComponentDescriptor,然后在实现文件中注册了这个Provider。我们看一下concreteComponentDescriptorProvider函数的实现,源码react-native/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProvider.h:
/*
* 为给定的 `ComponentDescriptor` 类创建一个 `ComponentDescriptorProvider`
*/
template <typename ComponentDescriptorT>
ComponentDescriptorProvider concreteComponentDescriptorProvider()
{
static_assert(
std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
"ComponentDescriptorT must be a descendant of ComponentDescriptor");
return {
ComponentDescriptorT::ConcreteShadowNode::Handle(),
ComponentDescriptorT::ConcreteShadowNode::Name(),
nullptr,
&concreteComponentDescriptorConstructor<ComponentDescriptorT>};
}
返回值是一个ComponentDescriptorProvider类型实例,继续跟踪一下类定义:
/*
* 提供了一种统一的方式来构造特定存储的 `ComponentDescriptor` 类的实例。
* C++ 不允许创建指向构造函数的指针,因此我们必须使用这样的数据结构来操作一组类。
*
* 注意:某些组件的 `handle` 和 `name` 的实际值取决于 `flavor`。
* 如果使用给定的 `flavor` 通过 `constructor` 对象实例化后,
* Provider暴露的 `handle` 和 `name` 值与预期值相同,则该提供者有效。
*/
class ComponentDescriptorProvider final {
public:
ComponentHandle handle;
ComponentName name;
ComponentDescriptor::Flavor flavor;
ComponentDescriptorConstructor *constructor;
};
这里大量使用了C++模版,要想查看真正的handle和name值,需要当实际的模版类型中查找。这里先查看源码react-native/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h:
namespace facebook::react {
/*
* Default template-based implementation of ComponentDescriptor.
* Use your `ShadowNode` type as a template argument and override any methods
* if necessary.
*/
template <typename ShadowNodeT>
class ConcreteComponentDescriptor : public ComponentDescriptor {
static_assert(std::is_base_of<ShadowNode, ShadowNodeT>::value, "ShadowNodeT must be a descendant of ShadowNode");
using SharedShadowNodeT = std::shared_ptr<const ShadowNodeT>;
public:
using ConcreteShadowNode = ShadowNodeT;
// 省略代码......
} // namespace facebook::react
可以看到,ConcreteShadowNode实际上只是一个类型别名,具体的要看模版的实际参数,那么ConcreteShadowNode::Handle就相当于CustomWebViewShadowNode::Handle。这里CustomWebViewShadowNode也是自动生成的代码,我们直接查看android/app/build/generated/source/codegen/jni/react/renderer/components/CustomviewViewSpec/ShadowNodes.h:
/*
* `ShadowNode` for <CustomWebView> component.
*/
using CustomWebViewShadowNode = ConcreteViewShadowNode<
CustomWebViewComponentName,
CustomWebViewProps,
CustomWebViewEventEmitter,
CustomWebViewState>;
继续查看android/app/build/generated/source/codegen/jni/react/renderer/components/CustomviewViewSpec/ShadowNodes.cpp:
#include <react/renderer/components/CustomviewViewSpec/ShadowNodes.h>
namespace facebook::react {
extern const char CustomWebViewComponentName[] = "CustomWebView";
}
现在跟踪一下react-native/packages/react-native/ReactCommon/react/renderer/components/view/ConcreteViewShadowNode.h:
namespace facebook::react {
/*
* Template for all <View>-like classes (classes which have all same props
* as <View> and similar basic behaviour).
* For example: <Paragraph>, <Image>, but not <Text>, <RawText>.
*/
template <
const char *concreteComponentName,
typename ViewPropsT = ViewProps,
typename ViewEventEmitterT = ViewEventEmitter,
typename StateDataT = StateData>
requires(std::is_base_of_v<ViewProps, ViewPropsT>)
class ConcreteViewShadowNode : public ConcreteShadowNode<
concreteComponentName,
YogaLayoutableShadowNode,
ViewPropsT,
ViewEventEmitterT,
StateDataT> {
// 省略代码......
};
} // namespace facebook::react
ConcreteViewShadowNode类中并未实现Handle和Name方法,继续查看父类react-native/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h
namespace facebook::react {
/*
* Base templace class for all `ShadowNode`s which connects exact `ShadowNode`
* type with exact `Props` type.
* `ConcreteShadowNode` is a default implementation of `ShadowNode` interface
* with many handy features.
*/
template <
ComponentName concreteComponentName,
typename BaseShadowNodeT,
typename PropsT,
typename EventEmitterT = EventEmitter,
typename StateDataT = StateData>
class ConcreteShadowNode : public BaseShadowNodeT {
protected:
using ShadowNode::props_;
using ShadowNode::state_;
public:
using BaseShadowNodeT::BaseShadowNodeT;
// 省略......
static ComponentName Name()
{
return ComponentName(concreteComponentName);
}
static ComponentHandle Handle()
{
return ComponentHandle(concreteComponentName);
}
// 省略......
};
} // namespace facebook::react
到这里就很清晰了,Name和Handle方法返回值内部是持有的相同的concreteComponentName,而这个模版参数,根据前面的传参,实际是就是
CustomWebViewComponentName,也就是"CustomWebView"。
扫描Fabric 组件库,生成代码的逻辑其实已经很清楚了,最后只剩下一个问题,真正调用注册的代码在哪里?事实上,安卓中并未真正通过CustomviewViewSpec_registerComponentDescriptorsFromCodegen函数去注册,而是使用了autolinking机制。这部分在《Android端TurboModule分析》一文有详细介绍了,可以去回顾一下GenerateAutolinkingNewArchitecturesFileTask脚本的分析,其中生成的信息源就来自我们前面费了半天劲分析的autolinking.json中的
现在来看一下Gradle脚本生成的autolinking.cpp:
#include "autolinking.h"
#include <CustomviewViewSpec.h>
#include <react/renderer/components/CustomviewViewSpec/ComponentDescriptors.h>
namespace facebook {
namespace react {
std::shared_ptr<TurboModule> autolinking_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams ¶ms) {
auto module_CustomviewViewSpec = CustomviewViewSpec_ModuleProvider(moduleName, params);
if (module_CustomviewViewSpec != nullptr) {
return module_CustomviewViewSpec;
}
return nullptr;
}
std::shared_ptr<TurboModule> autolinking_cxxModuleProvider(const std::string moduleName, const std::shared_ptr<CallInvoker>& jsInvoker) {
return nullptr;
}
void autolinking_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry) {
providerRegistry->add(concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>());
return;
}
} // namespace react
} // namespace facebook
这里生成了autolinking_registerProviders方法,这才是真正注册组件的地方。而此处的代码是由Gradle脚本生成,其中的关键信息就来自autolinking.json中的componentDescriptors字段,也就是前面我们费了半天劲才分析出该字段的默认值的地方。整个React Native的代码生成其实是有些混乱的,会生成一些并不会使用的代码,对理解产生干扰。
关于autolinking_registerProviders函数的调用链,在前面的文章也分析涉及过,这里再回顾一下调用流程,源码react-native/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp:
namespace facebook::react {
void registerComponents(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
// 自定义 Fabric 组件放在这里。您可以在此处注册来自您的应用程序或第三方库的自定义组件
// providerRegistry->add(concreteComponentDescriptorProvider<
// MyComponentDescriptor>());
// We link app local components if available
#ifdef REACT_NATIVE_APP_COMPONENT_REGISTRATION
REACT_NATIVE_APP_COMPONENT_REGISTRATION(registry);
#endif
// And we fallback to the components autolinked
autolinking_registerProviders(registry);
}
// 省略部分代码......
} // namespace facebook::react
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
return facebook::jni::initialize(vm, [] {
facebook::react::DefaultTurboModuleManagerDelegate::cxxModuleProvider =
&facebook::react::cxxModuleProvider;
facebook::react::DefaultTurboModuleManagerDelegate::javaModuleProvider =
&facebook::react::javaModuleProvider;
facebook::react::DefaultComponentsRegistry::
registerComponentDescriptorsFromEntryPoint =
&facebook::react::registerComponents;
});
}
可见,注册的起点也是JNI_OnLoad函数。
Kotlin层
使用npx create-react-native-library@latest工具创建一个Fabric组件库时,会生成一些模版代码,其中包括我们上面提到的CustomWebViewManager类,现在我们来看一下CustomWebViewPackage类:
class CustomWebViewPackage : ReactPackage {
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
viewManagers.add(CustomWebViewManager())
return viewManagers
}
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return emptyList()
}
}
这里createViewManagers方法会返回一个ViewManager的实例列表。根据我们在《ReactNative新架构之Android端TurboModule机制完全解析》一文中分析GeneratePackageListTask任务的结果,我们知道最终会生成一个PackageList文件,其中会注入每个三方TurboModule或Fabric组件包中的ReactPackage实现类。
现在来查看一下我们示例工程生成的example/android/app/build/generated/autolinking/src/main/java/com/facebook/react/PackageList.java文件:
public class PackageList {
private Application application;
private ReactNativeHost reactNativeHost;
private MainPackageConfig mConfig;
// 省略部分代码......
public ArrayList<ReactPackage> getPackages() {
return new ArrayList<>(Arrays.<ReactPackage>asList(
new MainReactPackage(mConfig),
new CustomWebViewPackage()
));
}
}
回顾一下本文开头的初始化部分,我们提到过以下代码
fabricUIManager =
FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)
现在回顾一下《React Native新架构之Android端初始化源码分析》一文,在ReactInstance类构造时,有如下初始化逻辑:
viewManagerResolver = BridgelessViewManagerResolver(reactPackages, context)
ComponentNameResolverBinding.install(
unbufferedRuntimeExecutor,
object : ComponentNameResolver {
override val componentNames: Array<String>
get() {
val viewManagerNames = viewManagerResolver.getViewManagerNames()
if (viewManagerNames.isEmpty()) {
FLog.e(TAG, "No ViewManager names found")
return arrayOf()
}
return viewManagerNames.toTypedArray<String>()
}
},
)
结合上一节查找组件 kotlin层实现的分析,整个流程都十分清晰了。