鸿蒙NEXT(五):鸿蒙版React Native架构浅析

张开发
2026/4/5 0:19:28 15 分钟阅读

分享文章

鸿蒙NEXT(五):鸿蒙版React Native架构浅析
鸿蒙版React Native架构如图React Native for OpenHarmony 在 React Native 的新架构0.68以及之后的版本的基础上进行了鸿蒙化的适配。按照功能可以进行如下的划分RN 应用代码开发者实现的业务代码。RN 库代码在 React Native 供开发者使用的组件和API的封装与声明。JSIJavaScript InterfaceJavaScript 与 CPP 之间进行通信的API。React Common所有平台通用的 CPP 代码用于对 RN 侧传过来的数据进行预处理。OpenHarmony 适配代码接收并处理 React Common 传过来的数据对接原生的代码调用 ArkUI 的原生组件与 API。主要包括了两个部分分别是 TurboModule 与 Fabric。OS代码对接系统底层功能根据适配层代码传过来的数据进行渲染或完成对应的功能。React Native库代码在现行的 React Native 中有很多属性是在React侧完成的封装也有很多属性是平台独有的。为了达成这个效果React Native 在JS侧根据Platform增加了很多判断。所以React Native 的鸿蒙化适配也需要增加HarmonyOS相关的平台判断与相应的组件属性的封装。为此鸿蒙化团队提供了react-native-harmony的tgz包并通过更改metro.config.js配置将该tgz包应用到 Metro Bundler中。React Native 还提供了很多库的封装例如Codegen、打包工具等。为此鸿蒙化团队提供了react-native-harmony-cli的包对这些库进行了HarmonyOS平台的适配用于向开发者提供相关的功能。FabricFabric 是 React Native 的组件渲染系统。接收 React Native 传过来的组件信息处理后发送给原生OS由OS完成页面的渲染。在适配方案中组件不通过复杂的流程对接到ArkUI的声明式范式上而是直接使用XComponent对接到ArkUI的后端接口进行渲染缩短了流程提高了组件渲染的效率。C-API的性能收益包括以下的几个部分C端最小化、无跨语言的组件创建和属性设置无跨语言前的数据格式转换不需要将stringenum等数据类型转换为object可以在CPP侧直接使用对应的数据进行处理可以进行属性Diff避免重复设置降低了属性设置的开销。渲染流水线请参考渲染三阶段。TurboModuleTurboModule 是 React Native 中用于 JavaScript 和原生代码进行交互的模块为RN JS应用提供调用系统能力的机制。根据是否依赖 HarmonyOS系统相关的能力可以分为两类cxxTurboModule和ArkTSTurboModule。ArkTSTurboModuleArkTSTurboModule为 React Native 提供了调用ArkTS原生API的方法。可以分为同步与异步两种。ArkTSTurboModule依赖NAPI进行原生代码与CPP侧的通信。包括JS与C之间的类型转换同步和异步调用的实现等。cxxTurboModulecxxTurboModule主要提供的是不需要系统参与的能力例如NativeAnimatedTurboModule主要提供了数据计算的相关能力。cxxTurboModule不依赖于系统的原生API为了提高相互通信的效率一般是在cpp侧实现这样可以减少native与cpp之间的通信次数提高性能。React Native线程模型RNOH线程模型RNOH的线程一共有3个enum TaskThread { MAIN 0, // main thread running the eTS event loop JS, // React Natives JS runtime thread BACKGROUND, // background tasks queue };MAIN/UI线程RN业务主线程也是应用主线应用UI线程。该线程在应用中有唯一实例。RN在MAIN线程中主要承担的业务功能是ArkUI组件的生命周期管理CREATE, UPDATE, INSERT, REMOVE, DELETEArkUI组件树管理RN TurboModule业务功能运行交互事件、消息处理。JS线程JS线程通过虚拟机执行ReactJS代码通过React代码与RN Common的核心代码交互完成React Native的Render阶段任务。RN在JS线程中主要承担的业务功能是加载Bundle执行Bundle依赖的React代码和Bundle的业务代码。由React业务代码驱动创建RN ShadowTree设置ShadowTree的属性。利用Yoga引擎进行组件布局文本测量和布局。比较完成布局的新、老ShadowTree生成差异结果mutations。将mutations提交到MAIN线程触发Native的显示刷新。交互事件、消息处理。JS线程与RNInstance的实例绑定有多个RNInstance则有多个对应的JS线程。BACKGROUND线程BACKGROUND线程是RN的实验特性开启BACKGROUND线程后会将JS线程的部分布局、ShadowTree比较的任务迁移到该线程执行从而降低JS线程的负荷。由于开启BACKGROUND涉及复杂的线程间通信在稳定性方面带来风险因此正式商用版本中不要开启BACKGROUND线程。RNOH线程的长期演进MAIN线程和JS线程承担了RN框架的全部业务在重载情况下可能会造成性能瓶颈。RN的业务也受同线程的其他应用代码的影响造成执行延迟或阻塞等问题。在长期演进时可以考虑进行线程扩展增加唯一TM线程将TurboModule的业务代码放到TM线程来执行从而降低MAIN线程负荷。增加单独的TIMER线程确保时间基准稳定执行。典型线程Trace图线程号53130MAIN线程线程号53214JS线程实例1线程号53216JS线程实例2命令式组件XComponent接入CAPI 版本使用XComponent总共分成了两个步骤createSurface的时候创建XComponentSurfacestartSurface的时候将CPP的XComponentSurface连接到ArkUI的Xcomponent上。createSurface的时候主要做了以下的操作创建并将XComponentSurface记录到Map中void RNInstanceCAPI::createSurface(facebook::react::Tag surfaceId,std::string const moduleName) {m_surfaceById.emplace(surfaceId,XComponentSurface(···surfaceId,moduleName));}在XComponentSurface中创建rootView用于挂载C-API的组件并在Surface上统一处理Touch事件XComponentSurface::XComponentSurface(···SurfaceId surfaceId,std::string const appKey):···m_nativeXComponent(nullptr),m_rootView(nullptr),m_surfaceHandler(SurfaceHandler(appKey, surfaceId)) {m_scheduler-registerSurface(m_surfaceHandler);m_rootView componentInstanceFactory-create(surfaceId, facebook::react::RootShadowNode::Handle(), “RootView”);m_componentInstanceRegistry-insert(m_rootView);m_touchEventHandler std::make_unique(m_rootView);}startSurface的时候主要做了以下的操作在ArkTS侧创建XComponent并设置idtype与libraryname属性。其中id组件的唯一标识又由InstanceID和SurfaceID共同组成记录了此XComponent属于哪一个Instance与Surfacetypenode标识该XComponent是一个占位组件组件的实现都在CAPI侧libraryname表示C-API组件在哪个so库中实现并加载该so库自动调用该so中定义的Init函数。当前React Native for OpenHarmony默认的so名字为rnoh_app。XComponent({id: this.ctx.rnInstance.getId() “_” this.surfaceHandle.getTag(),type: “node”,libraryname: ‘rnoh_app’})在CPP侧的Init中调用registerNativeXComponent函数该函数中调用了OH_NativeXComponent_GetXComponentId用于获取ArkTS设置的id并根据id找到对应的Instance与Surface。同时还要获取nativeXComponent对象记录ArkTS侧的XComponent。if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, idSize) !OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {···}std::string xcomponentStr(idStr);std::stringstream ss(xcomponentStr);std::string instanceId;std::getline(ss, instanceId, ‘);std::string surfaceId;std::getline(ss, surfaceId, ’);调用OH_NativeXComponent_AttachNativeRootNode将XComponentSurface中记录的rootView连接到ArkTS侧的XComponent上OH_NativeXComponent_AttachNativeRootNode(nativeXComponent,rootView.getLocalRootArkUINode().getArkUINodeHandle());将rootView连接到XComponent后rootView就作为CAPI组件的根节点后续的子孙节点通过Mutation指令逐个插入到组件树上。CAPI组件向上对接RN指令在RN鸿蒙适配层中SchedulerDelegate.cpp负责处理RN Common传递下来的指令。void SchedulerDelegate::schedulerDidFinishTransaction(MountingCoordinator::Shared mountingCoordinator) {…}在 MountingManagerCAPI.cpp 的didMount中对各个指令进行处理。MountingManagerCAPI::didMount(MutationList const mutations) {…}在didMount函数中先根据预先配置的arkTsComponentNames获取ArkTs组件和CAPI组件的指令分别进行处理。其中CAPI组件的指令会在handleMutation方法中逐个遍历每个指令根据指令的类型Create 、Delete、Insert、Remove、Update进行不同的处理。Create指令接收到Create指令后会根据指令的tag、componentName和componentHandle信息创建出一个对应组件类型的ComponentInstance比如Image组件的Create指令会创建对应的ImageComponentInstance。创建完组件之后调用updateComponentWithShadowView方法设置组件的信息。其中setLayout设置组件的布局信息setEventEmitter设置组件的事件发送器setState设置组件的状态setProps设置组件的属性信息。Delete指令根据接收到的Delete指令的tag删除对应组件的ComponentInstance。Insert指令根据接收到Insert指令中包含父节点的tag和子节点的tag将子节点插入到对应的父节点上。Remove指令接收到Remove指令中包含父节点的tag和子节点的tag在父节点上移除对应的子节点。Update指令接收到Update指令后调用组件的setLayout、setEventEmitter、setState、setProps更新组件相关信息。适配层事件分发逻辑1.适配层事件的注册当手势触碰屏幕后会命中相应的结点通过回调发送对应事件但是需要注册事件如一个Stack节点注册了NODE_ON_CLICK事件。StackNode::StackNode() :ArkUINode(NativeNodeAPi::getInstance()-createNode(ArkUI_NodeType::ARKUI_NODE_STACK)), m_stackNodeDelegate(nullptr) { maybeThrow(NativeNodeApi::getInstance()-registerNodeEvent(m_nodeHandle,NODE_ON_CLICK,0,this)); maybeThrow(NativeNodeApi::getInstance()-registerNodeEvent(m_nodeHandle,NODE_ON_HOVER,0,this)); }SurfaceTouchEventHandler注册了NODE_TOUCH_EVENT事件。SurfaceTouchEventHandler( ComponentInstance::Shared rootView, ArkTSMessageHub::Shared arkTSMessageHub,int rnInstanceId): ArkTSMessageHub::Observer(arkTSMessageHub), m_rootView(std::move(rootView)), m_rnInstanceId(rnInstanceId) { ArkUINodeRegistry::getInstance().registerTouchHandler( m_rootView-getLocalRootArkUINode(),this); NativeNodeApi::getInstance()-registerNodeEvent( m_rootView-getLocalRootArkUINode().getArkUINodeHandle(), NODE_TOUCH_EVENT, NODE_TOUCH_EVENT, this); }2.适配层事件的接收ArkUINodeRegistry的构造中注册了一个回调当注册了事件的节点被命中后该事件通过回调传递处理。ArkUINodeRegistry::ArkUINodeRegistry(ArkTSBridge::Shared arkTSBridge):m_arkTSBridge(std::move(arkTSBridge)) { NativeNodeApi::getInstance()-registerNodeEventReceiver( [](ArkUI_NodeEvent* event){ ArkUINodeRegistry::getInstance().receiveEvent(event) }); }3.适配层事件的处理回调传递的参数event通过OH_ArkUI_NodeEvent_GetEventType获取事件类型通过OH_ArkUI_NodeEvent_GetNodeHandle获取触发该事件的结点指针。auto eventType OHArkUI_NodeEvent_GetEventType(event); auto node OH_ArkUI_NodeEvent_GetNodeHandle(event);首先判断事件类型是否为Touch事件如果是就从一个存储了所有TouchEventHandler的Map中通过结点指针作为key去查找对应的TouchEventHandler如果没找到这次Touch事件不处理。if(eventType ArkUI_NodeEventType::NODE_TOUCH_EVENT) { auto it m_touchHandlerByNodeHandle.find(node); if(it m_touchHandlerByNodeHandle.end()) { return; } }如果找到了对应的TouchEventHandler通过OH_ArkUI_NodeEvent_GetInputEvent获取输入事件指针若输入事件指针不为空通过OH_ArkUI_UIInputEvent_GetType判断输入事件指针的类型是否为Touch事件如果不是这次Touch事件不处理。auto inputEvent OH_ArkUI_NodeEvent_GetInputEvent(event); if(inputEvent nullptr || OH_ArkUI_UIInputEvent_GetType(inputEvent) ! ArkUI_UIInputEvent_Type::ARKUI_UIINPUTEVENT_TYPE_TOUCH) { return; }如果上述两个条件都满足就通过TouchEventHandler去处理Touch事件。it-second-onTouchEvent(inputEvent);如果事件类型不为Touch事件就从一个存储了所有ArkUINode的Map中通结点指针作为key去查找对应的ArkUINode若未找到这次事件不处理。auto it m_nodeByHandle.find(node); if(it m_nodeByHandle.end()) { return; }如果找了对应的ArkUINode通过OH_ArkUI_NodeEvent_GetNodeComponentEvent获取组件事件指针该指针的data字段保留了arkUI传递过来的参数并通过ArkUINode处理该事件。auto commponentEvent OH_ArkUI_NodeEvent_GetNodeComponentEvent(event); if(commponentEvent ! nullptr) { it-second-onNodeEvent(eventType,compenentEvent-data); return; }4.Touch事件的传递给JS侧上文中写明TouchEventHandler对Touch事件进行处理以xcomponentSurface举例xcomponentSurface有一个继承了TouchEventHandler的成员变量这个成员变量通过dispatchTouchEvent处理这次Touch事件。void onTouchEvent(ArkUI_UIInputEvent* event)override { m_touchEventDispatcher.dispatchTouchEvent(event,m_rootView); }对于Touch事件首先通过Touch的位置等因素获取对应touchTarget每个componentInstance就是一个touchTarget下图的名字是eventTarget。class ComponentInstance:public TouchTarget,public std::enable_shared_from_thisComponentInstance for(auto const targetTouches:touchByTargetId) { auto it m_touchTargetByTouchId.find(targetTouches.second.begin()-identifier); if(it m_touchTargetByTouchId.end()) { continue; } auto eventTarget it-second.lock(); if(eventTarget nullptr) { m_touchTargetByTouchId.erase(it); continue; } }然后通过componentInstance保存的m_eventEmitter发送对应的事件给js侧从而触发页面的刷新等操作。 Touch事件有以下四种类型:UI_TOUCH_EVENT_ACTION_DOWNUI_TOUCH_EVENT_ACTION_MOVEUI_TOUCH_EVENT_ACTION_UPUI_TOUCH_EVENT_ACTION_CANCELswitch(action){case UI_TOUCH_EVENT_ACTION_DOWN:eventTarget-getTouchEventEmitter()-onTouchStart(touchEvent);break;case UI_TOUCH_EVENT_ACTION_MOVE:eventTarget-getTouchEventEmitter()-onTouchMove(touchEvent);break;case UI_TOUCH_EVENT_ACTION_UP:eventTarget-getTouchEventEmitter()-onTouchEnd(touchEvent);break;case UI_TOUCH_EVENT_ACTION_CANCEL:default:eventTarget-getTouchEventEmitter()-onTouchCancel(touchEvent);break;}5、非Touch事件的传递给js侧上文中写明非Touch事件由ArkUINode处理对于每个继承了ArkUINode的类重载了onNodeEvent方法以StackNode举例说明RN适配层是如何区分Click事件和Touch事件。前文说明StackNode注册了Click事件所以通过回调会走到StackNode的onNodeEvent部分这里会先判断这个事件类型这里是NODE_ON_CLICK类型符合要求但是对于第二个条件eventArgs[3].i32(即上文描述的arkUI传递过来的参数)如果是触屏手机其值为2不满足eventArgs[3].i32 2的条件。void StackNode::onNodeEvent(ArkUI_NodeEventType eventType,EventArgs eventArgs) { if(eventType ArkUI_NodeEventType::NODE_ON_CLICK eventArgs[3].i32 ! 2) { onClick(); } if(eventType ArkUI_NodeEventType::NODE_ON_HOVER) { if(m_stackNodeDelegate ! nullptr) { if(eventArgs[0].i32) { m_stackNodeDelegate-onHoverIn(); }else { m_stackNodeDelegate-onHoverOut(); } } } }所以此时实际上不会触发Click的事件因此Touch事件和Click事件不会冲突。如果触发了Click事件StackNode会通过代理StackNodeDelegate发送事件。void StackNode::onClick() { if(m_stackNodeDelegate ! nullptr) { m_stackNodeDelegate-onClick(); } }其中ViewComponentInstance继承了StackNodeDelegate所以实际上走的是ViewComponentInstance的onClick函数。namespace rnoh { class ViewComponentInstance :public CppComponentInstancefacebook::react::ViewShardowNode,public StackNodeDelegate { } }这个函数通过ViewComponentInstance的m_eventEmitter发送事件给JS从而触发页面的刷新。void ViewComponentInstance::onClick() { if(m_eventEmitter ! nullptr) { m_eventEmitter-dispatchEvent(click,[](facebook:jsi::Runtime runtime) {auto payload facebook::jsi::Object(runtime); return payload; }); } }鸿蒙版React Native启动流程鸿蒙RN启动阶段分为RN容器创建、Worker线程启动、NAPI方法初始化、RN实例创建四个阶段接下来加载bundle和界面渲染类图如下所示React Native容器创建EntryAbility全局AbilityApp的启动入口。Index.etsApp页面入口。RNApp.ets配置appKey和JS侧registerComponent注册的appKey关联配置初始化参数initialProps传递给js页面配置jsBundleProvider指定bundle加载路径配置ArkTS混合组件wrappedCustomRNComponentBuilder配置rnInstanceConfig指定开发者自定义package注入字体文件fontResourceByFontFamily设置BG线程开关设置C-API开关持有RNSurface作为RN页面容器。RNSurface.etsRN页面容器持有XComponent用于挂载ArkUI的C-API节点和响应手势事件。Worker线程启动TurboModule运行在worker线程worker线程是在程序启动时创建。WorkerThread.tsEntryAbility创建时会创建RNInstancesCoordinatorRNInstancesCoordinator的构造函数中获取worker线程类地址然后调用WorkerThread的create方法启动worker线程如下const workerThread new WorkerThread(logger, new worker.ThreadWorker(scriptUrl, { name: name }), onWorkerError)RNOHWorker.etsWorkerThread中配置的scriptUrl即RNOHWorker.ets路径RNOHWorker.ets内部调用setRNOHWorker.ets的setRNOHWorker方法配置worker线程收发消息通道。setRNOHWorker.etssetRNOHWorker方法配置worker线程收发消息通道createTurboModuleProvider方法注册系统自带和开发者自定义的运行在worker线程的TurboModule。NAPI方法初始化RNOHAppNapiBridge.cppInit方法是静态方法在程序启动时调用配置了18个ArkTS调用C的方法如下registerWorkerTurboModuleProvider, getNextRNInstanceId, onCreateRNInstance, // 创建RN实例 onDestroyRNInstance, // 销毁RN实例 loadScript, // 加载bundle startSurface, stopSurface, destroySurface, createSurface, // 创建RN界面 updateSurfaceConstraints, setSurfaceDisplayMode, onArkTSMessage, emitComponentEvent, // 给RN JS发消息 callRNFunction, onMemoryLevel, updateState, getInspectorWrapper, getNativeNodeIdByTagNapiBridge.tsArkTS侧RNInstance.ts、SurfaceHandle.ts调用C的桥梁。React Native实例创建在RNInstance.ts中创建RN实例分为以下步骤获取RNInstance的id在RNInstanceRegistry.ets中通过NAPI调用getNextRNInstanceId方法获取。注册ArkTS侧TurboModule在RNInstance.ts中调用processPackage方法注册系统自带和开发者自定义的运行在UI线程上的TurboModule。注册字体在RNInstanceFactory.h中调用FontRegistry.h的registerFont方法注册应用侧扩展字体接着通过图形接口注入字体信息。注册RN官方能力和开发者自定义能力RNInstanceFactory.h中通过PackageProvider.cpp的getPackage方法获取RN系统自带和开发者自定义TurboModule接着注册系统View、系统自带TurboModule、开发者自定义View、开发者自定义TurboModule。注册ArkTS混合组件在RNInstanceFactory.cpp中注册ArkTS侧传递到C的ArkTS组件。初始化JS引擎在RNInstanceInternal.cpp中初始化JS引擎Hermes或者JSVM通过JS引擎驱动JS消息队列。注册TM的JSI通道在RNInstanceCAPI.cpp中调用createTurboModuleProvider创建TurboModuleProvider注入__turboModuleProxy对象给JS侧。注入Scheduler在RNInstanceInternal.cpp中初始化Fabric的Scheduler对象ReactCommon的组件绘制找到鸿蒙适配层注入的SchedulerDelegate才能进行界面绘制。注册Fabric的JSI通道在RNInstanceInternal.cpp中调用UIManagerBinding.cpp的createAndInstallIfNeeded方法注入nativeFabricUIManager对象给JS侧。加载bundleRN实例创建完毕则开始加载bundle如下ArkTS侧加载bundle、C侧加载bundle切线程到ReactCommon的Instance.cpp中加载bundleRNApp.ets RNInstance.ts RNOHAppNapiBridge.cpp RNInstanceInternal.cpp Instance.cpp总结本文详细介绍了鸿蒙版 React Native 架构。包括按功能划分的架构组成如 RN 应用代码、库代码、JSI、React Common、OpenHarmony 适配代码及 OS 代码等。还阐述了 Fabric、TurboModule、线程模型、命令式组件、启动流程等方面内容。启动流程分为 RN 容器创建、Worker 线程启动、NAPI 方法初始化、RN 实例创建及加载 bundle 等阶段。整体架构复杂且功能明确为开发者提供了在鸿蒙平台上使用 React Native 的技术支持。

更多文章