react native for harmony整体架构说明
[TOC]
待决策:优先基于旧架构分析RN对Open Harmony的依赖
react native从22年年中发布了0.69版本,并正式开始支持新架构(New Architecture
),新架构在性能,开发效率等都有重大提升。按照官网的说法,老架构(legacy architecture
)将逐步废弃。
Native Module and Native Components are our stable technologies used by the legacy architecture. They will be deprecated in the future when the New Architecture will be stable. The New Architecture uses Turbo Native Module and Fabric Native Components to achieve similar results. ---- RN官网
但鉴于存量的三方类RN框架主要以老架构(legacy architecture
)为主,而且后续迁移到新架构本文的部分设计可以复用(复用部分后续介绍),所以本文同时对新,旧架构同时进行了分析。
此外,由于rn for android和ios的原理基本类似,故这里主要以rn for android为例介绍
如上图所示,RN 主要涉及以上两个部分,包括应用层,框架层。其中RN框架,主要包括几个模块(紫色部分需要鸿蒙化)
常说的RN是一个跨平台框架,只是说RN应用可以跨平台的,而不是说RN框架本身跨平台的。换句话说,RN框架针对不同的平台有不同的实现。上述提到的各个模块都是强耦合系统能力的,尤其是紫色部分需要鸿蒙化
其中sdk,JSI,框架核心,各种Native模块针对不同的os平台都有对应平台语言的实现。而RN JS框架由于最终要调用os能力,故在适配层有不同的实现。
由于各个模块包含不同的方案,总体工作量需要待决策后补齐
不包括商用落地的需求:如性能优化,国际化,无障碍,多语言,安全隐私等
领域 | 工作量 |
---|---|
JS引擎 & 语言 | 6~10人月 |
开发环境 & 调试 | 53人月 |
SDK & 工程目录 | 8人月 |
基础框架核心能力 | 5人月 |
框架核心能力-线程模型/任务调度 | 10人月 |
框架核心-模块化管理 | 14人月 |
UI模块 | 39人月 |
API | 20~30人月 |
其他商用落地:安全,性能,无障碍,国际化,隐私,CI等 | 50人月 |
三四方库 | 分析中 |
总体 | 220人月 + 三方 |
领域 | 工作量(不包括OH4.0) | 备注 |
---|---|---|
Ark引擎和语言 | 分析中 | |
IDE & 工具链 | 10人月 | |
ArkUI | 30人月 + (OH4. 0 20人月) | |
API | 明确应用后分析,基本可控 | 需要进一步明确典型应用 |
webview | 明确应用后分析,基本可控 | OH4.0满足基本功能 需要进一步明确典型应用 |
三方库 | 分析中 | 需要进一步明确典型应用 |
图形 | 2人月 | |
包管理 & 元能力 | 3.2交付 |
以anroid平台为例,代码行数说明:(不包含后续介绍的三方库)
代码类别 | 代码行数 | 说明 |
---|---|---|
JAVA (框架) | 10W+ | 全部重写 |
JS (RN JS框架) | 26W+ | 适配层重写,大部分复用 |
JS (工具链 | 4W | 适配层重写,大部分复用 |
c++ (JSI) | ??? | 适配层重写(harmony基于napi) |
C++ (Hermes,Yoga) | ??? | 编译配置(cmake,c库) |
说明:其中的二三四方,主要相对于OS(如android); 三方特指RN本身。
强调: React、React Native本身的强大之处在于社区丰富度上,meta维护的只是很小的一部分(core部分),其他功能都在React Native Community运作,而且这些功能都是React或React Native应用必须的。
主要分三类:
- Native生态:Android:30+ (build.gradle) , 这里直列了core部分的依赖
- Node JS生态: ~700(node_moduels),这里直列了core部分的依赖
- React Native 生态:1195 react community,以及基于React Native进行二次封装的:比如Expo(遵循React Native规范),国内包括类RN部分(独立规范)。
其中,比较典型的三四方库,如下:(Android为例)
具体参考 框架: build.gradle
其他的四方java库,后续需要进一步打开分析,如下:
- appcompat-v7
- boost_1_76_0
- List item
- double-conversion-1.1.6
- glog-0.3.5
- Fmt
- com.android.builder.model.v2.AndroidModel
- com.google.code.findbugs
- androidx.autofill
- androidx.swiperefreshlayout
以及各种测试框架:
- junit:junit:${JUNIT_VERSION
- org.assertj:assertj-core:${ASSERTJ_VERSION
- org.mockito:mockito-core:${MOCKITO_CORE_VERSION
- org.powermock:powermock-api-mockito2:${POWERMOCK_VERSION}
- org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}
- org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}
- org.robolectric:robolectric:${ROBOLECTRIC_VERSION}
- fileTree(dir: "src/main/third-party/java/buck-android-support/", include: ["*.jar"])
- androidx.test${ANDROIDX_TEST_VERSION}
- androidx.test:rules:${ANDROIDX_TEST_VERSION}
- org.mockito:mockito-core:${MOCKITO_CORE_VERSION}
具体参考:
React Native Comunity本身也是Node/js的一部分。
具体参照官网介绍: 新架构 业界介绍
https://cloud.tencent.com/developer/article/1889895 https://cloud.tencent.com/developer/article/1878792 https://cloud.tencent.com/developer/article/1950899
主要是性能和开发工程效率的增强,换句话说,对上不影响RN应用,对下不影响系统适配。具体,主要包括以下几个方面
总的来说,0.69主要在以下几个方面进行比较大的改动,同时也涉及鸿蒙化相关的工作
待补
官网介绍:JavaScriptRuntime
RN基于js框架开发,依赖js引擎。此外由于一般跟os是不同语言的实现,所以存在跨语言调用实现。为了屏蔽不同OS和不同JS的差异,RN引入了JSI实现跨语言的统一封装。具体如下:
- eval等功能,由于安全原因被限制,实际当中RN应用要使用怎么办
- harmony的ts版本固定的,当前4.2.4, 由于ts迭代较快,开发者想使用最新ts版本该怎么办。
待补
待补
jsc,Hermes都是开源代码,三方可以直接定制后并集成到产品中。而arkj虽然是开源的,但是不能产品化定制,而且h头文件不能使用。并且三方IDE依赖的调试能力,arkjs没有对外提供。
待补
待决策:JS引擎方案选择,理论上应该两种方案都支持。推荐方案1
方案1: Hamony上porting RN的Hermes/JSC引擎
方案2: 使用ArkJS引擎
RN框架团队(待补)
SR需求 | 领域 | 工作量 |
---|---|---|
方案1:porting Hermes/JSC | RN | 6人月 |
方案2:NAPI适配JSI,以支持Harmony平台能力 | RN | 10人月 |
harmony团队 |
只列出通用部分,其他对方舟的依赖具体见各个模块,涉及调试(基于ark机制获取堆信息,断点调试,work上支持js 动态import js文件,热加载等),worker能力增强,taskpool等
SR需求 | 领域 | 工作量 |
---|---|---|
方案2:ts版本可配置 | 方舟 | |
方案2:头文件暴露 | 方舟 |
包括工具链,预览调试器等,具体参照官网介绍:
主要涉及以下几个方面:
由于上述能力都是强耦合系统的,所以都涉及harmony化的相关工作。
需要在harmony平台支持 Expo Go、CLI、debug tool等涉及bundle,框架重启,远程加载、debug配置,应用安装,堆栈调试,ui inspect等各种开发和调试的工作。
具体的说,以Expo Go为例,具体涉及yarn或npm等相关的命令
其中执行以下命令后,会启动metro server,完成bundle,框架重启,远程加载、debug配置,应用安装等的功能
yarn start
以及四方的调试工具,react-native-debugger
总体上,建议这部分工作整体上follow RN现有设计和功能即可。
以工具链为例,RN工具链,metro本身既要编译react native应用和框架前端相关的代码,也要通过调用各种os的编译工具链完成对rn后端代码的编译。编译后的产物,以文件读取,或http加载,内存加载等各种方式动态加载到不同os平台的js环境当中。
待决策:推荐使用方案一:
说明:尽管metro和hvigor都是基于npm/Yarn实现的,理论上是可以融合在一起的(基于higor底座实现),但是由于rn是基于flowjs实现的,而且本身依赖巨量的三分npm/Yarn库,将metro融合到hvigor中,对hvigor打包工具(基于rollup)的能力要求极高,而且不方便react工具链的维护。所以采用异步加载 + 全局挂载的方式,本质上把
方案一:follow现有方案 优点:方便rn对工具链的维护 缺点:依赖动态化加载能力
方案二: 约点:混合编程和同步编译,不依赖动态加载能力 缺点:对rn现有架构改变大,需要将metro能力融合到hvigor中,对hvigor能力要求高,工作量大
SR描述 | 领域 | 工作量 |
---|---|---|
支持各种yarn等工具链的相关命令 | RN | 5 |
在vsc开发调试鸿蒙应用需要的插件 | RN | 10 |
支持metro调试,涉及对rn框架、应用打包并应用动态加载,abc热机制 | RN | 10 |
支持各种debug tool,预览,inspect,断点调试等功能,涉及websoket以及rn js框架修改 | RN | 10 |
支持各种trace,profile,hmr,ram bundle和内敛引用优化能力 | RN | 8 |
方案2:将metro的各种工具融合到hvigor中,支持folwjs和ets/TS混合编程 | RN | 10 |
SR描述 | 领域 | 工作量 |
---|---|---|
使用方舟js引擎:基于ark机制获取堆信息 | 方舟 | |
使用方舟js引擎:work上支持js 动态import js文件 | 方舟 | |
支持获取ui组件信息,以支持inspect(需要demo验证,可能不需要) | ArkUI | |
工具链编译配置能力开放, 具体为能被三方触发调用, | IDE | |
方案2(待定):识别将metro的各种工具融合到hvigor中,hvigor的能力缺乏 | IDE |
RN框架针对不同的平台有不同的实现,并具体通过暴露接口 + 二进制交付的方式给系统应用集成。
具体参照官网介绍:
其中关键的几点如下(以android 为例):
dependencies {
...
implementation "com.facebook.react:react-native:+" // From node_modules
}
allprojects {
repositories {
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
...
}
...
}
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SoLoader.init(this, false);
mReactRootView = new ReactRootView(this);
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
// 有一些第三方可能不能自动链接,对于这些包我们可以用下面的方式手动添加进来:
// packages.add(new MyReactNativePackage());
// 同时需要手动把他们添加到`settings.gradle`和 `app/build.gradle`配置文件中。
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的MyReactNativeApp 必须对应"index.js"中的
// "AppRegistry.registerComponent()"的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
其中,harmony内部为遵循OH目录结构的目录。
类比其他系统,增强相应的目录,或相应的文件,或编译运行开关,具体见如下ReactHarmony部分。
还有其他的要求,如RN jar的权限声明,基于node_module的发布等等,具体见官网介绍
待决策:整体上follow RN现有设计和功能 har包支持worker har支持权限配置 har支持页面配置 支持工程外路径的module加载
SR描述 | 领域 | 工作量 |
---|---|---|
提供reactRoot组件用于显示rn应用,对接事件等 | RN框架团队 | 2人月 |
提供react instance api完成各种参数配置,支持多实例 | RN框架团队 | 2人月 |
对接harmony的用户框架的各种生命周期 | RN框架团队 | 2人月 |
整体以二进制har包交付,并发布到npm市场 | RN框架团队 | 2人月 |
SR描述 | 领域 | 工作量 |
---|---|---|
har包支持worker | IDE | W |
har支持权限配置和权限合并 | 包管理 | 3.2:23/330交付 |
支持工程外路径的module加载 | IDE |
其中实例管理,显示窗口, 上下文环境比较简单,这里主要介绍线程模型/任务调度,其他后续章节单独介绍
官网介绍:Threading Model
此外,由于涉及多线程并发的问题,故RN使用了以下能力完成各种并发操作
ava.util.concurrent
背景:harmony不允许三方使用worker,而是推荐基于taskpool实现。但是,使用taskpool的话,本质上没有follow rn的设计思路。即使不考虑taskpool是否能做到线程模型的能力,也存在不被rn社区接收的可能性。
待决策:
- 方案1: 使用worker实现,允许三方启动worker,调用方无感知。 优点:follow rn现有的设计思路 缺点:harmony的work能力缺失严重,开发便捷性差,性能欠缺。
- 方案2: 使用task pool实现 优点:无感知线程能力 缺点:对rn现有架构改动比较大,能力gap大,短期无法验证功能gap
方案1:基于worker实现
方案说明
本质上是基于worker实现类似线程的能力。但harmony现在的worker机制存在限制,具体如下:
只能在父子worker之间通信数据
数据共享效率低
任务调度能力较低
同步机制: 待原型验证 由于worker本质上是一种actor模型,通过深度copy序列化避免了数据竞态操作,故理论上么有对锁的依赖,但不排除对同步信号的依赖
方案详细说明
由于只能在父子父子worker之间通信数据,故所有的通信都要绕道ui线程进行通信,再由于worker之间数据共享效率低,势必影响线程使用的性能。同时导致线程通信管理的复杂性提升。
private final class CreateViewOperation extends ViewOperation {
private final ThemedReactContext mThemedContext;
private final String mClassName;
private final @Nullable ReactStylesDiffMap mInitialProps;
public CreateViewOperation(
ThemedReactContext themedContext,
int tag,
String className,
@Nullable ReactStylesDiffMap initialProps) {
super(tag);
mThemedContext = themedContext;
mClassName = className;
mInitialProps = initialProps;
Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
}
@Override
public void execute() {
Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
mNativeViewHierarchyManager.createView(mThemedContext, mTag, mClassName, mInitialProps);
}
}
worker的各种限制,本质可以理解为宏任务,微任务无法在worker之间传递,无法实现类似其他os,如android的runnable/looper,或者ios的任务调度能力。理论上,如果taskpool支持跨线程的高效任务调度,也可以实现rn的线程模型的功能和性能要求。
使用方舟的TaskPool机制实现。和方案1的本质区别在于,开发者不感知thread,不需要维护对应的生命周期 , 而是由taskpool统一管理。理论上,统一管理可以降低系统的资源消耗,提高系统的整体性能。
一个典型示例为下:
import taskpll from "@ohos.taskpool"
@concurrent
function someLogic(para: Para) {
//some prcess to para
//.....
return para;
}
async function controller() {
let para = {message: "creatInstance", value: "Text"}
await taskpool.execute(someLogic, para)
}
controller().then(result => {
//some prcess to para
})
或者使用Task
,增加以下优先级或者生命周期控制的能力等,本质上区别不大。
目前方舟的规划里,someLogic只能是顶层函数或类的static函数。但上述worker的问题,依然存在。同时没有task的结构化并发机制来处理各种异常,保留了actor的特征,及序列化来避免竞态,但存在深度copy的性能问题。
taskpool
l机制完善后增加。实现上述方案,需要实现如下SR
方案1
SR描述 | 领域 | 工作量 |
---|---|---|
线程和消息通信管理 | RN框架团队 | 2人月 |
ui线程管理和通信 | RN框架团队 | 2人月 |
js线程管理和通信 | RN框架团队 | 2人月 |
native模块线程管理和通信 | RN框架团队 | 2人月 |
其他线程管理和通信 | RN框架团队 | 2人月 |
SR描述 | 领域 | 工作量 |
---|---|---|
支持类似web的shared worker机制,实现任意worker之间可以之间通信 | 方舟 | |
work通信支持高效数据传递,规避序列化带来的各种损耗,随之而来带来同步竞态问题 | 方舟 | |
work通信支持任务或者函数通信,从而解耦发送接收双方的功能,提高编程效率 | 方舟 | |
宏任务,微任务可以在worker之间传递 | 方舟 |
方案2(待taskpool能力实现后增加说明)
RN框架团队(待补)
harmony(待补)
RN本质上只是一个JS开发框架,不具备完整的系统能力,换句话说,其对应的能力都要依赖OS原生能力的实现。OS的能力比较多,为了提高管理效率:降低启动时间、内存消耗,同时方便代码工程管理,RN提供了模块化机制来管理RN的各种功能。模块化管理包括运行时和代码工程编译时的功能解耦。具体通过对不同的功能实现在开发态和运行态的高内聚,低耦合,从而实现框架在运行时根据应用需要,按需加载必要功能模块,从而极大降低启动时间和运行时内存。同时为了避免在应用使用时加载对应模块所需要的时间,在懒加载的同时,提供了预加载配置能力,方便对重要和常用的功能进行预加载。此外,模块本身具备cache能力,即只需要加载一次既可,后续不需要重复加载。
需要强调的是,rn的模块化不是文件级的模块,而是一个功能场景的模块,如ui模块,js框架模块等等。ui模块内部等各个组件也是模块化的,即只有使用text才会创建对应的text实例。
同时,实践中,有些功能,RN可能没有封装,故其提供了自定义模块化功能,用于开发者按需实现所需的功能并以模块化的方式加载管理,同时可以发布到NPM市场,方便其他开发者使用。
官网:NativeModule 官网:模块npm发布
用一般的工厂模式通过new 构造实例来实现懒加载,存在各种问题。如样板代码多,单元测试麻烦,实例生命周期管理复杂,没有cache复用机制等等。通过依赖注入的方式实现可以有效避免上述问题。具体参照:为什么需要依赖注入, 这里引用几句关键的话如下:
为了解决以上问题,依赖注入模式应运而生了,它用容器替代了工厂,我们不要再新建工厂类,工程师只需要处理依赖关系即可,对象的创建Constructor、对象之间的装配dependency和对象的销毁destroy都交由容器来处理,所以依赖注入又被称为控制反转。 控制反转或者依赖注入其实遵循了以下两个法则:
好莱坞法则:不要打电话给我们,我们会打给你 “我们”就可以理解为容器,打电话的时机交由容器来控制。 迪米特法则(最少知识原则):不要和陌生人说话 对象应该依赖接口,而不是具体实现,对其它对象要尽可能少的了解。
RN for Android具体通过遵循标准 JSR-330的以下依赖注入库实现,相关描述见javax.inject
介绍, 和接口说明
javax.inject.Provider
最终实现在需要时, 需要通过反射构造出模块的实例。而通过反射能力有着需要样板重复代码,通过元编程能力实现依赖配置注入器从而提高开发效率,如下库。
java.lang.annotation
@Model
public class Index {
@Inject
Provider<Boundary> boundary;
public String getMessage() {
return boundary.get().message();
}
}
@Stateless
public class Boundary {
public String message() {
return "Good morning";
}
}
此外,react native提供了ReactPackage
等相关接口,为了支持应用开发者扩展自定义的native module。这里涉及各种java的元编程能力,具体如下
@Retention(RUNTIME)
public @interface ReactMethod {
boolean isBlockingSynchronousMethod() default false;
}
React Native把 moulde归为四类, MainPackage
, CorePackage
, DebugPackge
,自定义ReactPackage
其中,MainPackage代码如下:本质上就是预先创建了一个Map,维护了各种模块的holer:Provider,并在应用需要时动态创建对应的module实例
@ReactModuleList(
nativeModules = {
AccessibilityInfoModule.class,
AppearanceModule.class,
AppStateModule.class,
BlobModule.class,
FileReaderModule.class,
AsyncStorageModule.class,
ClipboardModule.class,
DialogModule.class,
FrescoModule.class,
I18nManagerModule.class,
ImageLoaderModule.class,
ImageStoreManager.class,
IntentModule.class,
NativeAnimatedModule.class,
NetworkingModule.class,
PermissionsModule.class,
ShareModule.class,
SoundManagerModule.class,
StatusBarModule.class,
ToastModule.class,
VibrationModule.class,
WebSocketModule.class,
})
public class MainReactPackage extends TurboReactPackage {
private MainPackageConfig mConfig;
public MainReactPackage() {}
/** Create a new package with configuration */
public MainReactPackage(MainPackageConfig config) {
mConfig = config;
}
@Override
public @Nullable NativeModule getModule(String name, ReactApplicationContext context) {
switch (name) {
case AccessibilityInfoModule.NAME:
return new AccessibilityInfoModule(context);
case AppearanceModule.NAME:
return new AppearanceModule(context);
case AppStateModule.NAME:
return new AppStateModule(context);
.....
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
try {
Class<?> reactModuleInfoProviderClass =
Class.forName("com.facebook.react.shell.MainReactPackage$$ReactModuleInfoProvider");
return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance();
} catch (ClassNotFoundException e) {
// In OSS case, the annotation processor does not run. We fall back on creating this byhand
Class<? extends NativeModule>[] moduleList =
new Class[] {
AccessibilityInfoModule.class,
AppearanceModule.class,
AppStateModule.class,
BlobModule.class,
FileReaderModule.class,
AsyncStorageModule.class,
ClipboardModule.class,
DialogModule.class,
FrescoModule.class,
I18nManagerModule.class,
ImageLoaderModule.class,
ImageStoreManager.class,
IntentModule.class,
NativeAnimatedModule.class,
NetworkingModule.class,
PermissionsModule.class,
ShareModule.class,
StatusBarModule.class,
SoundManagerModule.class,
ToastModule.class,
VibrationModule.class,
WebSocketModule.class
};
final Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
for (Class<? extends NativeModule> moduleClass : moduleList) {
ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
reactModuleInfoMap.put(
reactModule.name(),
new ReactModuleInfo(
reactModule.name(),
moduleClass.getName(),
reactModule.canOverrideExistingModule(),
reactModule.needsEagerInit(),
reactModule.hasConstants(),
reactModule.isCxxModule(),
TurboModule.class.isAssignableFrom(moduleClass)));
}
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
return reactModuleInfoMap;
}
};
} catch (InstantiationException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for CoreModulesPackage$$ReactModuleInfoProvider", e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for CoreModulesPackage$$ReactModuleInfoProvider", e);
}
}
}
一个典型的NativeUI ManagerModule如下
@ReactModule(name = UIManagerModule.NAME)
public class UIManagerModule extends ReactContextBaseJavaModule
implements OnBatchCompleteListener, LifecycleEventListener, UIManager {
public static final String TAG = UIManagerModule.class.getSimpleName();
// ......
public static final String NAME = "UIManager";
// ......
@Override
public void onHostResume() {
mUIImplementation.onHostResume();
}
// ......
@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
if (DEBUG) {
String message =
"(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props;
FLog.d(ReactConstants.TAG, message);
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
}
mUIImplementation.createView(tag, className, rootViewTag, props);
}
@ReactMethod
public void updateView(final int tag, final String className, final ReadableMap props) {
if (DEBUG) {
String message =
"(UIManager.updateView) tag: " + tag + ", class: " + className + ", props: " + props;
FLog.d(ReactConstants.TAG, message);
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
}
mUIImplementation.updateView(tag, className, props);
}
// ......
}
参照
本质上,bundle less方案或者说harmony的import 模块化方案就是用来实现按需加载的,同时由于rn涉及到了es module和common js 模块,故二者都需要要支持。但由于,harmony的模块化只是文件级的模块,故为了满足功能场景的模块,如ui模块,js框架模块等等,rn for harmony的实现,需要各个模块相互解耦,否则即使harmony原生支持文件级模块解耦,也无法实现rn懒加载相应的能力。
这里可以参考angular等业界著名js框架的实现,具体通过以下三方JS库实现在harmony上的模块化方案。具体通过typedi
实现类似javax.inject.Provider
的功能,通过reflect-metadata
实现类似java.lang.annotation
,其他的元编程能力,使用ts-node的compiler api,这个和arkui的@Prop
一样的原理。
typedi
reflect-metadata
ts-node
目前看主要是RN团队的工作,harmony理论上没有额外需求(需要进一步原型确认) 这里不包括上述提到的三方库的适配实现。
SR需求 | 领域 | 工作量 |
---|---|---|
基于依赖注入和装饰器元编程能力实现模块化基座和管理 | RN团队 | 2人月 |
支持关键模块的可配置的预加载 | RN团队 | 2人月 |
模块化支持cache机制 | RN团队 | 2人月 |
功能代码的工程级别解耦 | RN团队 | 2人月 |
自定义模块化功能(如@ReactMethod,@ReactProp等,模块注册能力如ReactPackage等) | RN团队 | 2人月 |
支持模块按需发布到npm市场功能 | RN团队 | 2人月 |
打通和react native前端框架的对接 | RN团队 | 2人月 |
SR需求 | 领域 | 工作量 |
---|---|---|
元编程能力暴露(使用方舟的前提下) | 方舟编译器 |
涉及基础部分,如异步布局,ui解析,ui自定义实现(涉及绘制,布局,功能增加等). 相关介绍如下:
以及事件,手势,动画等等,相关介绍如下:
对RN应用的任意一个UI控件/组件,RN框架最终都会创建一个原生的UI控件/组件,具体如下表格(来自RN官网)。
但是由于RN的行为和OS组件的行为(如布局能力,组件属性)存在一定的gap:可能是有原生没有这个能力(如felx布局),也可能是由于原生的行为和rn存在差异(如事件冒泡)等。故RN(新架构有个实验特效:rn canvas提供类似flutter彻底的自渲染能力)只是没有图形的能力,但UI框架的能力基本都具备,具体如:UI事件管理(包括自定义事件dispatch等),hittest和冒泡,基于yoga的异步布局模型,甚至某些组件(如Image)还包括draw能力等等。具体实现如下图,即在js的组件和原生组件之间,rn提供了一个叫shadowNode的功能,本质上类似arkui的render组件,用于完成前述述gap能力。具体通过
进一步,如前所述,rn本身么有图形能力,故还需要依赖os的原生ui能力。这里包括,几个关键点:
这里主要以Image,Text,Scroll三种UI组件为例介绍RN对系统原生UI的依赖。
以Image为例,以下几个回调是Harmony么有或者存在差异的, RN Image官网 OH Gitlee
onLoad onError onLoadEnd onLoadStart onPartialLoad onProgress progressiveRenderingEnabled
此外,上述各个过程都可以配置额外的place holder,或者在加载个某个过程做特殊的处理,如高斯模糊,如下 react通过android的自定义ui能力 + fresco实现上述场景,继承关系具体如下:
本质上理解为ReactImageVIew增加或覆盖ImagView的通用能力,并最终用于ui显示和事件交互等。
事件交互涉及的几个关键代码如下
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
@ReactProp(name = "shouldNotifyLoadEvents")
public void setLoadHandlersRegistered(ReactImageView view, boolean shouldNotifyLoadEvents) {
view.setShouldNotifyLoadEvents(shouldNotifyLoadEvents);
}
}
public class ReactImageView extends GenericDraweeView {
public void setShouldNotifyLoadEvents(boolean shouldNotify) {
// Skip update if shouldNotify is already in sync with the download listener
if (shouldNotify == (mDownloadListener != null)) {
return;
}
if (!shouldNotify) {
mDownloadListener = null;
} else {
final EventDispatcher mEventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) getContext(), getId());
mDownloadListener =
new ReactImageDownloadListener<ImageInfo>() {
@Override
public void onProgressChange(int loaded, int total) {
// TODO: Somehow get image size and convert `loaded` and `total` to image bytes.
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createProgressEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this),
getId(),
mImageSource.getSource(),
loaded,
total));
}
@Override
public void onSubmit(String id, Object callerContext) {
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createLoadStartEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this), getId()));
}
@Override
public void onFinalImageSet(
String id, @Nullable final ImageInfo imageInfo, @Nullable Animatable animatable) {
if (imageInfo != null) {
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createLoadEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this),
getId(),
mImageSource.getSource(),
imageInfo.getWidth(),
imageInfo.getHeight()));
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createLoadEndEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this), getId()));
}
}
@Override
public void onFailure(String id, Throwable throwable) {
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createErrorEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this), getId(), throwable));
}
};
}
mIsDirty = true;
}
}
ui显示涉及的关键的几个代码如下
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
@ReactProp(name = "loadingIndicatorSrc")
public void setLoadingIndicatorSource(ReactImageView view, @Nullable String source) {
view.setLoadingIndicatorSource(source);
}
}
public class ReactImageView extends GenericDraweeView {
public void setLoadingIndicatorSource(@Nullable String name) {
Drawable drawable =
ResourceDrawableIdHelper.getInstance().getResourceDrawable(getContext(), name);
Drawable newLoadingIndicatorSource =
drawable != null ? (Drawable) new AutoRotateDrawable(drawable, 1000) : null;
if (!Objects.equal(mLoadingImageDrawable, newLoadingIndicatorSource)) {
mLoadingImageDrawable = newLoadingIndicatorSource;
mIsDirty = true;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0) {
mIsDirty = mIsDirty || hasMultipleSources() || isTiled();
maybeUpdateView();
}
}
public void maybeUpdateView() { //
GenericDraweeHierarchy hierarchy = getHierarchy();
if (mDefaultImageDrawable != null) {
hierarchy.setPlaceholderImage(mDefaultImageDrawable, mScaleType);
}
if (mLoadingImageDrawable != null) {
hierarchy.setPlaceholderImage(mLoadingImageDrawable, ScalingUtils.ScaleType.CENTER);
}
}
}
以上主要涉及绘制和事件,自定义方法;除此之外,还有布局相关的实现,以scollView为例。 RN scroll的详细接口说明见如下:
官网:ScrollView 三方:React-Native 之 ScrollView使用
这里依赖android提供的自定义布局能力,即measure
设置大小和layout
设置位置,以及通过requestLayout
接口通知android触发ui渲染流程。 具体代码如下,NativeViewHierarchyManager 是由shadownode通过yoga在native module线程进行完异步布局后通过线程通信能力最终交到ui线程触发的
@NotThreadSafe
public class NativeViewHierarchyManager {
public synchronized void updateLayout(
int parentTag, int tag, int x, int y, int width, int height) {
try {
View viewToUpdate = resolveView(tag);
viewToUpdate.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
ViewParent parent = viewToUpdate.getParent();
if (parent instanceof RootView) {
parent.requestLayout();
}
// Check if the parent of the view has to layout the view, or the child has to lay itself out.
if (!mRootTags.get(parentTag)) {
ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
IViewManagerWithChildren parentViewManagerWithChildren;
if (parentViewManager instanceof IViewManagerWithChildren) {
parentViewManagerWithChildren = (IViewManagerWithChildren) parentViewManager;
} else {
throw new IllegalViewOperationException(
"Trying to use view with tag "
+ parentTag
+ " as a parent, but its Manager doesn't implement IViewManagerWithChildren");
}
if (parentViewManagerWithChildren != null
&& !parentViewManagerWithChildren.needsCustomLayoutForChildren()) {
updateLayout(viewToUpdate, x, y, width, height);
}
} else {
updateLayout(viewToUpdate, x, y, width, height);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
private void updateLayout(View viewToUpdate, int x, int y, int width, int height) {
if (mLayoutAnimationEnabled && mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) {
mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height);
} else {
viewToUpdate.layout(x, y, x + width, y + height);
}
}
}
此外,为了避免使用yoga的flex布局算法,而不是scollview本身的能力,需要scollview的onlayout能力disable调
public class ReactScrollView extends ScrollView
implements ReactClippingViewGroup,
ViewGroup.OnHierarchyChangeListener,
View.OnLayoutChangeListener,
FabricViewStateManager.HasFabricViewStateManager,
ReactOverflowViewWithInset,
HasScrollState,
HasFlingAnimator,
HasScrollEventThrottle {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Call with the present values in order to re-layout if necessary
// If a "pending" content offset value has been set, we restore that value.
// Upon call to scrollTo, the "pending" values will be re-set.
int scrollToX =
pendingContentOffsetX != UNSET_CONTENT_OFFSET ? pendingContentOffsetX : getScrollX();
int scrollToY =
pendingContentOffsetY != UNSET_CONTENT_OFFSET ? pendingContentOffsetY : getScrollY();
scrollTo(scrollToX, scrollToY);
ReactScrollViewHelper.emitLayoutEvent(this);
}
}
后续分析(待补)
总体follow android或者ios的设计思路即可,只不过由于harmony UI是声明式,故重点需要做一些声明式的转换即可。其中,基于状态管理实现rn组件和harmony原生组件的映射,直接使用百度鸿蒙化的思路即可,这里不赘述。
SR需求描述 | 领域 | 工作量 |
---|---|---|
ui线程管理和消息通信管理, | RN | 2人月 |
基于上述机制实现ui创建,属性更新,ui结构管理 | RN | 2人月 |
ui 控件级按需加载机制Viewmanager | RN | 2人月 |
基于状态管理实现rn组件和harmony原生组件的映射 | RN | 2人月 |
各种控件的实现,包括图片,文字,scoll等rn涉及的所有ui能力 | RN | 15人月 |
ui异步布局机制,包括shadownode,以及跨语言调用到yoga相关能力,以及跨线程通信 | RN | 2人月 |
yoga迁移 | RN | 2人月 |
yoga napi扩展 | RN | 3人月 |
ui事件机制,包括事件管理,冒泡机制,hittes,派发触发事件监听器等 | RN | 2人月 |
ui 层级结构优化 | RN | 2人月 |
rootview实现,包括事件派发等等 | RN | 2人月 |
rn自定义组件支持 | RN | 3人月 |
理论上,所有的特性参数shadownode都有,故不存在子组件影响父组件布局的问题
SR描述 | android 实现方式 | harmony实现方式(spec待定) | 工作量 |
---|---|---|---|
复用组件基础能力+ 部分能力覆盖 | 通过继承view + override方法实现 | @Extend @Override + $.属性方法 | 3人月 |
复用组件基础能力,功能增加 | 通过继承view + 增加方法实现 | @Prop(true) | 3人月 |
覆盖内置容器组件的布局算法 | view.onLayout + override方法实现 | @Extend @Override + onLayout | 2人月 |
覆盖内置容器组件的绘制算法 | view.onDraw + override方法实现 | @Extend @Override + onDraw | 2人月 |
实现自定义容器组件的布局算法 | view.onLayout | @Component(true) onLayout | 2人月 |
配置ui组件(包括原生和自定义)的layout相关属性(位置,大小等) | view.layout | 通过通用方法size/postion配置(待验证) | 3人月 |
dirty组件,触发layout | view.requestLayout | 修改状态变量(待验证) | 1人月 |
dirty组件,触发draw | view.invalid | 修改状态变量(待验证) | 1人月 |
自定义事件 | view.dispatchEvent | 支持自定义派发arkui事件给rn框架,其他事件管理由rn内部实现 | 3人月 |
vSync信号回调注册 | Choreographer.FrameCallback | 图形增加类似机制 | 1人月 |
自定义动效机制实现 | ?人月 |
官网介绍:JavaScriptRuntime
RN本质上只是一个JS开发框架,不具备完整的系统能力,换句话说,其对应的能力都要依赖OS原生能力的实现。react原生依赖浏览器能力,即通过dom api和 w3c web api实现对浏览器能力的对接。而RN要依赖OS原生能力的实现,RN的JS框架主要包括2部分:即React 和react Native部分,两者都是基于flow.JS实现,其中react Native JS就是对平台能力的封装实现。
这里主要完成对os平台的支持,相关工作量主要包在工具链,模块化、api、UI能力中,这里不单独赘述。
官网介绍,如: API 非标准es API,如 Timer Fetch
react API分为两部分,原生支持的API (~30
个大类,~200
个细类API)和四方基于扩展机制通过实现API
实现这里依赖JS引擎模块和工具链模块的决策。
js引擎 \ 工具链 | 现有rn方案 | 融合方案 |
---|---|---|
Hermes | 方案1. Hermes在global挂载对应能力 + JSI/NAPI | 方案2. import + JSI/NAPI |
ArkJS引擎 | 方案3. ts在方舟global挂载对应能力 | 方案4. import |
原生支持部分
方案 | 领域 | 工作量 |
---|---|---|
方案1 + 扩展机制 | RN | 30人月 |
方案2 + 扩展机制 | RN | 30人月 |
方案3+ 扩展机制 | RN | 20人月 |
方案4+ 扩展机制 | RN | 20人月 |
官网介绍:Debug 官网介绍:Trace 官网介绍:Profile 官网介绍:性能 三方调试:flipper 待补
D:\development\sourcecode\reactnative\master\react-native\index.js
{
"entryFile": "D:\\\\development\\\\sourcecode\\\\reactnative\\\\master\\\\react-native\\\\index.js",
"options": {
"customResolverOptions": {},
"customTransformOptions": {},
"dev": true,
"experimentalImportBundleSupport": false,
"experimentalImportSupport": false,
"hot": true,
"minify": false,
"platform": "android",
"runtimeBytecodeVersion": null,
"shallow": false,
"type": "module",
"unstable_allowRequireContext": false,
"unstable_transformProfile": "default"
}
}
在react natve的VSC工程根目录,建立.vscode
目录,并在其中建立lauch.json
文件, 以调试yarn android
命令为例其中内容如下。如提示此系统上禁止运行脚本
,请将vscode以管理员方式运行,并在terminal下执行命令:set-ExecutionPolicy RemoteSigned
{
"configurations": [
{
"command": "yarn android",
"name": "Run yarn android",
"request": "launch",
"type": "node-terminal"
}
]
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。