应用启动链路封面

HarmonyOS 应用能显示第一个页面,并不等于启动链路已经可靠。真实工程里更容易出问题的是页面出现之前的几百毫秒:应用级数据是否已经准备好,窗口对象能否正常取得,状态栏与底部手势区高度有没有同步,媒体查询监听是否已注册,首屏加载失败后有没有留下可定位的日志,窗口销毁时监听是否完整释放。

本文基于句匠项目的真实源码 entry/src/main/ets/entryability/EntryAbility.ets 展开,并用 module.json5main_pages.jsonUserDataManager.etsBreakpointSystem.etsSplashPage.ets 交叉核对入口前后关系。目标不是再写一个“能启动”的示例,而是把 HarmonyOS 5.0 及以上 Stage Model 应用从 EntryAbility 创建到 SplashPage 首屏装载的责任边界讲清楚。

本文唯一源码标识:com.jiaweikang.one18

源码能够证明的范围包括:浅色模式设置、本地 Preferences 数据恢复、AppStorage 默认值、三档媒体查询注册、主窗口与避让区监听、loadContent('pages/SplashPage')、生命周期日志和监听释放。源码没有实现账号登录前置、远程配置、广告开屏、推送冷启动、深链参数分发、云端初始化或启动任务并发编排,本文不对这些能力作扩展性宣传。

一、先画清启动时序:页面只是最后一棒

Stage Model 下,桌面点击图标之后并不是立刻执行 SplashPage.aboutToAppear()。句匠的实际顺序可以拆成五段:

  1. 系统依据 module.json5 找到 EntryAbility
  2. onCreate() 完成应用级状态和依赖初始化;
  3. onWindowStageCreate() 取得主窗口并建立避让区监听;
  4. windowStage.loadContent('pages/SplashPage') 装载首屏;
  5. SplashPage 出现后播放动画,并在 2 秒后替换到 Index

从 Ability 到首屏的启动流程

这个顺序决定了初始化放在哪里。首屏一出现就要读取的数据,必须在 loadContent 前准备;依赖 Window 的系统区域信息,必须等到 WindowStage 创建后处理;只属于启动页动画的状态,应留在 SplashPage,不能被抬升到应用级存储。

动作所属边界句匠源码位置失败后的影响
恢复收藏、错题、进度应用数据初始化onCreate()首屏读取到空值或旧值
初始化主 Tab 和安全区默认值应用共享状态onCreate()@StorageLink 缺少稳定初值
注册宽度断点应用布局环境onCreate()页面无法跟随窗口宽度更新
获取主窗口、读取避让区窗口生命周期onWindowStageCreate()顶部或底部内容可能被系统区域遮挡
加载 SplashPage首屏装载onWindowStageCreate()白屏或入口不可用
清理监听销毁生命周期onWindowStageDestroy() / onDestroy()重复回调或生命周期泄漏

启动问题排查时,先判断故障落在哪个阶段,比盯着第一个页面的 build() 更有效。页面完全没有出现,优先看 Ability 和 loadContent;页面出现但布局压住系统栏,再看避让区同步;主页数据不对,才继续检查本地数据恢复和共享状态。

二、module.json5 决定系统如何找到入口

入口代码能被执行,前提是模块声明和类名一致。句匠的 entry/src/main/module.json5 里,mainElementabilities.namesrcEntry 指向同一个入口:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "mainElement": "EntryAbility",
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "icon": "$media:app_icon_square",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:app_icon_square",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["ohos.want.action.home"]
          }
        ]
      }
    ]
  }
}

这里有三处会直接影响冷启动。

第一,mainElement 和 Ability 名称都为 EntryAbility,系统入口与 ArkTS 类保持一致。第二,srcEntry 是相对模块目录的真实文件路径,入口重命名后必须同步修改。第三,startWindowIconstartWindowBackground 决定 ArkUI 内容加载前的启动窗口视觉,它们不是 SplashPage 的组件内容。

因此,遇到“点击图标后先闪一下不一致的背景”时,不要只改启动页背景,还应核对启动窗口背景资源与 SplashPage 根容器背景。遇到“安装成功但入口拉不起”时,则应先检查 mainElementnamesrcEntryskills,而不是在页面路由里反复试错。

