动态 import
动态 import 支持条件延迟加载,支持部分反射功能,可以提升页面的加载速度;动态 import 支持加载 HSP 模块/HAR 模块/OHPM 包/Native 库等,并且 HAR 模块间只有变量动态 import 时还可以进行模块解耦。
二、技术适用场景介绍
应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。下面是可能会需要动态导入的场景:
- 当静态导入的模块很明显的降低了代码的加载速度且被使用的可能性很低,或者并不需要马上使用它。
- 当静态导入的模块很明显的占用了大量的系统内存且被使用的可能性很低。
- 当被导入的模块,在加载时并不存在,需要异步获取。
- 当被导入的模块说明符,需要动态构建。(静态导入只能使用静态说明符)
- 当被导入的模块有副作用(这里的副作用,可以理解为模块中会直接运行的代码),这些副作用只有在触发了某些条件才被需要时。
三、业务扩展场景介绍
动态 import 在业务上除了能实现条件延迟加载,还可以实现部分反射功能。实例如下,HAP 动态 import HAR 包 harlibrary,并调用静态成员函数 staticAdd()、成员函数 instanceAdd(),以及全局方法 addHarlibrary()。
// harlibrary's src/main/ets/utils/Calc.ets
export class Calc {
public static staticAdd(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am harlibrary in staticAdd, %d + %d = %d', a, b, c);
return c;
}
public instanceAdd(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am harlibrary in instanceAdd, %d + %d = %d', a, b, c);
return c;
}
}
export function addHarlibrary(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am harlibrary in addHarlibrary, %d + %d = %d', a, b, c);
return c;
}
// harlibrary's Index.ets
export { Calc, addHarlibrary } from './src/main/ets/utils/Calc'
// HAP's oh-package.json5
"dependencies": {
"harlibrary": "file:../harlibrary"
}
// HAP's src/main/ets/pages/Index.ets
import('harlibrary').then((ns:ESObject) => {
ns.Calc.staticAdd(8, 9); // 调用静态成员函数staticAdd()
let calc:ESObject = new ns.Calc(); // 实例化类Calc
calc.instanceAdd(10, 11); // 调用成员函数instanceAdd()
ns.addHarlibrary(6, 7); // 调用全局方法addHarlibrary()
// 使用类、成员函数和方法的字符串名字进行反射调用
let className = 'Calc';
let methodName = 'instanceAdd';
let staticMethod = 'taticAdd';
let functionName = 'addHarlibrary';
ns[className][staticMethod](12, 13); // 调用静态成员函数staticAdd()
let calc1:ESObject = new ns[className](); // 实例化类Calc
calc1[methodName](14, 15); // 调用成员函数instanceAdd()
ns[functionName](16, 17); // 调用全局方法addHarlibrary()
});
四、动态 import 实现方案介绍
动态 import 根据入参是常量还是变量,分成动态 import 常量表达式和动态 import 变量表达式两大特性规格。
以下是动态 import 支持的规格列表:
动态 import 场景 | 动态 import 详细分类 | 说明 |
---|---|---|
本地工程模块 | 动态 import 模块内文件路径 | 要求路径以./或…/开头 |
本地工程模块 | 动态 import HSP 模块名 | - |
本地工程模块 | 动态 import HSP 模块文件路径 | 暂仅支持动态 import 常量表达式,不支持动态 import 变量表达式 |
本地工程模块 | 动态 import HAR 模块名 | - |
本地工程模块 | 动态 import HAR 模块文件路径 | 暂仅支持动态 import 常量表达式,不支持动态 import 变量表达式 |
远程包 | 动态 import 远程 HAR 模块名 | - |
远程包 | 动态 import ohpm 包名 | - |
API | 动态 import @system.* | - |
API | 动态 import @ohos.* | - |
API | 动态 import @arkui-x.* | - |
模块 Native 库 | 动态 import libNativeLibrary.so | - |
注:
- 当前所有 import 中使用的模块名是依赖方 oh-package.json5 的 dependencies 中的别名;
- 本地模块在依赖方的 dependencies 中配置的别名建议与 moduleName 以及 packageName 三者一致。moduleName 指的是被依赖的 HSP/HAR 的 module.json5 中配置的名字,packageName 指的是被依赖的 HSP/HAR 的 oh-package.json5 中配置的名字。
- import 一个模块名,实际的行为是 import 该模块的入口文件,一般为 index.ets/ts。
五、动态 import 实现中的关键点
(一)动态 import 常量表达式
动态 import 常量表达式是指动态 import 的入参为常量的场景。下面以 HAP 引用其他模块或 API 的示例来说明典型用法。
说明:本文示例代码中 Index.ets 等路径是按照当前 DevEco Studio 的模块配置设置,如后续发生变化,请调整位置及其他文件相对路径。
- HAP 常量动态 import HAR 模块名
// HAR's Index.ets
export function add(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am a HAR, %d + %d = %d', a, b, c);
return c;
}
// HAP's src/main/ets/pages/Index.ets
import('myHar').then((ns:ESObject) => {
console.log(ns.add(3, 5));
});
// HAP's oh-package.json5
"dependencies": {
"myHar": "file:../myHar"
}
- HAP 常量动态 import HAR 模块文件路径
// HAR's Index.ets
export function add(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am a HAR, %d + %d = %d', a, b, c);
return c;
}
// HAP's src/main/ets/pages/Index.ets
import('myHar/Index').then((ns:ESObject) => {
console.log(ns.add(3, 5));
});
// HAP's oh-package.json5
"dependencies": {
"myHar": "file:../myHar"
}
- HAP 常量动态 import HSP 模块名
// HSP's Index.ets
export function add(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am a HSP, %d + %d = %d', a, b, c);
return c;
}
// HAP's src/main/ets/pages/Index.ets
import('myHsp').then((ns:ESObject) => {
console.log(ns.add(3, 5));
});
// HAP's oh-package.json5
"dependencies": {
"myHsp": "file:../myHsp"
}
- HAP 常量动态 import HSP 模块名文件路径
// HSP's Index.ets
export function add(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am a HSP, %d + %d = %d', a, b, c);
return c;
}
// HAP's src/main/ets/pages/Index.ets
import('myHsp/Index').then((ns:ESObject) => {
console.log(ns.add(3, 5));
});
// HAP's oh-package.json5
"dependencies": {
"myHsp": "file:../myHsp"
}
- HAP 常量动态 import 远程 HAR 模块名
// HAP's src/main/ets/pages/Index.ets
import('@ohos/crypto-js').then((ns:ESObject) => {
console.log('DynamicImport @ohos/crypto-js: ' ns.CryptoJS.MD5(123456));
});
// HAP's oh-package.json5
"dependencies": {
"@ohos/crypto-js": "2.0.3-rc.0"
}
- HAP 常量动态 import ohpm 包
// HAP's src/main/ets/pages/Index.ets
import('json5').then((ns:ESObject) => {
console.log('DynamicImport json5');
});
// HAP's oh-package.json5
"dependencies": {
"json5": "1.0.2"
}
- HAP 常量动态 import 自己的单文件
// HAP's src/main/ets/Calc.ets
export function add(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am a HAP, %d + %d = %d', a, b, c);
return c;
}
// HAP's src/main/ets/pages/Index.ets
import('../Calc').then((ns:ESObject) => {
console.log(ns.add(3, 5));
});
- HAP 常量动态 import 自己的 Native 库
// libnativeapi.so's index.d.ts
export const add: (a:number, b:number) => number;
// HAP's src/main/ets/pages/Index.ets
import('libnativeapi.so').then((ns:ESObject) => {
console.log('DynamicImport libnativeapi.so: ' ns.default.add(2, 3));
});
// HAP's oh-package.json5
"dependencies": {
"libnativeapi.so": "file:./src/main/cpp/types/libnativeapi"
}
- HAP 常量动态 import 加载 API
// HAP's src/main/ets/pages/Index.ets
import('@system.app').then((ns:ESObject) => { ns.default.terminate(); });
import('@system.router').then((ns:ESObject) => { ns.default.clear(); });
import('@ohos.curves').then((ns:ESObject) => { ns.default.springMotion(0.555, 0.75, 0.001); });
import('@ohos.matrix4').then((ns:ESObject) => { ns.default.identity(); });
import('@ohos.hilog').then((ns:ESObject) => { ns.default.info(0x0000, 'testTag', '%{public}s', 'DynamicImport @ohos.hilog.'); });
(二)动态 import 变量表达式
DevEco Studio 中模块间的依赖关系通过 oh-package.json5 中的 dependencies 进行配置。dependencies 列表中所有模块默认都会进行安装(本地模块)或下载(远程模块),但是不会默认参与编译。HAP/HSP 编译时会以入口文件(一般为 Index.ets/ts)开始搜索依赖关系,搜索到的模块或文件才会加入编译。
在编译期,静态 import 和常量动态 import 可以被打包工具 rollup 及其插件识别解析,加入依赖树中,参与到编译流程,最终生成方舟字节码。但是如果是变量动态 import,该变量值可能需要进行运算或者外部传入才能得到,在编译态无法解析出其内容,也就无法加入编译。为了将这部分模块/文件加入编译,还需要额外增加一个 runtimeOnly 的 buildOption 配置,用于配置动态 import 的变量实际的模块名或者文件路径。
- runtimeOnly 字段 schema 配置格式
在 HAP/HSP/HAR 的 build-profile.json5 中的 buildOption 中增加 runtimeOnly 配置项,仅在通过变量动态 import 时配置,静态 import 和常量动态 import 无需配置;并且,通过变量动态 import 加载 API 时也无需配置 runtimeOnly。
如下实例说明如何配置通过变量动态 import 其他模块,以及变量动态 import 本模块自己的单文件:
// 变量动态import其他模块myHar
let harName = 'yHar';
import(harName).then(……);
// 变量动态import本模块自己的单文件src/main/ets/index.ets
let filePath = './Calc';
import(filePath).then(……);
对应的 runtimeOnly 配置:
"buildOption": {
"arkOptions": {
"runtimeOnly": {
"packages": [ "myHar" ] // 配置本模块变量动态import其他模块名,要求与dependencies中配置的名字一致。
"sources": [ "./src/main/ets/utils/Calc.ets" ] // 配置本模块变量动态import自己的文件路径,路径相对于当前 build-profile.json5 文件。
}
}
}
“runtimeOnly"的"packages”:用于配置本模块变量动态 import 其他模块名,要求与 dependencies 中配置的名字一致。
“runtimeOnly"的"sources”:用于配置本模块变量动态 import 自己的文件路径,路径相对于当前 build-profile.json5 文件。
-
使用实例
- HAP 变量动态 import HAR 模块名
// HAR's Index.ets export function add(a:number, b:number):number { let c = a + b; console.log('DynamicImport I am a HAR, %d + %d = %d', a, b, c); return c; } // HAP's src/main/ets/pages/Index.ets let packageName = 'yHar'; import(packageName).then((ns:ESObject) => { console.log(ns.add(3, 5)); }); // HAP's oh-package.json5 "dependencies": { "myHar": "file:../myHar" } // HAP's build-profile.json5 "buildOption": { "arkOptions": { "runtimeOnly": { "packages": [ "myHar" // 仅用于使用变量动态import其他模块名场景,静态import或常量动态import无需配置。 ] } } }
- HAP 变量动态 import HSP 模块名
// HSP's Index.ets export function add(a:number, b:number):number { let c = a + b; console.log('DynamicImport I am a HSP, %d + %d = %d', a, b, c); return c; } // HAP's src/main/ets/pages/Index.ets let packageName = 'yHsp'; import(packageName).then((ns:ESObject) => { console.log(ns.add(3, 5)); }); // HAP's oh-package.json5 "dependencies": { "myHsp": "file:../myHsp" } // HAP's build-profile.json5 "buildOption": { "arkOptions": { "runtimeOnly": { "packages": [ "myHsp" // 仅用于使用变量动态import其他模块名场景,静态import或常量动态import无需配置。 ] } } }
- HAP 变量动态 import 远程 HAR 模块名
// HAP's src/main/ets/pages/Index.ets let packageName = '@ohos/crypto-js'; import(packageName).then((ns:ESObject) => { console.log('DynamicImport @ohos/crypto-js: ' ns.CryptoJS.MD5(123456)); }); // HAP's oh-package.json5 "dependencies": { "@ohos/crypto-js": "2.0.3-rc.0" } // HAP's build-profile.json5 "buildOption": { "arkOptions": { "runtimeOnly": { "packages": [ "@ohos/crypto-js" // 仅用于使用变量动态import其他模块名场景,静态import或常量动态 import 无需配置。 ] } } }
- HAP 变量动态 import ohpm 包
// HAP's src/main/ets/pages/Index.ets let packageName = 'json5'; import(packageName).then((ns:ESObject) => { console.log('DynamicImport json5'); }); // HAP's oh-package.json5 "dependencies": { "json5": "1.0.2" } // HAP's build-profile.json5 "buildOption": { "arkOptions": { "runtimeOnly": { "packages": [ "json5" // 仅用于使用变量动态 import 其他模块名场景,静态 import 或常量动态 import 无需配置。 ] } } }
- HAP 变量动态 import 自己的单文件
// HAP's src/main/ets/Calc.ets export function add(a:number, b:number):number { let c = a + b; console.log('DynamicImport I am a HAP, %d + %d = %d', a, b, c); return c; } // HAP's src/main/ets/pages/Index.ets let filePath = '../Calc'; import(filePath).then((ns:ESObject) => { console.log(ns.add(3, 5)); }); // HAP's build-profile.json5 "buildOption": { "arkOptions": { "runtimeOnly": { "sources": [ "./src/main/ets/Calc.ets" // 仅用于使用变量动态 import 模块自己单文件场景,静态 import 或常量动态 import 无需配置。 ] } } }
- HAP 变量动态 import 自己的 Native 库
// libnativeapi.so's index.d.ts export const add: (a:number, b:number) => number; // HAP's src/main/ets/pages/Index.ets let soName = 'libnativeapi.so'; import(soName).then((ns:ESObject) => { console.log('DynamicImport libnativeapi.so: 's.default.add(2, 3)); }); // HAP's oh-package.json5 "dependencies": { "libnativeapi.so": "file:./src/main/cpp/types/libnativeapi" } // HAP's build-profile.json5 "buildOption": { "arkOptions": { "runtimeOnly": { "packages": [ "libnativeapi.so" // 仅用于使用变量动态 import 其他模块名场景,静态 import 或常量动态 import 无需配置。 ] } } }
- HAP 变量动态 import 加载 API
// HAP's src/main/ets/pages/Index.ets let packageName = '@system.app'; import(packageName).then((ns:ESObject) => { ns.default.terminate(); }); packageName = '@system.router'; import(packageName).then((ns:ESObject) => { ns.default.clear(); }); packageName = '@ohos.curves'; import(packageName).then((ns:ESObject) => { ns.default.springMotion(0.555, 0.75, 0.001); }); packageName = '@ohos.matrix4'; import(packageName).then((ns:ESObject) => { ns.default.identity(); }); packageName = '@ohos.hilog'; import(packageName).then((ns:ESObject) => { ns.default.info(0x0000, 'testTag', '%{public}s', 'DynamicImport @ohos.hilog.'); });
变量动态 import 加载 API 时无需配置 runtimeOnly。
六、HAR 模块间动态 import 依赖解耦
当应用包含多个 HAR 包,且 HAR 包之间依赖关系比较复杂。在 IDE 中配置依赖关系时,可能会形成循环依赖。这时,如果 HAR 之间的依赖关系中仅有变量动态 import,可以将 HAR 包之间直接依赖关系转移到 HAP/HSP 中配置,HAR 包之间无需配置依赖关系,从而达到 HAR 包间依赖解耦的目的。如下示意图:
HAR 之间依赖关系转移到 HAP/HSP 后:
(一)使用限制
- 仅限本地源码 HAR 包之间形成循环依赖时可使用该规避方案。
- 被转移依赖的 HAR 之间只能通过变量动态 import,不能有静态 import 或常量动态 import。
- 转移依赖时,dependencies 和 runtimeOnly 依赖配置要同时转移。
- HSP 不支持转移依赖。即:HAP->HSP1->HSP2->HSP3,这里的 HSP2 和 HSP3 不能转移到 HAP 上面。
- 转移依赖的整个链路上只能有 HAR,不能跨越 HSP 转移。即:HAP->HAR1->HAR2->HSP->HAR3->HAR4。HAR1 对 HAR2 的依赖可以转移到 HAP 上,HAR3 对 HAR4 的依赖可以转移到 HSP 上,但是,不能将 HAR3 或 HAR4 转移到 HAP 上。
(二)使用实例
下面的实例 HAP 变量动态 import HAR 包 har1,har1 变量动态 import 另一个 HAR 包 har2。
// HAP's oh-package.json5
"dependencies": {
"har1": "file:../har1"
}
// HAP's build-profile.json5
"buildOption": {
"arkOptions": {
"runtimeOnly": {
"packages": [
"har1" // 仅用于使用变量动态 import 其他模块名场景,静态 import 或常量动态 import 无需配置。
]
}
}
}
// HAP's src/main/ets/pages/Index.ets
let harName = 'har1';
import(harName).then((ns:ESObject) => {
console.log('DynamicImport addHar1 4 + 5 = ' ns.addHar1(4, 5));
});
// har1's oh-package.json5
"dependencies": {
"har2": "file:../har2"
}
// har1's build-profile.json5
"buildOption": {
"arkOptions": {
"runtimeOnly": {
"packages": [
"har2" // 仅用于使用变量动态 import 其他模块名场景,静态 import 或常量动态 import 无需配置。
]
}
}
}
// har1's Index.ets
export { addHar1 } from './src/main/ets/utils/Calc'
// har1's src/main/ets/utils/Calc.ets
export function addHar1(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am har1, %d + %d = %d', a, b, c);
let harName = 'har2';
import(harName).then((ns:ESObject) => {
console.log('DynamicImport addHar2 4 + 5 = ' ns.addHar2(4, 5));
});
return c;
}
// har2's Index.ets
export { addHar2 } from './src/main/ets/utils/Calc'
// har2's src/main/ets/utils/Calc.ets
export function addHar2(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am har2, %d + %d = %d', a, b, c);
return c;
}
har1 对 har2 的依赖 dependencies 和 runtimeOnly 配置转移到 HAP 中,har1 不需要配置对 har2 的 dependencies 和 runtimeOnly 配置:
// HAP's oh-package.json5
"dependencies": {
"har1": "file:../har1",
"har2": "file:../har2"
}
// HAP's build-profile.json5
"buildOption": {
"arkOptions": {
"runtimeOnly": {
"packages": [
"har1",
"har2"
]
}
}
}
// HAP's src/main/ets/pages/Index.ets
let harName = 'har1';
import(harName).then((ns:ESObject) => {
console.log('DynamicImport addHar1 4 + 5 = ' ns.addHar1(4, 5));
});
// har1's Index.ets
export { addHar1 } from './src/main/ets/utils/Calc'
// har1's src/main/ets/utils/Calc.ets
export function addHar1(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am har1, %d + %d = %d', a, b, c);
let harName = 'har2';
import(harName).then((ns:ESObject) => {
console.log('DynamicImport addHar2 4 + 5 = ' ns.addHar2(4, 5));
});
return c;
}
// har2's Index.ets
export { addHar2 } from './src/main/ets/utils/Calc'
// har2's src/main/ets/utils/Calc.ets
export function addHar2(a:number, b:number):number {
let c = a + b;
console.log('DynamicImport I am har2, %d + %d = %d', a, b, c);
return c;
}