IT评测·应用市场-qidao123.com

标题: 鸿蒙NEXT(五):鸿蒙版React Native架构浅析 [打印本页]

作者: 悠扬随风    时间: 2025-1-1 22:05
标题: 鸿蒙NEXT(五):鸿蒙版React Native架构浅析
鸿蒙版React Native架构


如图,React Native for OpenHarmony 在 React Native 的新架构(0.68以及之后的版本)的底子上,进行了鸿蒙化的适配。按照功能可以进行如下的分别:

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的性能收益包罗以下的几个部分:

渲染流水线请参考渲染三阶段。
TurboModule


TurboModule 是 React Native 中用于 JavaScript 和原生代码进行交互的模块,为RN JS应用提供调用体系本领的机制。根据是否依赖 HarmonyOS体系相干的本领,可以分为两类:cxxTurboModule和ArkTSTurboModule。


React Native线程模型

RNOH线程模型

RNOH的线程一共有3个:

  1. enum TaskThread {
  2.   MAIN = 0, // main thread running the eTS event loop
  3.   JS, // React Native's JS runtime thread
  4.   BACKGROUND, // background tasks queue
  5. };
复制代码
MAIN/UI线程

RN业务主线程,也是应用主线,应用UI线程。该线程在应用中有唯一实例。
RN在MAIN线程中主要承担的业务功能是:

JS线程

JS线程通过假造机执行React(JS)代码,通过React代码与RN Common的核心代码交互完成React Native的Render阶段使命。
RN在JS线程中主要承担的业务功能是:

JS线程与RNInstance的实例绑定,有多个RNInstance,则有多个对应的JS线程。
BACKGROUND线程

BACKGROUND线程是RN的实行特性,开启BACKGROUND线程后,会将JS线程的部分布局、ShadowTree比力的使命迁移到该线程执行,从而降低JS线程的负荷。
由于开启BACKGROUND涉及复杂的线程间通讯,在稳固性方面带来风险,因此正式商用版本中不要开启BACKGROUND线程。
RNOH线程的长期演进

MAIN线程和JS线程承担了RN框架的全部业务,在重载环境下可能会造成性能瓶颈。RN的业务也受同线程的其他应用代码的影响,造成执行延迟或阻塞等题目。
在长期演进时,可以考虑进行线程扩展:

典范线程Trace图



下令式组件

XComponent接入


CAPI 版本使用XComponent总共分成了两个步调:
createSurface的时间主要做了以下的操作:
startSurface的时间主要做了以下的操作:

CAPI组件向上对接RN指令

在didMount函数中,先根据预先设置的arkTsComponentNames获取ArkTs组件和CAPI组件的指令,分别进行处理。此中CAPI组件的指令会在handleMutation方法中逐个遍历每个指令,根据指令的范例(Create 、Delete、Insert、Remove、Update)进行差别的处理。

适配层事故分发逻辑

1.适配层事故的注册

当手势触碰屏幕后会命中相应的结点,通过回调发送对应事故,但是需要注册事故,如一个Stack节点注册了NODE_ON_CLICK事故。
  1. StackNode::StackNode()
  2. :ArkUINode(NativeNodeAPi::getInstance()->createNode(ArkUI_NodeType::ARKUI_NODE_STACK)),
  3.     m_stackNodeDelegate(nullptr)
  4.     {
  5.         maybeThrow(NativeNodeApi::getInstance()->registerNodeEvent(m_nodeHandle,NODE_ON_CLICK,0,this));
  6.         maybeThrow(NativeNodeApi::getInstance()->registerNodeEvent(m_nodeHandle,NODE_ON_HOVER,0,this));
  7.     }
复制代码
SurfaceTouchEventHandler注册了NODE_TOUCH_EVENT事故。
  1. SurfaceTouchEventHandler(
  2.     ComponentInstance::Shared rootView,
  3.     ArkTSMessageHub::Shared arkTSMessageHub,int rnInstanceId):
  4.     ArkTSMessageHub::Observer(arkTSMessageHub),
  5.     m_rootView(std::move(rootView)),
  6.     m_rnInstanceId(rnInstanceId)
  7.     {
  8.         ArkUINodeRegistry::getInstance().registerTouchHandler(
  9.             &m_rootView->getLocalRootArkUINode(),this);
  10.             NativeNodeApi::getInstance()->registerNodeEvent(
  11.                 m_rootView->getLocalRootArkUINode().getArkUINodeHandle(),
  12.                 NODE_TOUCH_EVENT,
  13.                 NODE_TOUCH_EVENT,
  14.                 this);
  15.     }