pages 指向 $profile:main_pages,其列表同时包含 pages/SplashPagepages/Index。这让后面的 loadContent('pages/SplashPage')router.replaceUrl({ url: 'pages/Index' }) 都有明确的页面注册依据。

三、onCreate 只做应用级准备,不碰窗口布局

句匠的 EntryAbility 继承 UIAbilityonCreate() 收到 WantLaunchParam,但当前源码没有解析它们,而是集中完成四类应用级初始化:

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  try {
    this.context.getApplicationContext()
      .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT)
  } catch (err) {
    hilog.error(
      DOMAIN,
      'testTag',
      'Failed to set colorMode. Cause: %{public}s',
      JSON.stringify(err)
    )
  }

  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate')
  UserDataManager.init(this.context)

  AppStorage.setOrCreate<number>('currentTabIndex', 0)
  AppStorage.setOrCreate<number>('favoriteTabIndex', 0)
  AppStorage.setOrCreate<number>('topAvoidAreaHeightPx', 0)
  AppStorage.setOrCreate<number>('navigationIndicatorHeightPx', 0)

  BreakpointSystem.register()
}

颜色模式设置被 try/catch 包围,失败时记录错误但不阻断后续初始化。当前项目明确把应用设置为浅色模式,因此测试时要验证系统处于深色模式时,启动窗口、Splash 和主页仍保持可读,而不能宣称项目已实现跟随系统的深浅色切换。

UserDataManager.init(this.context) 依赖 UIAbilityContext,放在 Ability 边界比放进页面更合适。页面不需要持有宽泛的 Context,也不需要知道 Preferences 的存储名和键名,只消费恢复后的 AppStorage 数据。

四个 setOrCreate 则提供确定的默认值。尤其是两个避让区高度先写为 0,意味着即使后续窗口 API 抛错,页面也不会拿到未初始化值。setOrCreate 只在键不存在时创建,并不是每次启动都强制覆盖已有状态,这一点与直接 set 的语义不同。

这一阶段没有调用窗口 API,是正确的生命周期分层。WindowStage 尚未交给 onWindowStageCreate(),此时不应假定主窗口已经可用。

四、把本地数据恢复放在 loadContent 之前

句匠的首屏和主页会读取收藏、笔记、错题、学习进度、考试记录等状态。UserDataManager.init() 使用同步 Preferences API 恢复这些数据,并写入 AppStorage

static init(context: common.UIAbilityContext | common.Context): void {
  try {
    UserDataManager.prefs = preferences.getPreferencesSync(
      context,
      { name: UserDataManager.STORE_NAME }
    )

    const favStr = UserDataManager.prefs
      .getSync(UserDataManager.K_FAV, '[]') as string
    const wrongStr = UserDataManager.prefs
      .getSync(UserDataManager.K_WRONG, '[]') as string
    const progressStr = UserDataManager.prefs
      .getSync(UserDataManager.K_PROGRESS, '[]') as string

    AppStorage.setOrCreate<FavoriteRecord[]>(
      'favoriteRecords',
      JSON.parse(favStr) as FavoriteRecord[]
    )
    AppStorage.setOrCreate<WrongRecord[]>(
      'wrongRecords',
      JSON.parse(wrongStr) as WrongRecord[]
    )
    AppStorage.setOrCreate<BankProgress[]>(
      'bankProgress',
      JSON.parse(progressStr) as BankProgress[]
    )
  } catch (_) {
    AppStorage.setOrCreate<FavoriteRecord[]>('favoriteRecords', [])
    AppStorage.setOrCreate<WrongRecord[]>('wrongRecords', [])
    AppStorage.setOrCreate<BankProgress[]>('bankProgress', [])
  }
}

这段实现给启动链路提供了两个保证:正常时首屏装载前已经有本地数据,异常时至少有空数组和默认设置,不会因为 JSON 损坏或 Preferences 读取失败让 Ability 直接退出。

