ArkTS下加载RNbundle,大致分为以下三个步骤
- 创建并持有RN实例对象(RNInstance)
- 创建RN三方组件上下文(RNComponentContext)
- 创建UI(RNAPP/RNSurface)
基于RN构建的app,可以分为单bundle应用和多bundle应用两类。
单bundle应用很好理解:整个应用基于一个bundle。多bundle应用有通过"A-RN跳转到B-RN"方式的实现,或是界面干脆由多个bundle共同绘制。
对于多bundle应用来说往往需要考虑bundle的预加载。如果跳转对象bundle体积较大在看到界面前会有一定时间的白屏等待(多个bundle已经事先预下载到本地)。虽然可以通过加载动画等方式来缓解,但不能满足部分对延迟较为敏感的客户。鸿蒙上对于多bundle的预加载处理较为简单,下面来看看ArkTS下是如何加载多个bundle的。
还是以index.ets为入口页面进行说明。
在aboutToAppear生命周期中进行单/多个bundle的创建。其中rnohCoreContext是RN自身的上下文,我们加载的bundle都在它下面。
index.ets
@StorageLink('RNOHCoreContext') rnohCoreContext: RNOHCoreContext | undefined = undefined
aboutToAppear() {
......
// 多bundle
if (!this.rnohCoreContext) return;
this.loadMetroBundle()
}
loadMetroBundle方法是bundle加载的实现,通过LoadManager进行了封装,其中_rnInstance始终指向当前显示的RN对象用于原生和RN的交互(项目中不存在多个bundle同时活跃的情况)。注意assetsDest的设置,在例子中两个bundle的资源文件指向了不同的路径,这里面存在一个未解决问题,下面会提到。
index.ets
loadMetroBundle() {
let XXXBundle = new BundleBean();
XXXBundle.debugMode = false;
XXXBundle.bundlePath = 'XXX/bundle.harmony.js';
XXXBundle.assetsDest = 'rawfile/XXX/assets';
XXXBundle.appName = 'XXX';
LoadManager.loadMetroBundle(XXXBundle).then((flag: boolean) => {
AppStorage.setOrCreate('isMetroAvailable', flag)
this.isMetroAvailable = flag
this.isBundleXXXReady = flag;
if(flag){
this._rnInstance = LoadManager.instanceMap.get(XXXBundle.appName);
this.currentModuleName = XXXBundle.appName;
}
})
let YYYBundle = new BundleBean();
YYYBundle.debugMode = false;
YYYBundle.bundlePath = 'YYY/bundle.harmony.js';
YYYBundle.assetsDest = 'rawfile/YYY/assets';
YYYBundle.appName = 'YYY';
LoadManager.loadMetroBundle(YYYBundle).then((flag: boolean) => {
AppStorage.setOrCreate('isMetroAvailable', flag)
this.isMetroAvailable = flag
this.isBundleYYYReady = flag;
})
}
LoadManager.ets封装的loadMetroBundle用于创建RN实例与上下文。注意enableCAPIArchitecture的设置,现在只有ture这个选项,HarmonyOS NEXT由于不再支持java,turboModule在原生端只有cpp实现。
LoadManager.ets
public static async loadMetroBundle(bundle:BundleBean): Promise<boolean> {
const rnohCoreContext: RNOHCoreContext | undefined = AppStorage.get('RNOHCoreContext')
if (LoadManager.shouldResetMetroInstance && rnohCoreContext && !LoadManager.instanceMap.has(bundle.appName)) {
let rnInstance = await rnohCoreContext.createAndRegisterRNInstance({
createRNPackages: createRNPackages,
enableNDKTextMeasuring: true,
enableBackgroundExecutor: true,
enableCAPIArchitecture: ENABLE_CAPI_ARCHITECTURE,
arkTsComponentNames: arkTsComponentNames,
assetsDest:bundle.assetsDest
});
LoadManager.ctx = new RNComponentContext(
RNOHContext.fromCoreContext(rnohCoreContext!, rnInstance),
wrapBuilder(buildCustomComponent),
wrapBuilder(buildRNComponentForTag),
new Map()
);
let provider:JSBundleProvider |null = null;
if(bundle.debugMode){
provider = new MetroJSBundleProvider();
}else {
provider = new ResourceJSBundleProvider(rnohCoreContext.uiAbilityContext.resourceManager, bundle.bundlePath);
}
await rnInstance.runJSBundle(provider)
const jsBundleExecutionStatus: string = rnInstance.getBundleExecutionStatus(provider.getURL()) as string
console.log(" getBundleExecutionStatus = ", jsBundleExecutionStatus);
// LoadManager.shouldResetMetroInstance = false
if (jsBundleExecutionStatus === "DONE") {
// 缓存bundle
LoadManager.loadedBundle.add(bundle);
// 缓存rnInstance
LoadManager.instanceMap.set(bundle.appName,rnInstance);
return true
} else {
return false
}
}
return true
}
回到index.ets。对于单bundle应用,可以选择RNApp组件作为RN加载容器。
build() {
Column() {
if (this.rnohCoreContext && this.shouldShow) {
if (this.rnohCoreContext?.isDebugModeEnabled) {
RNOHErrorDialog({ ctx: this.rnohCoreContext })
}
RNApp({
rnInstanceConfig: {
createRNPackages,
enableNDKTextMeasuring: true,
enableBackgroundExecutor: false,
enableCAPIArchitecture: true,
arkTsComponentNames: arkTsComponentNames,
},
initialProps: { "keyXXX": "valueXXX" } as Record<string, string>,
appKey: "XXX",
wrappedCustomRNComponentBuilder: wrappedCustomComponentBuilder,
onSetUp: (rnInstance) => {
this._rnInstance = rnInstance;
rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP")
},
jsBundleProvider: new TraceJSBundleProviderDecorator(
new AnyJSBundleProvider([
new MetroJSBundleProvider(),
// NOTE: to load the bundle from file, place it in
// `/data/app/el2/100/base/com.rnoh.tester/files/bundle.harmony.js`
// on your device. The path mismatch is due to app sandboxing on HarmonyOS
new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
]),
this.rnohCoreContext.logger),
})
}
}
.height('100%')
.width('100%')
}
RNApp组件只能存在一个,对于需要加载多个bundle,尤其是多bundle拼接出页面这种组件化开发场合,可以使用RNSurface作为加载容器。根据参数选择实现bundle的切换或者同时拼接UI。
build() {
Column() {
if (this.isBundleXXXReady && this.currentModuleName === "XXX") {
RNSurface({
surfaceConfig: {
appKey: "XXX",
initialProps: this.initPropsMap.get('XXX'),
},
ctx: new RNComponentContext(
RNOHContext.fromCoreContext(this.rnohCoreContext!, this._rnInstance),
wrappedCustomComponentBuilder,
wrapBuilder(buildRNComponentForTag),
new Map()
),
})
}
if(this.isBundleYYYReady && this.currentModuleName === "YYY") {
RNSurface({
surfaceConfig: {
appKey: "YYY",
initialProps: this.initPropsMap.get('YYY'),
},
ctx: new RNComponentContext(
RNOHContext.fromCoreContext(this.rnohCoreContext!, this._rnInstance),
wrappedCustomComponentBuilder,
wrapBuilder(buildRNComponentForTag),
new Map()
),
})
}
}
}
.height('100%')
.width('100%')
}
bundle间的切换通过RN发消息给原生实现。index.ets中,isBundleXXXReady,isBundleYYYReady,currentModuleName均是@state变量。
index.ets
emitter.on(ConstUtil.event_id_open_YYY, (data) => {
// 带入参数
this.initProps = {};
this.initPropsMap.delete('YYY');
if(data && data.data && data.data.param){
try {
this.initPropsMap.set('YYY',JSON.parse(data.data.param) as Record<string,object>);
} catch (error) {
console.error("Parsing error:", error);
}
}
// 切换显示YYYRN
this.currentModuleName = 'YYY';
this._rnInstance = LoadManager.instanceMap.get('YYY');
console.info('this.initProps = ' + this.initPropsMap.get('YYY'));
});
项目是基于单RN开发的,多个bundle的逻辑在单RN版本上架后开始修改,边边角角考虑尚有欠缺。最后说下assetDest的问题。目前发现设置资源文件路径后,app还是只会去读取rawfile/assets/文件下的内容,在鸿蒙论坛提问了还是没有找到原因,暂时是将两个RN的资源文件夹名称改成不一样的,让两个资源文件可以都放在rawfile/assets/下(形如rawfile/assets/src/imgXXX与rawfile/assets/src/imgYYY,这样需要批量替换下RN代码里的图片引用路径)。
鸿蒙论坛相关梯子传送门
这是大佬的回复,应该是官方马甲。
如有知道为何资源无法正常加载的同学不吝赐教:)
微信扫一扫
关注该公众号悬空八只脚