复制代码
2.适配层事故的吸收

ArkUINodeRegistry的构造中注册了一个回调,当注册了事故的节点被命中后,该事故通过回调通报处理。
  1. ArkUINodeRegistry::ArkUINodeRegistry(ArkTSBridge::Shared arkTSBridge):m_arkTSBridge(std::move(arkTSBridge))
  2. {
  3.     NativeNodeApi::getInstance()->registerNodeEventReceiver(
  4.         [](ArkUI_NodeEvent* event){
  5.             ArkUINodeRegistry::getInstance().receiveEvent(event);
  6.             });
  7. }
复制代码
3.适配层事故的处理

回调通报的参数event通过OH_ArkUI_NodeEvent_GetEventType获取事故范例,通过OH_ArkUI_NodeEvent_GetNodeHandle获取触发该事故的结点指针。
  1. auto eventType = OHArkUI_NodeEvent_GetEventType(event);
  2. auto node = OH_ArkUI_NodeEvent_GetNodeHandle(event);
复制代码
起首判断事故范例是否为Touch事故,如果是,就从一个存储了所有TouchEventHandler的Map中通过结点指针作为key去查找对应的TouchEventHandler,如果没找到,这次Touch事故不处理。
  1. if(eventType == ArkUI_NodeEventType::NODE_TOUCH_EVENT)
  2. {
  3.     auto it = m_touchHandlerByNodeHandle.find(node);
  4.     if(it == m_touchHandlerByNodeHandle.end())
  5.     {
  6.         return;
  7.     }
  8. }
复制代码
如果找到了对应的TouchEventHandler,通过OH_ArkUI_NodeEvent_GetInputEvent获取输入事故指针,若输入事故指针不为空,通过OH_ArkUI_UIInputEvent_GetType判断输入事故指针的范例是否为Touch事故,如果不是,这次Touch事故不处理。
  1. auto inputEvent = OH_ArkUI_NodeEvent_GetInputEvent(event);
  2. if(inputEvent == nullptr || OH_ArkUI_UIInputEvent_GetType(inputEvent) != ArkUI_UIInputEvent_Type::ARKUI_UIINPUTEVENT_TYPE_TOUCH)
  3. {
  4.     return;
  5. }
复制代码
如果上述两个条件都满意,就通过TouchEventHandler去处理Touch事故。
  1. it->second->onTouchEvent(inputEvent);
复制代码
如果事故范例不为Touch事故,就从一个存储了所有ArkUINode的Map中通结点指针作为key去查找对应的ArkUINode,若未找到,这次事故不处理。
  1. auto it = m_nodeByHandle.find(node);
  2. if(it == m_nodeByHandle.end())
  3. {
  4.     return;
  5. }
复制代码
如果找了对应的ArkUINode,通过OH_ArkUI_NodeEvent_GetNodeComponentEvent获取组件事故指针,该指针的data字段保留了arkUI通报过来的参数,并通过ArkUINode处理该事故。
  1. auto commponentEvent = OH_ArkUI_NodeEvent_GetNodeComponentEvent(event);
  2. if(commponentEvent != nullptr)
  3. {
  4.     it->second->onNodeEvent(eventType,compenentEvent->data);
  5.     return;
  6. }
复制代码
4.Touch事故的通报给JS侧

上文中写明TouchEventHandler对Touch事故进行处理,以xcomponentSurface举例,xcomponentSurface有一个继续了TouchEventHandler的成员变量,这个成员变量通过dispatchTouchEvent处理这次Touch事故。
  1. void onTouchEvent(ArkUI_UIInputEvent* event)override
  2. {
  3.     m_touchEventDispatcher.dispatchTouchEvent(event,m_rootView);
  4. }
