先检查启动后的内容是否正确

阅读应用启动更快之后,如果字号仍然停留在上一次的值,用户感受到的就是设置失效。适配准备 Demo 把字号保存到本机,强制停止进程,再次启动时重新读取;同时接入快启开关与回调标记,分别观察接口调用和实际启动路径。

字号从 20sp 改为 28sp 后,重新启动读回 28sp;快启开关的开启与关闭调用均返回 16000150,未观察到快启回调。字号持久化与普通重启回读已经通过,快启恢复和加速效果没有可用结果。

2026-09-19 重启后重新读到保存的字号

用独立工程隔离快启准备

本实验只保留一个阅读页、一个字号仓库和一个 AbilityStage,不加载三维模型、音频播放器或网络模块。模块导入阶段没有读写磁盘,仓库构造时也没有读取配置。需要访问外部状态的部分放在页面显示后的流程中,让每次进入都取得当前值。

HyperSnap 属于 AbilityKit,基础接口从 API 24 起提供。实验运行在 API 26 工程中,用它为系列应用做启动适配准备;这项基础接口不属于 API 26 首次新增,也不属于 FAST 算法工具。参考官方应用快启指南。

记录回调,不从耗时猜快照命中

AbilityStage 的普通创建回调先写入未观察标记,只有 onLaunchFromHyperSnap 才写入已观察标记。页面通过 StorageLink 显示该值。当前标记是进程内观察,不是持久化计数,也没有推断系统是否已经生成可用快照。

import { AbilityStage } from '@kit.AbilityKit';

export default class QuickStage extends AbilityStage {
  onCreate(): void {
    AppStorage.setOrCreate<boolean>('hyperSnapObserved', false);
  }
  onLaunchFromHyperSnap(): void {
    AppStorage.setOrCreate<boolean>('hyperSnapObserved', true);
  }
}

把“未观察到”显示出来比自动标成“冷启动”更准确:同一进程中的页面往返、前后台切换和实际的新进程启动属于不同路径。这个标记只回答当前进程是否走过指定回调,不测首帧时间,也不覆盖系统的其他启动路径。

工程在模块配置中注册这个 AbilityStage;没有注册时,页面代码即使显示一个布尔值,也不能证明系统会调用对应方法。现在的标记接入已编译,真实快启回调仍须在满足条件的环境中触发后检查,不能在验收脚本里直接把布尔值设为 true。

字号只从存储读取,不在模块顶层缓存

仓库允许 20 和 28 两个字号。存储没有值或含有不支持的值时,回到 20;写入不接受范围外的值。保存先 put,再 await flush,调用方等待成功后才更新页面的保存反馈。这里没有自动删除收藏、账号或其他业务数据,实验使用自己的存储名称。

import { common } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';

export class LaunchPreferences {
  private store: preferences.Preferences | undefined = undefined;
  async open(context: common.UIAbilityContext): Promise<void> {
    this.store = await preferences.getPreferences(context, { name: 'quick_start_lab' });
  }
  async readFont(): Promise<number> {
    if (!this.store) { throw new Error('存储未初始化'); }
    const value = await this.store.get('fontSize', 20);
    return value === 28 ? 28 : 20;
  }
  async saveFont(value: number): Promise<void> {
    if (!this.store) { throw new Error('存储未初始化'); }
    if (value !== 20 && value !== 28) { throw new Error('不支持的字号'); }
    await this.store.put('fontSize', value);
    await this.store.flush();
  }
}

仓库本身不持有字号缓存,因此新实例会再次从存储读取。不过 Preferences 对象可能有进程内缓存,不能把同一进程里连续 get 等同于物理磁盘重新加载。本次额外执行了强制停止进程和重新启动,验证的是新进程回读路径。

如果 flush 失败,调用方收到错误,不显示保存成功。put 已经改变的底层缓存是否回滚,不由这个仓库保证;遇到保存错误应提示用户,而不能通过静默读回缓存值把失败改写成成功。替身测试检查了错误向上传递,未模拟文件系统断电或存储损坏。

页面把读取、保存与开关结果分别显示

页面依赖前面的仓库与 AbilityKit,以下是完整组件体,省略工程内部相对导入路径。onPageShow 触发回读,ready 在读取完成后才允许修改。revision 拦截晚到的读取结果,避免离页后的旧读取覆盖当前状态;busy 用于防止同页连续保存。