同时也要看到它的真实边界。这里使用同步读取,数据量目前只是轻量 JSON 字符串,适合当前本地学习记录;如果未来记录规模明显增大,继续在 onCreate() 同步解析大量数据会拉长首屏前耗时。那时应测量启动耗时,再决定是否迁移到 RDB、拆分关键数据与延后数据,而不是未经测量就把所有初始化改成异步。

更稳的启动任务分级可以按首屏依赖划分:

等级示例是否必须在 loadContent 前完成
必需主 Tab 初值、首屏直接读取的数据
布局必需当前断点、安全区初值至少要有默认值
可延后非首屏统计汇总、低优先级缓存
不属于当前源码登录、远程配置、广告请求不应凭空加入

启动优化不是简单地“全部异步化”,而是先识别首屏真正依赖什么,再缩短关键路径。

五、BreakpointSystem 在页面出现前建立布局环境

模块声明支持 phonetablet2in1,源码用 BreakpointSystem.register() 建立三档媒体查询:

static register(): void {
  BreakpointSystem.smListener =
    mediaquery.matchMediaSync('(width<=600vp)')
  BreakpointSystem.mdListener =
    mediaquery.matchMediaSync('(600vp<width<=840vp)')
  BreakpointSystem.lgListener =
    mediaquery.matchMediaSync('(840vp<width)')

  BreakpointSystem.smListener.on('change', result => {
    if (result.matches) BreakpointSystem.update('sm')
  })
  BreakpointSystem.mdListener.on('change', result => {
    if (result.matches) BreakpointSystem.update('md')
  })
  BreakpointSystem.lgListener.on('change', result => {
    if (result.matches) BreakpointSystem.update('lg')
  })

  if (BreakpointSystem.smListener.matches) {
    BreakpointSystem.update('sm')
  } else if (BreakpointSystem.mdListener.matches) {
    BreakpointSystem.update('md')
  } else {
    BreakpointSystem.update('lg')
  }
}

注册后立即执行一次初始判断,避免页面第一次构建时只有监听、没有当前值。update() 把结果写入 AppStoragecurrentBreakpoint,页面再通过 @StorageLink 响应变化。

这项初始化放在 onCreate(),意味着 SplashPage 装载前断点状态已经可用。当前 Splash 的布局是简单居中结构,没有直接消费断点;真正使用该状态的是后续 Index。不过入口提前建立环境,可以避免进入主页时才注册监听造成首帧布局跳变。

媒体查询监听也是资源,因此 onDestroy() 必须调用 BreakpointSystem.unregister()。如果只注册不释放,在 Ability 重建、窗口反复创建或测试多次进入退出时,会增加重复回调风险。

需要谨慎描述的是:存在 sm/md/lg 三档断点不等于当前每个断点都呈现不同导航。文章能确认的是断点系统已注册并同步状态,具体布局分支仍应以页面源码为准。

六、onWindowStageCreate 才开始操作主窗口

当系统创建窗口舞台后,onWindowStageCreate(windowStage) 获得合法的窗口入口。句匠源码使用同步 API取得主窗口,然后立即读取避让区:

onWindowStageCreate(windowStage: window.WindowStage): void {
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate')

  try {
    this.mainWindow = windowStage.getMainWindowSync()
    this.updateNavigationIndicatorHeight()

    this.avoidAreaCallback = (data: window.AvoidAreaOptions) => {
      if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR ||
          data.type === window.AvoidAreaType.TYPE_SYSTEM) {
        this.updateNavigationIndicatorHeight()
      }
    }

    this.mainWindow.on('avoidAreaChange', this.avoidAreaCallback)
  } catch (err) {
    AppStorage.setOrCreate<number>('topAvoidAreaHeightPx', 0)
    AppStorage.setOrCreate<number>('navigationIndicatorHeightPx', 0)
    hilog.warn(
      DOMAIN,
      'testTag',
      'Failed to observe avoid area. Cause: %{public}s',
      JSON.stringify(err)
    )
  }

  windowStage.loadContent('pages/SplashPage', (err) => {
    if (err.code) {
      hilog.error(
        DOMAIN,
        'testTag',
        'Failed to load the content. Cause: %{public}s',
        JSON.stringify(err)
      )
      return
    }
    hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.')
  })
}

