当前位置: 首页 > news >正文

【HarmonyOS】HMRouter关键原理-动态import

动态加载(动态import)

使用场景

如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入

  • 当静态导入的模块明显降低了代码的加载速度而且被使用的可能性较低
  • 当静态导入的模块明显占用的大量内存资源且被使用的可能很低
  • 当被导入的模块在加载时并不存在,需要异步获取
  • 当被导入的模块说明符,需要动态构建时(静态导入只能使用静态说明符)
  • 当被导入的模块有副作用,如模块中会直接运行的代码时

动态import介绍

  • 当前所有import中使用的模块名是依赖方oh-package.json5的dependencies中的别名
  • import一个模块名,实际上机会import该模块的入口文件,一般为Index.ets

常量动态import

//HAR模块的Index.ets
export {add,swap} from './src/main/ets/pages/page2'
//HAP模块
import('testhar/Index').then((ns: ESObject) => {console.log('[result:]' + ns.add(1,2))
})

变量动态import

概述

  • 在方舟编译器中,模块间的依赖关系通过oh-package.json5中的dependencies字段来配置,这字段中的所有模块都会进行安装,但是默认不参与编译
  • HAP编译时会以入口文件开始搜索依赖关系,搜索的到的模块会加入编译,import顺序为后序遍历(不同的模块之间的联系抽象为树结构)
  • 在编译时,静态import和常量动态import可以被打包工具识别,并被加入依赖树种,参与编译,最终生成方舟字节码。
  • 但是如果是变量动态import,在编译时无法确定变量的值,也就无法加入编译,而要加入编译,就要额外进行一些配置,用于指定动态import的变量实际的值

配置runtimeOnly