@Entry
@Component
struct Index {
  @StorageLink('hyperSnapObserved') observed: boolean = false;
  @State savedFont: number = 20;
  @State ready: boolean = false;
  @State busy: boolean = false;
  @State feedback: string = '正在读取本机字号';
  @State switchResult: string = '尚未请求快启开关';
  private repo: LaunchPreferences = new LaunchPreferences();
  private active: boolean = true;
  private revision: number = 0;
  aboutToAppear(): void { this.active = true; }
  onPageShow(): void { this.reload(); }
  aboutToDisappear(): void { this.active = false; this.revision++; }
  private async reload(): Promise<void> {
    const revision = ++this.revision;
    this.ready = false;
    try {
      await this.repo.open(this.getUIContext().getHostContext() as common.UIAbilityContext);
      const value = await this.repo.readFont();
      if (!this.active || revision !== this.revision) { return; }
      this.savedFont = value; this.ready = true; this.feedback = `本次读取完成:${value}sp`;
    } catch (error) { if (this.active && revision === this.revision) { this.feedback = `读取失败:${(error as Error).message}`; } }
  }
  private async save(): Promise<void> {
    if (!this.ready || this.busy) { return; }
    this.busy = true;
    const value = this.savedFont === 20 ? 28 : 20;
    try {
      await this.repo.saveFont(value);
      if (this.active) { this.savedFont = value; this.feedback = `已持久化:${value}sp`; }
    } catch (error) { if (this.active) { this.feedback = `保存失败:${(error as Error).message}`; } }
    finally { if (this.active) { this.busy = false; } }
  }
  private configure(enabled: boolean): void {
    try {
      hyperSnapManager.setHyperSnapEnabled(enabled);
      this.switchResult = `setHyperSnapEnabled(${enabled}) 已返回`;
    } catch (error) { this.switchResult = `开关失败:${JSON.stringify(error)}`; }
  }
  build() {
    Column({ space: 18 }) {
      Text('18 · 快启与字号回读').fontSize(24).fontWeight(FontWeight.Bold)
      Text(this.observed ? '本进程观察到快启回调' : '本进程未观察到快启回调').fontSize(17).id('launchObserved')
      Text('开关调用返回,不代表系统生成或使用快照。').fontSize(16)
      Button('请求开启快启').id('enableHyper').onClick(() => { this.configure(true); })
      Button('请求关闭快启').id('disableHyper').onClick(() => { this.configure(false); })
      Text(this.switchResult).fontSize(16).id('hyperResult')
      Divider()
      Text('旋龟:其状如龟而鸟首虺尾').fontSize(this.savedFont).width('100%')
      Text(`当前字号:${this.savedFont}sp`).fontSize(17).id('fontValue')
      Button('切换字号并保存').id('saveFont').enabled(this.ready && !this.busy).onClick(() => { this.save(); })
      Button('重新读取存储').enabled(!this.busy).onClick(() => { this.reload(); })
      Text(this.feedback).fontSize(16).id('launchFeedback')
      Text('重启应用后核对字号;快启命中与耗时需要独立证据。').fontSize(16)
    }.padding(24).width('100%').height('100%').backgroundColor('#F3F0E8')
  }
}

快启开关的返回信息单独显示,字号保存信息也单独显示。即使快启调用失败,普通阅读设置仍可保存与回读。当前组件没有自动重建快照,也没有在每次显示页面时重复调用开关,避免让系统请求与普通业务读取相互干扰。

2026-09-19 开启快启调用返回实际错误

按实际执行记录验收

操作观察结果验证范围
首次进入当前字号 20sp,未观察到快启回调默认状态与读取完成反馈
点击切换并保存字号 28sp,显示已持久化页面更新和存储写入路径
强制停止后重新启动读回 28sp新进程持久化回读
请求开启快启16000150错误显示路径,未开启成功
请求关闭快启16000150错误显示路径,未确认系统开关状态

当前使用未签名 HAP 安装到 API 26 模拟器。官方指南对开发调试说明了 debug 签名条件,发布环境还有独立接入要求,所以当前错误不足以证明“模拟器缺少快启”。现有记录没有符合本应用的 debug 签名验证或系统侧详细原因日志;复验时需要同时确认签名条件和服务状态。

本地 SDK 将 16000150 描述为向系统服务发送请求失败。它不是“没有快照”的查询结果,也不是系统能力缺失的专用错误。因此页面保留原始错误,不把它翻译成“设备不支持”。请求失败后,同样不能宣称已经成功关闭或恢复了某个系统设置。

三类数据采用不同的恢复策略

数据类型示例恢复时的处理
固定内容分类常量、静态文案在无外部依赖的前提下复用
可变业务数据字号、收藏、下载状态从当前持久化状态重新读取
外部资源及授权网络连接、文件句柄、登录权限按当前环境重新确认有效性

这份 Demo 只验证第二类中的字号,不包括账号过期、数据库迁移或 GPU 句柄恢复。真正接入应用时,要沿启动入口检查所有模块顶层代码、静态初始化和 Native 构造函数,避免看似只创建一个对象,实际却在构造期间读取了外部状态。

当前回调标记与业务读取路径是两条独立证据:回调证明启动路径,数据回读证明业务状态。只有两条都取得实际证据,才可以进一步比较开启和关闭条件下的表现。字体回读通过不能推断快启回调已发生,调用开关成功也不能推断下次必定使用快照。

准备性能实验之前还缺什么

先在符合 debug 签名条件的包上确认开关调用与真实回调,再采集启动请求、首帧、可操作时刻和内容就绪时刻。用同一个版本、同一数据集、多次交错运行做对照,保留普通启动、实际快启和路径未知三组样本。

目前没有这些分组数据,无法计算毫秒级收益或百分比。系统生成和使用快照由自身策略决定,不能通过等待固定秒数或重启一次来保证命中。后续即使首帧提前,也仍需核对刚修改的字号、失效条目和新的外部入口,确保优化后的内容符合当前用户意图。

Logo

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

更多推荐