这里的真实 API 是 getMainWindowSync(),不是异步 getMainWindow()。技术文章和实际代码必须一致,因为两种写法的错误处理和回调时序不同。

窗口初始化被一个独立的 try/catch 包住。即使主窗口或监听建立失败,代码仍会继续执行 loadContent,首屏不必因为安全区信息读取失败而完全不可用。代价是安全区值回退到 0,因此真实设备测试还要观察是否出现内容贴边或被遮挡,并结合 warn 日志定位。

启动链路的职责结构

这种处理体现了“核心路径与增强信息分级”:首屏可加载属于核心路径,避让区精确值属于布局增强;增强失败可以降级,核心路径失败必须明确记录。

七、避让区回调只响应相关类型

系统可能在旋转、窗口尺寸改变、导航方式变化时触发 avoidAreaChange。句匠没有对所有变化无条件刷新,而是检查事件类型:

this.avoidAreaCallback = (data: window.AvoidAreaOptions) => {
  if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR ||
      data.type === window.AvoidAreaType.TYPE_SYSTEM) {
    this.updateNavigationIndicatorHeight()
  }
}

updateNavigationIndicatorHeight() 同时读取顶部系统区域和底部导航区域:

private updateNavigationIndicatorHeight(): void {
  if (!this.mainWindow) {
    AppStorage.setOrCreate<number>('topAvoidAreaHeightPx', 0)
    AppStorage.setOrCreate<number>('navigationIndicatorHeightPx', 0)
    return
  }

  try {
    const navigationArea = this.mainWindow.getWindowAvoidArea(
      window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR
    )
    const systemArea = this.mainWindow.getWindowAvoidArea(
      window.AvoidAreaType.TYPE_SYSTEM
    )

    const topHeight =
      systemArea.visible ? systemArea.topRect.height : 0
    const navigationHeight =
      navigationArea.visible ? navigationArea.bottomRect.height : 0
    const systemHeight =
      systemArea.visible ? systemArea.bottomRect.height : 0

    AppStorage.setOrCreate<number>('topAvoidAreaHeightPx', topHeight)
    AppStorage.setOrCreate<number>(
      'navigationIndicatorHeightPx',
      Math.max(navigationHeight, systemHeight)
    )
  } catch (err) {
    AppStorage.setOrCreate<number>('topAvoidAreaHeightPx', 0)
    AppStorage.setOrCreate<number>('navigationIndicatorHeightPx', 0)
    hilog.warn(DOMAIN, 'testTag', 'Failed to update avoid area.')
  }
}

几个细节容易被忽略。

其一,读取的是 px,入口层没有擅自按固定密度转换。页面持有 UIContext,在实际布局处使用 px2vp() 更合理。其二,底部高度取导航指示条和系统区域的最大值,避免只看一种类型时漏掉真实设备上的更大遮挡区域。其三,只有 visible 为真才使用矩形高度,防止不可见区域仍参与布局。

页面层最终通过 @StorageLink('topAvoidAreaHeightPx')@StorageLink('navigationIndicatorHeightPx') 消费结果。这样窗口 API 留在 Ability,ArkUI 页面只处理数值与布局,不必到处传递 Window

八、loadContent 的回调必须区分成功与失败

入口最后调用:

windowStage.loadContent('pages/SplashPage', (err) => {
  if (err.code) {
    hilog.error(
      DOMAIN,
      'testTag',
      'Failed to load the content. Cause: %{public}s',
      JSON.stringify(err)
    )
    return
  }
  hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.')
})

pages/SplashPage 必须与 main_pages.json 中的路径一致。大小写、目录或重命名不一致,都可能让 loadContent 失败。回调中先判断 err.code 并立即返回,避免失败后仍打印成功日志。

启动白屏排查可以沿着以下顺序缩小范围:

  1. 检查 module.json5 是否正确声明 EntryAbility
  2. 检查 main_pages.json 是否包含 pages/SplashPage
  3. 检查 SplashPage.ets 是否带有可作为页面入口的 @Entry
  4. 过滤 testTag 日志,确认是否到达 onWindowStageCreate
  5. 查看 Failed to load the content 的具体错误;
  6. 若加载成功,再检查 Splash 根容器背景、透明度和动画状态。

句匠的 Splash 初始透明度为 0,并在 aboutToAppear() 中通过 600ms 动画变为 1。因此“短时间看不到内容”还需要区分是页面加载失败,还是页面已加载但仍处于透明动画起点。日志和生命周期断点能帮助两者分离。

九、首屏之后的路由稳定性靠 replaceUrl 与定时器清理

虽然本文主角是 EntryAbility,但首屏是否正确离开决定了启动链路能否闭环。句匠的 SplashPage 在出现时建立动画和延迟跳转:

aboutToAppear(): void {
  animateTo({ duration: 600, curve: Curve.EaseOut }, () => {
    this.opacity_ = 1
    this.scale_ = 1
  })

  this.timerId = setTimeout(() => {
    router.replaceUrl({ url: 'pages/Index' })
  }, 2000)
}

aboutToDisappear(): void {
  if (this.timerId !== -1) {
    clearTimeout(this.timerId)
  }
}

replaceUrl 用主页替换当前启动页,用户从主页执行系统返回时不会重新看到 Splash。timerId 在页面消失时被清理,避免页面提前退出后延迟任务继续发起路由。

这段代码也限定了文章结论:当前跳转条件只是固定 2 秒,不包含登录判断、协议确认、远程开关或首次启动引导。若未来增加这些分支,应把“决定下一站”的规则提取为可测试的启动路由策略,同时确保同一次启动只能提交一次导航,避免定时器和异步任务同时跳转。

十、onWindowStageDestroy 与 onDestroy 形成两层清理

句匠在窗口舞台销毁和 Ability 销毁时都尝试注销避让区监听:

onWindowStageDestroy(): void {
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy')

  if (this.mainWindow && this.avoidAreaCallback) {
    this.mainWindow.off('avoidAreaChange', this.avoidAreaCallback)
  }
  this.mainWindow = undefined
  this.avoidAreaCallback = undefined
}

onDestroy(): void {
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy')

  if (this.mainWindow && this.avoidAreaCallback) {
    this.mainWindow.off('avoidAreaChange', this.avoidAreaCallback)
  }
  BreakpointSystem.unregister()
}

回调保存为字段,而不是注册时写一个无法引用的匿名函数,这让 off 可以传入同一个函数对象。onWindowStageDestroy() 清理窗口引用,onDestroy() 再释放应用级断点监听,职责与创建阶段对应。

创建动作清理动作对称性
BreakpointSystem.register()BreakpointSystem.unregister()Ability 级
mainWindow.on('avoidAreaChange', callback)mainWindow.off('avoidAreaChange', callback)Window 级
保存 mainWindow置为 undefined防止使用失效窗口
保存 avoidAreaCallback置为 undefined防止残留引用

这里的双重 off 有条件判断保护:onWindowStageDestroy() 执行后字段已被清空,后续 onDestroy() 不会再次调用。即使生命周期顺序或异常路径不同,也有更稳的兜底。

onForeground()onBackground() 当前只记录日志,没有在前后台切换时重复注册断点或重新装载页面。这避免了每次回前台都叠加监听。若未来确实需要刷新数据,应区分“首次初始化”和“回前台刷新”,不能直接重跑整个 onCreate()

十一、用故障矩阵验证,而不是只看一次启动成功

启动链路的测试不能止于“模拟器点开一次”。至少应覆盖冷启动、前后台、旋转或窗口缩放、异常数据和销毁重建。

场景预期结果重点证据
全新安装后冷启动Splash 正常出现,随后进入 IndexonCreateonWindowStageCreate、load success 日志
已有本地学习记录启动主页相关状态恢复UserDataManager.init 后的业务页面展示
系统深色模式启动项目仍按源码锁定浅色且文字可读启动窗口、Splash、主页截图
手机旋转或窗口缩放断点值和避让区重新计算currentBreakpoint、顶部/底部间距
前后台切换不重复装载 Splash,不叠加监听onForeground / onBackground 日志
销毁后重建无重复回调,无失效窗口访问destroy 日志与监听数量
Preferences 内容异常使用默认空数据,应用不闪退首屏可用、默认状态正确
页面路径写错的测试分支loadContent 明确报错错误码与失败日志