//在在HAP/HSP/HAR的build-profile.json5中
"buildOption": {"arkOptions": {"runtimeOnly": {"packages": [ "myhar" ],  // 配置本模块变量动态import其他模块名,要求与dependencies中配置的名字一致。"sources": [ "./src/main/ets/utils/Calc.ets" ]  // 配置本模块变量动态import自己的文件路径,路径相对于当前build-profile.json5文件。}}
}

使用实例

// 变量动态import其他模块myhar
let harName = 'myhar';
import(harName).then((obj: ESObject) => {console.info('DynamicImport I am a har');
})// 变量动态import本模块自己的单文件src/main/ets/index.ets
let filePath = './utils/Calc';
import(filePath).then((obj: ESObject) => {console.info('DynamicImport I am a file');
})

为什么要使用动态import

HAR包之间依赖解耦

在这里插入图片描述

  • 如果使用静态import或者常量动态import,当应用中含有多个HAR包时,在配置HAR之间的依赖关系时,可能会形成HAR之间循环依赖。
    在这里插入图片描述
  • 而如果使用变量动态import,就可以在将HAR包之间的依赖转移到HAP中去配置,HAR包之间无需配置依赖关系,从而达到HAR包之间的依赖解耦

使用实例

在这里插入图片描述

首先构筑一个存在循环依赖的项目,其中每个Har包之间的依赖关系都在各自的oh-package.json5和build-profile.json5中单独配置

在该项目工程中,Har2和Har1出现了循环依赖,导致ohmp无法安装Har2包

解决问题就需要将HAR之间的dependencies和runtimeOnly字段的相关配置转移到HAP中,实现HAR包之间的依赖解耦

// HAP's oh-package.json5
"dependencies": {"har1": "file:../har1","har2": "file:../har2","har3": "file:../har3"
}
// HAP's build-profiles.json5
"buildOption": {"arkOptions": {"runtimeOnly": {"packages" : [ // 仅用于变量动态加载的场景,静态加载或常量动态加载无需配置。"har1","har2","har3"]}}
}

基于Node-API加载模块

Node-API支持开发者在C++侧加载工程内的模块及文件

napi_load_module_with_info

该接口可以在主线程或者子线程中进行模块的加载,模块加载成功之后可以使用napi_get_property获取模块导出的变量,使用napi_get_named_property获取导出的函数

函数说明

napi_status napi_load_module_with_info(napi_env env, const char* path, const char* module_info, napi_value* result);
  • env:当前的虚拟机环境
  • path:加载的文件路径或者模块名
  • module_info:bundleName/moudleName的路径拼接
  • result:加载的模块

由于该接口使用范围更广,不仅可以在主线程也可以在子线程中使用,HMRouter中使用的就是napi_load_module_with_info来加载模块

napi_load_module

该接口用于在主线程中进行模块的加载,加载成功之后,可以使用同样的函数获取发哦出的变量和函数

函数说明

napi_status napi_load_module(napi_env env, const char* path, napi_value* result);
  • env:当前的虚拟机环境
  • path:加载的文件

该接口虽然只能在主线程中使用,但是入参更少,使用更便捷

实践

  1. 使用Node-API 我们首先首先创建一个Native工程或者模块,在这里以创建一个新的Native工程为例,创建完成之后在main目录下,相较于一般的工程会多出一个cpp文件夹
    在这里插入图片描述
  2. 实现模块的注册以及ArkTS接口与C++接口的绑定、设置打包参数等(这些在创建好工程后,在代码中已经初始完毕)
    下面给出模块注册和接口绑定的部分代
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{napi_property_descriptor desc[] = {{ "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }};napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);return exports;
}
EXTERN_C_END
//准备模块加载的相关信息,将上述Init函数与本模块的名称记录下来
static napi_module demoModule = {.nm_version = 1,.nm_flags = 0,.nm_filename = nullptr,.nm_register_func = Init,.nm_modname = "entry",.nm_priv = ((void*)0),.reserved = { 0 },
};
//加载该模块的.so时,该函数会被自动调用,将上述demoModule模块注册到系统中
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{napi_module_register(&demoModule);
}
  1. 实现Native侧的接口,新建工程后,系统会自动生成一个名为add的接口,为了便于演示,在这里我们直接修改接口中的内容
static napi_value Add(napi_env env, napi_callback_info info)
{napi_value result;// 1. 使用napi_load_module_with_info加载Test文件中的模块napi_status status = napi_load_module_with_info(env, "entry/src/main/ets/pages/test1", "com.example.NativeTest/entry", &result);if (status != napi_ok) {return nullptr;}napi_value testFn;// 2. 使用napi_get_named_property获取test函数napi_get_named_property(env, result, "test", &testFn);// 3. 使用napi_call_function调用函数testnapi_call_function(env, result, testFn, 0, nullptr, nullptr);return result;}

注意在使用API时,若API的参数中有文件路径的选项,一般不用加上文件的后缀

  1. ArkTS端被动态import的模块
function test() {console.log("Hello HarmonyOS");
}
export {test};

这里的函数名需要与Native侧中使用napi_call_function接口中传入的函数名一致

  1. ArkTS端调用
import testNapi from 'libentry.so';
testNapi.add(0,0)

为了方便演示,我并没有修改参数列表,所以两个参数可以随便穿,实际并没有用到

  1. 因为使用的是动态import,还是需要在当前模块的build-profile.json5中进行配置runtimeOnly字段,因为这是加载模块内的文件,所以不用配置oh-package.json5
  "buildOption": {"arkOptions": {"runtimeOnly": {"sources": ['./src/main/ets/pages/test1.ets']}}}

需要注意的是若使用的是模拟器,还需要修改build-profile.json5中的abiFilters字段,该字段是表示当前鸿蒙支持的ABI编译环境,包括:

  • arm64-v8a
  • x64_64

arm64主要是当前移动设备的主流框架,x86则是主要用于电脑端的模拟器,该字段若不设置默认为arm64-v8a

HMRouter的动态加载原理

HMRouter编译期

hmrouter-plugin插件会在编译期自动解析代码中配置的装饰器,并在rawfile中生成系统路由表,方便HMRouter初始化时读取路由表
在这里插入图片描述
其中部分内容如下

{"name": "__interceptor__homeCheckName","pageSourceFile": "src/main/ets/model/HomeLoginInterceptor.ets","buildFunction": "","customData": {"name": "EditCheckInterceptor","interceptorName": "homeCheckName"},"moduleNodeDir": "D:\\Harmony\\Project\\HMjd\\feature\\home","ohmurl": "@normalized:N&&&home/src/main/ets/model/HomeLoginInterceptor&1.0.0","bundleName": "com.example.hmjd.shop","moduleName": "entry","packageName": "home"
},

这是在开发态时定义的一个路由拦截器经HMRouter插件编译后生成的路由表内容

  • ohmurl中含有该模块的文件路径
  • bundleName为App的bundleName
  • moduleName
@HMInterceptor({ interceptorName: CommonConstants.homeCheckName })
export class EditCheckInterceptor implements IHMInterceptor {
handle(info: HMInterceptorInfo): HMInterceptorAction {
if (AppStorage.get(CommonConstants.curToken) != '') {
return HMInterceptorAction.DO_NEXT
} else {
//info.context.getPromptAction().showToast({message: '请登录'});
AppStorage.setOrCreate(CommonConstants.homeBindSheetState, true)return HMInterceptorAction.DO_REJECT}}
}

HMRouter运行期

在运行期使用napi_load_module_with_info所需的参数,由编译而成的路由表JSON数据解析而成

  • path参数通过ohmurl解析而成
  • module_info由bundleName和moduleName拼接而成
    解析部分的源码
public importComponent<T>(): T | undefined {let modulePath: string | undefined = ImportUtil.getModuleInfoFromOhmUrl(this.ohmurl);if (!modulePath) {HMLogger.error('[HMRouter Store] get module path error,HMComponent data is %s', JSON.stringify(this));return undefined;}let moduleInfo = this.bundleName + HMRouterConst.MODULE_INFO_SEPARATOR + this.moduleName;return ImportUtil.nativeImport<T>(modulePath, moduleInfo, this.className);}

userNormalizedOHMUrl

一个ets文件在编译之后会成为安装包的一部分,每个ets文件对应的字节码称为一个字节码段,OHMUrl是用来定位一个字节码段的标识
HMRouter使用动态加载,可以在运行态动态加载指定的模块。使用时必须开启useNormalizedOHMUrl,若不开启,则会导致动态加载失效
useNormalizedOHMUrl的开启在工程级的build-profile.json5中设置

  • 该字段表示是否使用标准化的OHMUrl
  • 使用集成态HSP和字节码HAR需要使用标准化的OHMUrl
    • 构建HAR有debug、release、字节码三种模式,前两种主要用于调试,debug会包含部分源码,release会包含js中间码,字节码HAR包中包含的是编译后的abc字节码,当被其他模块依赖时,不需要再对依赖的HAR中的代码进行语法检查和编译,相较于前两种构建效率更高

参考

https://gitee.com/hadss/hmrouter/wikis/HMRouter%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D#1332-hmrouter%E8%BF%90%E8%A1%8C%E6%9C%9F

http://www.dtcms.com/a/395315.html

相关文章:

  • 【Python】面向对象(三)
  • 05-django项目的跨域处理
  • go语言并发
  • Qt QSS 美化完整教程文档
  • jwt与token+redis,哪种方案更好用?
  • 基于react的前端项目开发和实战总结(umi框架)
  • 【iOS】YYModel
  • 修改docker配置使其支持本机tcp连接
  • ReportFragment:Android 生命周期的桥梁与兼容性解决方案
  • 力扣Hot100--234.回文链表
  • 视觉语言大模型(VLM)的产业落地:从Qwen-VL技术解析到医疗、车险行业革新
  • 零基础新手小白快速了解掌握服务集群与自动化运维(七)Nginx模块--Nginx Web服务
  • 一个硬盘选MBR 还是GPT
  • 【含文档+PPT+源码】基于GPT+SpringBoot的个人健康管理与咨询系统设计与实现
  • 【项目实战 Day5】springboot + vue 苍穹外卖系统(Redis + 店铺经营状态模块 完结)
  • 旧衣回收小程序:非技术视角下的价值重构与发展前景
  • 使用vue-i18n实现语言切换
  • 做小程序找哪家公司,解析小程序开发定制公司哪家适合你
  • 【python】python进阶——math模块
  • NHD-6108 全自动远、近光检测仪:智能高效的汽车灯光检测方案
  • 《 Linux 点滴漫谈: 一 》开源之路:Linux 的历史、演进与未来趋势
  • C#和微软System.Speech.Synthesis库实现语音合成
  • C++概述 (一)
  • 【开题答辩全过程】以 基于springboot的高校仪器共享管理系统设计和实现为例,包含答辩的问题和答案
  • 【python】FastAPI简介
  • IDEA lombok注解无效的问题,运行时提示java: 找不到符号或者方法
  • Windows 系统部署 Kronos 金融 K 线基础模型——基于 EPGF 架构
  • 010 Rust流程控制
  • MyBatisPlus快速入门:简化CRUD操作
  • 网络编程套接字(三)---简单的TCP网络程序