Lazy Import 动态加载:减少 HarmonyOS 应用冷启动时间

适用版本 :HarmonyOS 6.1(API 12)及以上

验证环境 :Pura 90 Pro 模拟器(HarmonyOS 6.1.1,API 24)

关键 API :动态 import() 表达式、ESObject 类型


前言

每一行 import 语句都有代价:Bundle 启动时,运行时需要解析和执行所有静态导入的模块。对于大型工具库(PDF 解析、图表渲染、复杂加密算法),如果写在文件顶部的 import 里,即使用户从未触发相关功能,这些模块也会在每次启动时占用时间和内存。

动态 import() 是解决方案:首次真正需要时才异步加载模块,首屏渲染不受影响。


静态 import vs 动态 import()

typescript 复制代码
// ── 静态 import(写在文件顶部)──
// Bundle 启动时立即解析和执行,无论是否用到
import { heavyFunction } from '../utils/HeavyUtils'

// ── 动态 import()(写在函数内)──
// 返回 Promise,首次调用时才加载
async function loadOnDemand(): Promise<void> {
  const mod: ESObject = await import('../utils/HeavyUtils')
  const result = (mod.heavyFunction as Function)(args)
}
维度 静态 import 动态 import()
加载时机 Bundle 启动时 首次调用时
首屏影响 增加冷启动耗时 不影响首屏
使用方式 直接用符号名 通过 ESObject 访问
适用场景 核心功能 非首屏、大型工具库

完整代码

HeavyUtils 模块(被懒加载的模块)

typescript 复制代码
// utils/HeavyUtils.ets

// 模块级变量:记录模块初始化时刻(模块加载时执行一次)
const MODULE_LOAD_TIME = Date.now()

// 计算 limit 以内质数个数(CPU 密集型,适合懒加载)
export function computeHeavy(limit: number): number {
  const sieve: boolean[] = new Array<boolean>(limit + 1).fill(true)
  sieve[0] = false
  if (limit >= 1) { sieve[1] = false }
  for (let i = 2; i * i <= limit; i++) {
    if (sieve[i]) {
      for (let j = i * i; j <= limit; j += i) { sieve[j] = false }
    }
  }
  let count = 0
  for (let k = 0; k <= limit; k++) { if (sieve[k]) { count++ } }
  return count
}

// 数字格式化(用逗号分隔千位)
export function formatNumber(n: number): string {
  return n.toLocaleString()
}

// 返回模块初始化时刻(用于对比与应用启动的时间差)
export function getModuleLoadTime(): number {
  return MODULE_LOAD_TIME
}

主页面:动态加载与缓存复用

typescript 复制代码
@Entry
@Component
struct LazyImportDemo {
  @State dynamicStatus: LoadStatus = 'idle'
  @State dynamicResult: string = ''
  @State results: DemoResult[] = []

  // 已加载的模块缓存(ESObject 接收 import() 结果)
  private cachedModule: ESObject = null
  private appStartTime: number = Date.now()

  private async loadModuleDynamic(): Promise<void> {
    // 首次加载后缓存复用
    if (this.cachedModule !== null) {
      this.dynamicResult = '模块已缓存,直接复用(无需重新加载)'
      return
    }

    this.dynamicStatus = 'loading'
    const start = Date.now()

    try {
      // 路径相对于当前 .ets 文件
      const mod: ESObject = await import('../utils/HeavyUtils')
      this.cachedModule = mod

      // 通过 ESObject 访问导出函数(需要类型断言)
      const primeCount: number = (mod.computeHeavy as Function)(100000) as number
      const formatted: string = (mod.formatNumber as Function)(primeCount) as string
      const loadTime: number = (mod.getModuleLoadTime as Function)() as number

      this.dynamicResult =
        `动态加载成功!耗时 ${Date.now() - start}ms\n` +
        `10万以内质数:${formatted} 个\n` +
        `模块初始化时刻:+${loadTime - this.appStartTime}ms(相对应用启动)`
      this.dynamicStatus = 'loaded'
    } catch (e) {
      const err = e as Error
      this.dynamicStatus = 'error'
      this.dynamicResult = `加载失败:${err.message}`
    }
  }
}

ESObject 类型注意事项

typescript 复制代码
// ✅ 正确:用 ESObject 接收 import() 返回值
const mod: ESObject = await import('../utils/HeavyUtils')

// ✅ 正确:通过 as Function 类型断言调用导出函数
const count = (mod.computeHeavy as Function)(100000) as number