上架前还应在目标设备范围内完成安装、启动、主流程、退出、卸载烟测。module.json5 声明了 phone、tablet、2in1,不能只凭手机模拟器通过就推断全部设备形态稳定。窗口避让区和媒体查询尤其需要在可用的真实窗口尺寸上验证。

性能方面可记录三个时间点:进入 onCreate、进入 onWindowStageCreateloadContent 成功。若首屏慢,再细分 UserDataManager.init 与断点注册耗时。先测量,再优化;不要通过删除必要初始化换取表面上的启动速度。

十二、常见问题与定位方法

现象先检查可能原因修复方向
点击图标后白屏loadContent 回调页面路径未注册、首屏构建异常对齐 main_pages.json,查看首个错误
Splash 背景先闪色module.json5 启动窗口资源startWindow 与页面背景不一致统一两处视觉资源
主页底部被手势区遮挡避让区值与 px/vp 转换未监听、类型读取不全、页面未消费核对事件类型、最大值和 px2vp
旋转后布局不更新断点监听未注册或回调未写入 AppStorage检查三档 listener 与初始匹配
重建后同一事件触发多次destroy 日志与 listener注册后未 off / unregister保留回调引用并对称释放
本地数据损坏导致启动异常Preferences JSON解析异常未兜底恢复为空数组和默认设置
从主页返回又看到 Splash路由方法使用了 pushUrl启动页转主页使用 replaceUrl
页面消失后仍跳转Splash 定时器未在生命周期中清理保存 timerId 并在消失时 clear

排查启动问题的原则是抓第一处失败。日志最后一行不一定是根因;应从入口声明、onCreate、窗口创建、首屏装载、首屏生命周期依次确认。只要某一阶段没有到达,后面的页面现象就只是结果。

十三、可复用的启动链路检查清单

将句匠的实现迁移到其他 HarmonyOS 5.0+ ArkTS 项目时,可以按以下清单验收:

  • module.json5mainElement、Ability 名称和 srcEntry 一致;
  • 首屏和后续主页都已写入 main_pages.json
  • onCreate() 只初始化应用级依赖与共享状态;
  • 首屏必需数据在 loadContent 前已有真实值或安全默认值;
  • 窗口 API 只在 onWindowStageCreate() 之后调用;
  • 获取主窗口和监听避让区有异常降级;
  • loadContent 回调分别处理失败和成功;
  • px 值在页面布局边界转换为 vp;
  • 注册的窗口与媒体查询监听在销毁阶段释放;
  • Splash 延迟任务在页面消失时清理;
  • Splash 到主页使用符合返回栈预期的替换路由;
  • 冷启动、前后台、窗口变化、异常本地数据和重建均有验证记录。

这套方法的关键不是把所有工作塞进 EntryAbility,而是让每种初始化回到正确生命周期:应用状态归 onCreate,窗口能力归 onWindowStageCreate,页面动画归页面组件,监听释放归对应销毁回调。边界明确后,启动失败可以快速定位,窗口变化不会留下旧回调,首屏也不必承担平台级初始化。

句匠当前实现已经形成一条可复核的本地启动闭环:系统从模块声明找到 EntryAbility,入口恢复用户数据和共享状态,注册断点,窗口创建后同步避让区并装载 Splash,Splash 完成短动画后替换到主页,销毁时释放窗口与媒体查询监听。它解决的是稳定启动与基础适配,不应被包装成账号、云端或多设备流转能力。

AI 辅助声明

本文由 AI 辅助生成和整理,内容已根据 com.jiaweikang.one18 项目中的 EntryAbility.etsmodule.json5main_pages.jsonUserDataManager.etsBreakpointSystem.etsSplashPage.ets 逐项复核;能力描述仅覆盖源码中可验证的本地启动链路。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