本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
大家好,我是拭心。
随着鸿蒙系统的诞生,手机操作系统世界又多一位霸主,很多公司需要重新开发一个 app,其中的成本不言而喻。
三系统时代,使用什么跨平台技术提效再度成了值得思考的问题。
在不久前的鸿蒙发布会上,华为宣布鸿蒙支持 React Native。
React Native 是一种使用 React 开发、支持在多平台运行的跨平台技术,国内外有不少 Android/iOS app 使用这种技术实现,如今鸿蒙也支持 React Native,使得学习、使用这个技术的性价比更高一筹。
这篇文章我们来了解一下,在鸿蒙系统上开发 React Native(下文简称 RN) 和 Android 上有什么区别?
要将可以在 Android 设备上运行的 RN 业务运行在鸿蒙设备上,我们需要做客户端和前端两方面的工作。
客户端主要是提供 RN 容器,让 app 可以通过输入一个业务标识的方式打开一个 RN 业务;前端主要是针对鸿蒙特殊处理,比如使用鸿蒙新提供的 API。
一、客户端的不同
首先来看一下客户端的不同。
当我们通过一个业务名称打开一个 RN 页面时,要经历以下几步:
- 创建页面容器
- 创建引擎
- 注册自定义的接口
- 加载 bundle 文件
- 执行 JS 代码渲染界面
- JS 调用
AppRegistry.registerComponent
注册入口 - 客户端调用 runApplication 执行 JS 的入口函数
- 通知客户端已经开始运行
- JS 绘制布局,使用客户端注册的组件或者模块
其中,4 和 5 Android 和鸿蒙基本一样,1 2 3 步有所不同。
1.1 页面容器的不同
第一步是创建页面容器。
什么是页面容器呢?
页面容器主要做三件事:
- 提供 root View,让 JS 创建的布局可以添加到这个 View tree 上
- 分发用户操作事件,比如用户滑动界面后通知 JS
- 分发页面的生命周期,比如 app 退到后台通知 JS
在 Android 上,RN 官方提供的页面容器是 ReactActivity,在其中向 JS 分发事件和生命周期。而我们的 app 是单 Activity 架构,页面容器就是内部实现的 ReactFragment。
ReactActivity 和 ReactFragment 的核心都是 ReactRootView(RN 官方提供的 View),ReactRootView 提供了 RN 布局的 root 布局、绑定 View 到引擎的接口,并且负责将 TouchEvent 传递给 JS。
在鸿蒙上,rnoh 中提供的页面容器是 RNAbility,在其中实现 RNInstance 的创建、window 的监听和事件、生命周期分发。
由于使用 RNAbility 实现 RN 页面,会导致启动的 RN 页面和原生不在同一个任务栈,在用户体验和页面跳转实现上有问题,因此我们通过融合 Ability 的方式实现了页面容器,融合的方式简单讲就是通过非 Ability 的方式实现 RNAbility 的功能。
鸿蒙中,和 Android ReactRootView 类似的组件是 RNSurface
:
RNSurface
是一个 Component,因此它可以作为 root view,其次它也分发了事件给 JS 侧,在它的 build 方法中,使用外部提供的 ComponentFactory 创建端上提供的组件,整体功能和 ReactRootView 基本一致。
在 RNSurface 的基础上,我们实现一个 RN 页面就简单多了:
如上图所示,只需要给 RNSurface 传递 RN 业务名称、初始化参数、rnInstance 和自定义的组件构造函数,就可以加载出 RN 页面。
1.2 引擎的不同
上一节讲的 ReactActivity&ReactRootView 和 RNAbility&RNSurface 只是页面容器,RN 代码运行还需要有 JS 引擎(也叫" JS 运行环境")。
什么是 JS 引擎呢?JS 引擎负责执行 JS 代码,类似 Android 的 Runtime。
在 Android 平台上,RN 使用的 JS 引擎目前有两种:
JavaScriptCore(简称 JSC)是 RN 早期版本的默认引擎(也是 Safari 所使用的 JavaScript 引擎),它的好处是设备兼容性好,更加稳定,缺点是执行效率慢;Hermes 是专门为 RN 而优化的一个新引擎,它的好处是性能更高(打开速度比 JSC 快 30~50%),缺点是包体积略大,且设备兼容性差一些(尤其是华为部分版本!!)。
从 RN 0.70 版本开始,React Native 会默认使用 Hermes 引擎,但为了保证业务的稳定性,我们一般会在端上保存 JS Bundle 和 hbc 两种格式的文件,以实现双引擎动态切换(优先使用 Hermes,一旦发生崩溃切换到 JSC)。
与 Android 不同的是,鸿蒙只支持 Hermes,因此我们在 Android/iOS 上使用的双引擎策略,在鸿蒙上无法继续使用。
1.3 自定义接口的不同
开发鸿蒙 RN 和 Android 的第三个不同点:自定义接口的方式不同。
什么是自定义接口?RN 中前端要使用业务相关的 UI 组件或者功能接口时,需要客户端自己开发一套,这种提供给前端使用的接口就是自定义接口。
随着 RN 的不断迭代,逐渐演化出两种架构方式:新架构和老架构,不同的架构方式下,开发自定义接口的方式也不同。
新架构是指从 0.68 版本开始提供的架构,它为开发者提供了构建高性能和响应式应用的新功能。新架构的三个核心组成:
在新架构之前存在的架构就是老架构,老架构的两个核心组成:
- 旧的原生模块体系 Native Modules
- 旧的原生布局管理 ViewManager
新架构比旧架构好的地方如下图所示(reactnative.cn/docs/the-ne...):
RN 高版本仍然支持新架构,这使得 app 可以先升级版本,然后再升级架构。
目前我们的 Android 项目升级到比较新的 RN 版本(0.72),但仍旧使用的老架构,因为使用新架构需要重写之前的自定义接口,工作量较大。
鸿蒙和 Android 支持的新老架构不同,因此我们来分别看下新老架构中如何实现自定义接口。
1.3.1 老架构
老架构的功能接口自定义方式比较简单:
- Native Module: 继承
ReactContextBaseJavaModule
,使用@ReactModule
修饰 class,使用@ReactMethod
修饰提供给 JS 调用的函数,最后在getName
里返回约定的接口名称 - ViewManager:继承
SimpleViewManager
,在createViewInstance
中创建 View,使用@ReactProp
修饰属性 setter
1.3.2 新架构
新架构的一个核心特性是 JSI(JavaScript Interfaces),它的好处是 JS 可以直接调用 C++ 代码,但缺点是我们自定义接口的步骤变得繁琐了。
RN 官方提供了 Codegen(reactnative.cn/docs/the-ne...) 工具,目的是简化我们的步骤。
比如要实现一个自定义的居中 TextView Fabric Component 提供给 JS,要做这几步:
1.使用 TS 定义接口
- 文件必须以 NativeComponent 结尾 <MODULE_NAME>NativeComponent
- 文件必须导出一个 HostComponent 对象
2.package.json 中配置 codegen 信息
3.执行 codegen: ./gradlew generateCodegenArtifactsFromSchema
4.客户端实现接口
- 修改 ViewManager,实现生成的 XXXManagerInterface、创建生成的 XXXManagerDelegate
Codegen 看着很方便,但实际开发时效率反而没有那么高,因为它的开发顺序和我们的实际工作流不符合。
我们在开发 RN 项目时,一般是先由客户端定义接口,然后提供给前端,所以接口是在客户端这边定义。而 Codegen 需要先在 JS 侧定义接口,再执行 Codegen 工具生成代码,然后客户端实现。
因此我们在开发鸿蒙项目时,偏向不使用 Codegen,直接手写。
OK,以上就是客户端开发鸿蒙 RN 时和 Android 的区别。
二、前端的不同
前端开发鸿蒙 RN 业务时,和 Android 的区别主要在这几方面:
- 组件和 Module 获取方式需要修改(如果都是新架构的话就可以一样)
- 业务相关的特殊逻辑,根据 Platform.OS 进行区分
在旧架构的项目中获取 NativeModule 时,我们需要这样写:
arduino
const { Account } = NativeModules;
而在新架构中,则变成了这样:
typescript
interface AccountModuleProtocol {
getUserInfo(): Promise<Record<string, string>>;
}
interface AccountModuleSpec extends TurboModule, AccountModuleProtocol {}
const AccountModule = TurboModuleRegistry.getEnforcing<AccountModuleSpec>('Account')
TurboModuleRegistry.getEnforcing
实现如下所示:
TurboModuleRegistry.getEnforcing
封装了旧架构的实现方式,因此前端可以直接使用这种方式,即使运行在旧架构的 app 上也不会有问题。
此外新架构中获取原生提供的 View 组件也有所不同:
less
const GestureHandlerRootViewNativeComponent = registerViewConfig('RNGestureHandlerRootView', () => ({
uiViewClassName: 'RNGestureHandlerRootView',
bubblingEventTypes: {},
directEventTypes: {},
validAttributes: {
...ReactNativeViewAttributes.UIView
}
}));
如上所示,需要通过 registerViewConfig
获取原生组件,并且在其中指定名称和参数。
三、总结
本文主要介绍了开发鸿蒙 RN 项目时,与 Android RN 项目相比,客户端和前端的不同。
客户端的不同点:
- 创建页面容器
- 创建引擎
- 注册自定义的接口
前端的不同点:
- 获取原生组件和 NativeModule 的方式不同
- 针对 Platform.OS 做业务特殊处理
希望阅读完这篇文章,你可以对鸿蒙 RN 有更多的了解,欢迎留言反馈。
推荐阅读: