鸿蒙版React Native架构
如图,React Native for OpenHarmony 在 React Native 的新架构(0.68以及之后的版本)的根本上,举行了鸿蒙化的适配。按照功能可以举行如下的分别:
- RN 应用代码:开发者实现的业务代码。
- RN 库代码:在 React Native 供开发者使用的组件和API的封装与声明。
- JSI(JavaScript Interface):JavaScript 与 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平台的适配,用于向开发者提供相干的功能。
Fabric
Fabric 是 React Native 的组件渲染体系。吸收 React Native 传过来的组件信息,处理处罚后发送给原生OS,由OS完成页面的渲染。
在适配方案中,组件不通过复杂的流程对接到ArkUI的声明式范式上,而是直接使用XComponent对接到ArkUI的后端接口举行渲染,收缩了流程,进步了组件渲染的服从。C-API的性能收益包罗以下的几个部门:
- C端最小化、无跨语言的组件创建和属性设置;
- 无跨语言前的数据格式转换,不必要将string,enum等数据范例转换为object,可以在CPP侧直接使用对应的数据举行处理处罚;
- 可以举行属性Diff,克制重复设置,低落了属性设置的开销。
渲染流水线请参考渲染三阶段。
TurboModule
TurboModule 是 React Native 中用于 JavaScript 和原生代码举行交互的模块,为RN JS应用提供调用体系本领的机制。根据是否依赖 HarmonyOS体系相干的本领,可以分为两类:cxxTurboModule和ArkTSTurboModule。
- ArkTSTurboModule为 React Native 提供了调用ArkTS原生API的方法。可以分为同步与异步两种。
- ArkTSTurboModule依赖NAPI举行原生代码与CPP侧的通讯。包罗JS与C之间的范例转换,同步和异步调用的实现等。
- cxxTurboModule紧张提供的是不必要体系到场的本领,比方NativeAnimatedTurboModule紧张提供了数据盘算的相干本领。
- cxxTurboModule不依赖于体系的原生API,为了进步相互通讯的服从,一样平常是在cpp侧实现,如答应以淘汰native与cpp之间的通讯次数,进步性能。
React Native线程模子
RNOH线程模子
RNOH的线程一共有3个:
- enum TaskThread {
- MAIN = 0, // main thread running the eTS event loop
- JS, // React Native's JS runtime thread
- BACKGROUND, // background tasks queue
- };
复制代码 MAIN/UI线程
RN业务主线程,也是应用主线,应用UI线程。该线程在应用中有唯一实例。
RN在MAIN线程中紧张负担的业务功能是:
- ArkUI组件的生命周期管理:CREATE, UPDATE, INSERT, REMOVE, DELETE;
- ArkUI组件树管理;
- RN TurboModule业务功能运行;
- 交互变乱、消息处理处罚。
JS线程
JS线程通过捏造机实验React(JS)代码,通过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图
- 线程号53130:MAIN线程
- 线程号53214:JS线程实例1
- 线程号53216:JS线程实例2
下令式组件
XComponent接入
CAPI 版本使用XComponent统共分成了两个步调:
- createSurface的时间创建XComponentSurface;
- startSurface的时间将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,并设置id,type与libraryname属性。此中:
- id:组件的唯一标识,又由InstanceID和SurfaceID共同构成,纪录了此XComponent属于哪一个Instance与Surface;
- type:node,标识该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_this<ComponentInstance>
- 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_DOWN
- UI_TOUCH_EVENT_ACTION_MOVE
- UI_TOUCH_EVENT_ACTION_UP
- UI_TOUCH_EVENT_ACTION_CANCEL
switch(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 CppComponentInstance<facebook::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
全局Ability,App的启动入口。
- Index.ets
App页面入口。
- RNApp.ets
- 设置appKey,和JS侧registerComponent注册的appKey关联;
- 设置初始化参数initialProps,转达给js页面;
- 设置jsBundleProvider,指定bundle加载路径;
- 设置ArkTS混淆组件wrappedCustomRNComponentBuilder;
- 设置rnInstanceConfig,指定开发者自界说package,注入字体文件fontResourceByFontFamily,设置BG线程开关,设置C-API开关;
- 持有RNSurface,作为RN页面貌面貌器。
- RNSurface.ets
RN页面貌面貌器,持有XComponent用于挂载ArkUI的C-API节点和相应手势变乱。
Worker线程启动
TurboModule运行在worker线程,worker线程是在步伐启动时创建。
- WorkerThread.ts
EntryAbility创建时会创建RNInstancesCoordinator,RNInstancesCoordinator的构造函数中获取worker线程类地点,然后调用WorkerThread的create方法启动worker线程,如下:
const workerThread = new WorkerThread(logger, new worker.ThreadWorker(scriptUrl, { name: name }), onWorkerError)
- RNOHWorker.ets
WorkerThread中设置的scriptUrl即RNOHWorker.ets路径,RNOHWorker.ets内部调用setRNOHWorker.ets的setRNOHWorker方法设置worker线程收发消息通道。
- setRNOHWorker.ets
setRNOHWorker方法设置worker线程收发消息通道,createTurboModuleProvider方法注册体系自带和开发者自界说的运行在worker线程的TurboModule。
NAPI方法初始化
Init方法是静态方法,在步伐启动时调用,设置了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,
- getNativeNodeIdByTag
复制代码
ArkTS侧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侧。
加载bundle
RN实例创建完毕则开始加载bundle,如下:
ArkTS侧加载bundle、C++侧加载bundle,切线程到ReactCommon的Instance.cpp中加载bundle:
- RNApp.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 的技能支持。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|