// ❌ 错误:直接调用不经类型断言(ArkTS 不允许隐式调用 ESObject)
mod.computeHeavy(100000)  // 编译错误

// ❌ 错误:用具体类型接收(import 返回的是模块命名空间,非具体类型)
import type { HeavyModule } from '../utils/HeavyUtils'
const mod: HeavyModule = await import('../utils/HeavyUtils')  // 类型不匹配

模拟器运行截图

初始状态

模块尚未加载,按钮显示"动态 import() 加载模块","复用缓存"按钮为不可用状态。


动态加载完成

点击"动态 import() 加载模块"后,运行时按需加载 HeavyUtils 模块,计算出 10 万以内质数共 9,592 个,并显示模块初始化相对应用启动的时间差。

实测数据:

指标 数值
动态加载耗时 0ms(ArkTS 运行时预解析优化)
10万以内质数 9,592 个
模块初始化相对应用启动 +820,819ms(延迟加载,远离启动路径)

加载耗时 0ms 是 ArkTS 运行时的预编译优化结果------模块的 ABC(Ark ByteCode)已预先编译,import() 触发时只需实例化模块而非重新解析,实际延迟极低。


关键模式:延迟加载 + 缓存复用

typescript 复制代码
private cachedModule: ESObject = null

private async getModule(): Promise<ESObject> {
  if (this.cachedModule !== null) {
    return this.cachedModule  // 直接返回缓存,0开销
  }
  this.cachedModule = await import('../utils/HeavyUtils')
  return this.cachedModule
}

// 调用方:无论调用几次,模块只加载一次
private async doSomething(): Promise<void> {
  const mod = await this.getModule()
  const result = (mod.someFunction as Function)(args) as string
}

适合懒加载的模块类型

模块类型 懒加载理由
PDF/Office 文档处理 体积大,仅特定场景用
图表渲染库(如折线图) 非首屏,用户主动触发
复杂加密/哈希算法 偶尔调用,无需常驻内存
地图/导航功能 有独立页面,进入时再加载
数据导出(CSV/Excel) 用户明确触发导出操作

不适合懒加载的:首页必需的 UI 组件、网络层基础库、登录鉴权模块(会造成可见的加载延迟)。


常见问题

Q:import() 的路径写法有什么规则?

A:路径相对于当前 .ets 文件,与静态 import 写法相同。字符串必须是静态字面量(编译器需要在构建期分析依赖图),不能是运行时拼接的变量:

typescript 复制代码
// ✅ 正确:字面量路径
const mod = await import('../utils/HeavyUtils')

// ❌ 错误:变量路径(编译器报错)
const path = '../utils/HeavyUtils'
const mod = await import(path)

Q:ESObject 有什么安全风险?

A:通过 (mod.fn as Function)(args) as ReturnType 访问导出函数时,类型断言绕过了编译器检查,函数名拼错或参数类型错误会在运行时才报错。建议为模块编写类型声明文件(.d.ets),或封装一个类型安全的 wrapper 函数。

相关推荐
条tiao条6 小时前
Navigation页面跳转教学博客
ui·华为·harmonyos·鸿蒙·页面跳转
ZZZMMM.zip6 小时前
竞品分析框架-基于鸿蒙的AI竞品分析应用开发实践
人工智能·华为·harmonyos·鸿蒙系统
ldsweet8 小时前
《HarmonyOS技术精讲-Basic Services Kit》账号管理:应用内账号创建与管理
华为·harmonyos
shaodong11239 小时前
HarmonyOS新特性-沉浸光感在叠叠消小游戏中的落地实践
harmonyos·鸿蒙
xd1855785559 小时前
歌词创作工坊-基于鸿蒙的AI歌词创作应用开发实践
人工智能·华为·harmonyos·鸿蒙
子非鱼942710 小时前
07-Flutter 鸿蒙实战 07:故事集列表与缓存设计
学习·flutter·缓存·华为·harmonyos
程序员黑豆10 小时前
从零开始:创建第一个鸿蒙应用程序
前端·harmonyos
千逐6812 小时前
HarmonyOS 实战 | 弹窗/提示框怎么做——Alert、Dialog、Toast 详解
华为·harmonyos·鸿蒙
子非鱼942712 小时前
08-Flutter 鸿蒙实战 08:故事详情页,编辑、删除、重新生成与底部弹层
flutter·华为·harmonyos