跳到主要内容

动态加载

动态import支持条件延迟加载,支持部分反射功能,可以提升页面的加载速度;动态import支持加载HSP模块/HAR模块/OHPM包/Native库等,并且HAR模块间只有变量动态import时还可以进行模块解耦。

技术适用场景介绍

应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态import代替静态import。下面是可能会需要动态import的场景:

  • 当静态import的模块明显降低了代码的加载速度且很少被使用,或者并不需要马上使用它。
  • 当静态import的模块明显占用了大量的系统内存且很少被使用。
  • 当被导入的模块,在加载时并不存在,需要异步获取。
  • 当需要动态构建模块说明符时,应使用动态import。静态import仅支持静态说明符。
  • 当导入的模块存在副作用(即模块中包含直接运行的代码),这些副作用仅在满足特定条件时才需要。

业务扩展场景介绍

动态import在业务上除了能实现条件延迟加载,还可以实现部分反射功能。实例如下,HAP动态import HAR包harlibrary,并调用类Calc的静态成员函数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.info('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.info('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.info('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 = 'staticAdd';
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 HAR模块名-
本地工程模块动态import HAR模块文件路径-
远程包动态import远程HAR模块名-
远程包动态import ohpm包名-
API动态import @system.*-
API动态import @ohos.*-
API动态import @arkui-x.*-
模块Native库动态import libNativeLibrary.so-

说明:

1.当前所有import中使用的模块名都是依赖方oh-package.json5文件中dependencies项的别名。

2.本地模块在依赖方的dependencies中配置的别名建议与moduleName以及packageName三者一致。moduleName指的是被依赖的HSP/HAR的module.json5中配置的名字,packageName指的是被依赖的HSP/HAR的oh-package.json5中配置的名字。

3.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.info('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.info('DynamicImport ns.add(3, 5) = %d', ns.add(3, 5));
    });

    // 可使用 await 处理动态import (必须在 async 函数内使用)
    async function asyncDynamicImport() {
    let ns:ESObject = await import('myhar');
    console.info('DynamicImport ns.add(3, 5) = %d', 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.info('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.info('DynamicImport ns.add(3, 5) = %d', 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.info('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.info('DynamicImport ns.add(3, 5) = %d', 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.info('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.info('DynamicImport ns.add(3, 5) = %d', 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.info('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.info('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.info('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.info('DynamicImport ns.add(3, 5) = %d', 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.info('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/Index.ts)开始搜索依赖关系,搜索到的模块或文件才会加入编译。 在编译期,静态import和常量动态import可以被打包工具rollup及其插件识别解析,加入依赖树中,参与编译流程,最终生成方舟字节码。但是,如果是变量动态import,该变量值可能需要进行运算或外部传入才能得到,在编译态无法解析其内容,也就无法加入编译。为了将这部分模块/文件加入编译,还需要额外增加一个runtimeOnly的buildOption配置,用于指定动态import的变量实际的模块名或文件路径。

1. runtimeOnly字段schema配置格式

在HAP/HSP/HAR的build-profile.json5中的buildOption中增加runtimeOnly配置项,仅在通过变量动态import时配置,静态import和常量动态import无需配置;并且,通过变量动态import加载API时也无需配置runtimeOnly。如下实例说明如何配置通过变量动态import其他模块,以及变量动态import本模块自己的单文件:

// 变量动态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');
})

对应的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文件。

2. 使用实例

  • HAP变量动态import HAR模块名

    // HAR's Index.ets
    export function add(a: number, b: number): number {
    let c = a + b;
    console.info('DynamicImport I am a HAR, %d + %d = %d', a, b, c);
    return c;
    }
    // HAP's src/main/ets/pages/Index.ets
    let packageName = 'myhar';
    import(packageName).then((ns:ESObject) => {
    console.info('DynamicImport ns.add(3, 5) = %d', 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.info('DynamicImport I am a HSP, %d + %d = %d', a, b, c);
    return c;
    }
    // HAP's src/main/ets/pages/Index.ets
    let packageName = 'myhsp';
    import(packageName).then((ns:ESObject) => {
    console.info('DynamicImport ns.add(3, 5) = %d', 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.info('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.info('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.info('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.info('DynamicImport ns.add(3, 5) = %d', 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.info('DynamicImport libnativeapi.so: ' + ns.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包之间的依赖关系比较复杂。在DevEco Studio中配置依赖关系时,可能会形成循环依赖。这时,如果HAR之间的依赖关系中仅有变量动态import,可以将HAR包之间直接依赖关系转移到HAP/HSP中配置,HAR包之间无需配置依赖关系,从而达到HAR包间依赖解耦的目的。如下示意图:

变量动态import HAR包形成循环依赖

HAR之间的依赖关系转移至HAP/HSP后:

变量动态import HAR包依赖转移到HAP

使用限制

  • 仅限在本地源码HAR包之间存在循环依赖时,使用该规避方案。
  • 被转移依赖的HAR之间只能通过变量动态import,不能有静态import或常量动态import。
  • 转移依赖时,需同时转移dependenciesruntimeOnly依赖配置。
  • 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上。
  • 如果引用了其他工程模块、远程包或集成HSP,需确保useNormalizedOHMUrl配置一致,同时设置为true或false,否则可能导致运行错误:Cannot find dynamic-import module library

使用实例

下面的实例通过在单向依赖HAP->HAR1->HAR2->HAR3之上增加依赖HAR2->HAR1、HAR3->HAR1,形成了循环依赖。

变量动态import HAR包形成循环依赖

// HAP's src/main/ets/pages/Index.ets
let harName = 'har1'
import(harName).then((ns: ESObject) => {
console.info('[DynamicImport] hap -> har1, 0 + 1 = ' + ns.classHar1.add(0, 1));
})

// HAR1's src/main/ets/utils/Calc.ets
export class classHar1 {
static isImportedHar2: boolean = false;

static add(a: number, b: number): number {
const c = a + b;
console.info('[DynamicImport] classHar1.add(), %d + %d = %d', a, b, c);

if (!classHar1.isImportedHar2) {
const harName = 'har2';
import(harName).then((ns: ESObject) => {
classHar1.isImportedHar2 = true;
console.info('[DynamicImport] har1 -> har2, 1 + 2 = ' + ns.classHar2.add(1, 2));
})
}

return c;
}
}
// HAR1's Index.ets
export { classHar1 } from './src/main/ets/utils/Calc';

// HAR2's src/main/ets/utils/Calc.ets
export class classHar2 {
static isImportedHar1: boolean = false;
static isImportedHar3: boolean = false;

static add(a: number, b: number): number {
const c = a + b;
console.info('[DynamicImport] classHar2.add(), %d + %d = %d', a, b, c);

if (!classHar2.isImportedHar1) {
const harName = 'har1';
import(harName).then((ns: ESObject) => {
classHar2.isImportedHar1 = true;
console.info('[DynamicImport] har2 -> har1, 2 + 1 = ' + ns.classHar1.add(2, 1));
})
}

if (!classHar2.isImportedHar3) {
const harName = 'har3';
import(harName).then((ns: ESObject) => {
classHar2.isImportedHar3 = true;
console.info('[DynamicImport] har2 -> har3, 2 + 3 = ' + ns.classHar3.add(2, 3));
})
}

return c;
}
}
// HAR2's Index.ets
export { classHar2 } from './src/main/ets/utils/Calc';

// HAR3's src/main/ets/utils/Calc.ets
export class classHar3 {
static isImportedHar1: boolean = false;

static add(a: number, b: number): number {
const c = a + b;
console.info('[DynamicImport] classHar3.add(), %d + %d = %d', a, b, c);

if (!classHar3.isImportedHar1) {
const harName = 'har1';
import(harName).then((ns: ESObject) => {
classHar3.isImportedHar1 = true;
console.info('[DynamicImport] har3 -> har1, 3 + 1 = ' + ns.classHar1.add(3, 1));
})
}

return c;
}
}
// HAR3's Index.ets
export { classHar3 } from './src/main/ets/utils/Calc';

若未对HAR之间的dependenciesruntimeOnly配置进行依赖解耦,ohpm无法解决循环依赖,依赖安装失败。

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

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

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

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

对应的报错信息如下:

ohpm ERROR: Run install command failed 
Error: 00618005 Invalid Dependency
Error Message: Invalid dependency har2@~\Coupled\har2 -> har2@1.0.0.

Try the following:
The name of an indirect dependency cannot be the same as the module name.

将HAR之间的dependenciesruntimeOnly配置转移到HAP中,解耦了包间循环依赖,程序能够正确运行。

// 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"
]
}
}
}

// HAR1's oh-package.json5
"dependencies": {}
// HAR1's build-profile.json5
"buildOption": {}

// HAR2's oh-package.json5
"dependencies": {}
// HAR2's build-profile.json5
"buildOption": {}

// HAR3's oh-package.json5
"dependencies": {}
// HAR3's build-profile.json5
"buildOption": {}

对应的运行日志如下:

[DynamicImport] classHar1.add(), 0 + 1 = 1
[DynamicImport] hap -> har1, 0 + 1 = 1
[DynamicImport] classHar2.add(), 1 + 2 = 3
[DynamicImport] har1 -> har2, 1 + 2 = 3
[DynamicImport] classHar1.add(), 2 + 1 = 3
[DynamicImport] har2 -> har1, 2 + 1 = 3
[DynamicImport] classHar3.add(), 2 + 3 = 5
[DynamicImport] har2 -> har3, 2 + 3 = 5
[DynamicImport] classHar1.add(), 3 + 1 = 4
[DynamicImport] har3 -> har1, 3 + 1 = 4