复制代码
对于Touch事故起首通过Touch的位置等因素,获取对应touchTarget(每个componentInstance就是一个touchTarget,下图的名字是eventTarget)。
  1. class ComponentInstance:public TouchTarget,public std::enable_shared_from_this<ComponentInstance>
  2. for(auto const& targetTouches:touchByTargetId)
  3. {
  4.     auto it = m_touchTargetByTouchId.find(targetTouches.second.begin()->identifier);
  5.     if(it == m_touchTargetByTouchId.end())
  6.     {
  7.         continue;
  8.     }
  9.     auto eventTarget = it->second.lock();
  10.     if(eventTarget == nullptr)
  11.     {
  12.         m_touchTargetByTouchId.erase(it);
  13.         continue;
  14.     }
  15. }
复制代码
然后通过componentInstance生存的m_eventEmitter发送对应的事故给js侧,从而触发页面的刷新等操作。 Touch事故有以下四种范例:

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的条件。
  1. void StackNode::onNodeEvent(ArkUI_NodeEventType eventType,EventArgs& eventArgs)
  2. {
  3.     if(eventType == ArkUI_NodeEventType::NODE_ON_CLICK && eventArgs[3].i32 != 2)
  4.     {
  5.         onClick();
  6.     }
  7.     if(eventType == ArkUI_NodeEventType::NODE_ON_HOVER)
  8.     {
  9.         if(m_stackNodeDelegate != nullptr)
  10.         {
  11.             if(eventArgs[0].i32)
  12.             {
  13.                 m_stackNodeDelegate->onHoverIn();
  14.             }else
  15.             {
  16.                 m_stackNodeDelegate->onHoverOut();
  17.             }
  18.         }
  19.     }
  20. }
复制代码
所以此时现实上不会触发Click的事故,因此Touch事故和Click事故不会辩论。如果触发了Click事故,StackNode会通过代理StackNodeDelegate发送事故。
  1. void StackNode::onClick()
  2. {
  3.     if(m_stackNodeDelegate != nullptr)
  4.     {
  5.         m_stackNodeDelegate->onClick();
  6.     }
  7. }
复制代码
此中ViewComponentInstance继续了StackNodeDelegate,所以现实上走的是ViewComponentInstance的onClick函数。
  1. namespace rnoh
  2. {
  3.     class ViewComponentInstance
  4.     :public CppComponentInstance<facebook::react::ViewShardowNode>,public StackNodeDelegate
  5.     {
  6.     }
  7. }
复制代码
这个函数通过ViewComponentInstance的m_eventEmitter发送事故给JS,从而触发页面的刷新。
  1. void ViewComponentInstance::onClick()
  2. {
  3.     if(m_eventEmitter != nullptr)
  4.     {
  5.         m_eventEmitter->dispatchEvent("click",[=](facebook:jsi::Runtime& runtime)
  6.         {auto payload = facebook::jsi::Object(runtime);
  7.                 return payload;
  8.         });
  9.     }
  10. }
复制代码
鸿蒙版React Native启动流程

鸿蒙RN启动阶段分为RN容器创建、Worker线程启动、NAPI方法初始化、RN实例创建四个阶段,接下来加载bundle和界面渲染,类图如下所示:

React Native容器创建


RN页面目面貌器,持有XComponent用于挂载ArkUI的C-API节点和响应手势事故。
Worker线程启动

TurboModule运行在worker线程,worker线程是在程序启动时创建。

NAPI方法初始化


Init方法是静态方法,在程序启动时调用,设置了18个ArkTS调用C++的方法,如下:
  1. registerWorkerTurboModuleProvider,
  2. getNextRNInstanceId,
  3. onCreateRNInstance,                  // 创建RN实例
  4. onDestroyRNInstance,                 // 销毁RN实例
  5. loadScript,                          // 加载bundle
  6. startSurface,
  7. stopSurface,
  8. destroySurface,
  9. createSurface,                       // 创建RN界面
  10. updateSurfaceConstraints,
  11. setSurfaceDisplayMode,
  12. onArkTSMessage,
  13. emitComponentEvent,                  // 给RN JS发消息
  14. callRNFunction,
  15. onMemoryLevel,
  16. updateState,
  17. getInspectorWrapper,
  18. getNativeNodeIdByTag
复制代码

ArkTS侧RNInstance.ts、SurfaceHandle.ts调用C++的桥梁。
React Native实例创建

在RNInstance.ts中创建RN实例,分为以下步调:
加载bundle

RN实例创建完毕则开始加载bundle,如下:
ArkTS侧加载bundle、C++侧加载bundle,切线程到ReactCommon的Instance.cpp中加载bundle:
  1. 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企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4