在这里插入图片描述

📖 引言

想象一下这个场景:

你在地铁上突然想查一个民族的信息,点开「民族图鉴」图标——“啪”,立刻就打开了,首页内容瞬间呈现在你眼前。没有启动页的等待,没有加载的转圈,几乎是秒开。

这就是应用快启的魅力——**点击图标,秒级响应,几乎无等待。

你可能会问:

  • 什么是应用快启?它和普通启动有什么区别?
  • 快启的原理是什么?为什么能做到秒开?
  • 预加载、进程保活、热启动优化,这些都是什么意思?
  • 冷启动、温启动、热启动,三者怎么优化?
  • 鸿蒙7 提供了哪些系统级的快启能力?
  • 「民族图鉴」可以从哪些方面做快启优化?
  • 快启会不会占很多内存?内存和速度怎么平衡?
  • 为什么有时候快启又失效了?有什么限制?

这些问题非常关键。启动速度是用户对 App 的第一印象,也是体验的第一道门槛。根据 Google 的研究表明,启动时间每增加 1 秒,用户流失率就增加 7%。可以说,启动速度直接影响用户留存。

在第 62 篇我们已经讲过基础的启动优化。但鸿蒙7 带来了更强大的系统级快启能力,能让启动速度再上一个台阶。

本文将带你深入理解应用快启的技术原理,系统学习鸿蒙7 的快启新能力,掌握预启动、进程保活、页面缓存等进阶优化手段,并以「民族图鉴」项目为例,完成快启优化实战。


🎯 学习目标

完成本文后,你将能够:

  • ✅ 深入理解应用快启的技术原理
  • ✅ 掌握冷启动、温启动、热启动的优化策略
  • ✅ 学会预加载、进程保活、状态保存与恢复
  • ✅ 掌握鸿蒙系统级快启能力的使用
  • ✅ 理解快启与内存的平衡之道
  • ✅ 了解快启的限制与系统策略
  • ✅ 能够独立完成「民族图鉴」的快启优化
  • ✅ 解决快启失效、数据不一致、内存占用高等常见问题

💡 需求分析

启动优化的"不可能三角

在讲快启之前,我们先回顾一下启动优化的核心矛盾。

启动优化有三个核心指标,它们构成了一个"不可能三角":

         启动速度
           ╱ ╲
          ╱   ╲
         ╱     ╲
        ╱       ╲
       ╱         ╲
  内存占用 ─── 功能完整度
  • 启动速度:越快越好
  • 内存占用:越少越好
  • 功能完整度:越全越好

这三者很难同时做到最优——你想启动快,就要少加载东西,功能就不全;你想功能全,就要多加载,启动就慢、占内存就多。

快启技术,就是在这三者之间寻找最佳平衡点。鸿蒙7 的快启新能力,让这个平衡点更优了。


快启的三种形态:冷/温/热

在第 62 篇我们讲过启动的三种形态。这里我们再复习一下,并从快启的角度重新审视。

启动类型 说明 典型场景 耗时 优化空间
冷启动 进程不存在,从头开始 第一次打开、杀掉后重开 2-5秒 最大
温启动 进程还在,但页面要重建 按 Home 键退出后再打开 1-2秒
热启动 进程和页面都在 切换到后台再回来 <1秒 最小

快启优化的目标

  • 冷启动 → 尽量做到"像温启动一样快"
  • 温启动 → 尽量做到"像热启动一样快"
  • 热启动 → 做到"瞬间恢复

💡 终极目标:不管什么启动,用户都感觉"秒开"。


「民族图鉴」启动现状分析

让我们先分析一下「民族图鉴」当前的启动情况。

当前启动流程
用户点击图标
    ↓
进程创建(系统)
    ↓
Application/Ability 初始化
    ├─ StorageService 初始化
    ├─ ThemeService 初始化
    ├─ I18nService 初始化
    └─ TTSEngineService 初始化
    ↓
加载 SplashPage(启动页)
    ├─ 显示 Logo 动画
    └─ 固定等待 2.2 秒
    ↓
跳转到首页 Index
    ├─ 加载 5 个 Tab 页面
    ├─ 加载首页数据
    └─ 渲染页面
    ↓
用户看到内容
当前启动耗时估算
阶段 耗时 说明
进程创建 + Ability初始化 ~300ms 系统部分,优化空间有限
服务初始化 ~200ms 4个服务同步初始化
SplashPage 展示 ~2200ms 固定等待 2.2 秒,占比最大
首页渲染 ~300ms 页面加载 + 数据加载 + 渲染
总计 ~3000ms 3 秒左右

3 秒的启动时间,说快不快,说慢也不算特别慢。但还有很大的优化空间——光是启动页那 2.2 秒,就很有问题。

快启优化后的目标
启动类型 当前 目标
冷启动 ~3000ms <1500ms
温启动 ~1500ms <500ms
热启动 <500ms <200ms

目标:**冷启动减半,温启动三分之一,热启动瞬时。


🛠️ 核心实现

步骤1:冷启动优化——布局预加载与资源预解析

1.1 冷启动为什么慢?

冷启动是最慢的,因为要从零开始:

  • 创建进程
  • 初始化运行时
  • 加载代码
  • 初始化服务
  • 加载布局
  • 解析资源
  • 渲染首屏

每一步都需要时间。我们能优化的,主要是应用层能控制的部分。

1.2 启动页极简:只显示最必要的内容

启动页(SplashPage)是用户看到的第一屏。它的设计原则应该是:

启动页三原则

  1. 越简单越好:只放 Logo 和品牌,不要放复杂内容
  2. 越快越好:尽早跳转到首页,不要让用户等
  3. 有用才等:如果要加载数据,在启动页加载,不要让用户白等

「民族图鉴」当前启动页最大的问题是:固定等待 2.2 秒

不管数据加载完了,也要等够 2.2 秒。这完全是浪费时间。

优化方案

  • 启动页动画继续播,但数据加载完、首页准备好就立刻跳转
  • 动画时长 = max(动画时长, 最短展示时长)
  • 最短展示时长 500ms(别太快了品牌没看清),最长 1.5 秒
// pages/SplashPage.ets(优化版)

@Entry
@Component
struct SplashPage {
  @State logoScale: number = 0.5;
  @State logoOpacity: number = 0;
  @State isDataReady: boolean = false;
  @State minTimePassed: boolean = false;
  private startTime: number = 0;
  private minDuration: number = 500;   // 最短展示时间
  private maxDuration: number = 1500;  // 最长展示时间

  aboutToAppear(): void {
    this.startTime = Date.now();
    this.startAnimation();
    this.preloadData();
    this.startMinTimer();
    this.startMaxTimer();
  }

  /**
   * 启动页动画
   */
  private startAnimation(): void {
    animateTo({
      duration: 600,
      curve: Curve.EaseOut
    }, () => {
      this.logoScale = 1.0;
      this.logoOpacity = 1;
    });
  }

  /**
   * 预加载首页数据
   */
  private async preloadData(): Promise<void> {
    try {
      // 并行预加载各种数据
      await Promise.all([
        this.preloadEthnicList(),    // 预加载民族列表
        this.preloadUserProfile(),  // 预加载用户信息
        this.preloadFavorites()   // 预加载收藏数据
      ]);
      this.isDataReady = true;
      this.tryNavigateToHome();
    } catch (e) {
      console.error('预加载失败:', JSON.stringify(e));
      this.isDataReady = true;  // 就算失败了也跳,不能卡在启动页
      this.tryNavigateToHome();
    }
  }

  /**
   * 最短时间定时器
   */
  private startMinTimer(): void {
    setTimeout(() => {
      this.minTimePassed = true;
      this.tryNavigateToHome();
    }, this.minDuration);
  }

  /**
   * 最长时间定时器(兜底,最多等 1.5 秒必须跳
   */
  private startMaxTimer(): void {
    setTimeout(() => {
      this.navigateToHome();
    }, this.maxDuration);
  }

  /**
   * 尝试跳转首页(两个条件都满足才跳)
   */
  private tryNavigateToHome(): void {
    if (this.isDataReady && this.minTimePassed) {
      this.navigateToHome();
    }
  }

  /**
   * 跳转首页
   */
  private navigateToHome(): void {
    router.replaceUrl({ url: 'pages/Index' }).catch((err: Error) => {
      console.error('跳转失败:', JSON.stringify(err));
    });
  }

  // 预加载方法...

  build() {
    Column() {
      // Logo...
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

优化效果

  • 快的话(数据加载快):500ms 就跳转
  • 慢的话(数据加载慢):最多 1.5 秒
  • 比原来的 2.2 秒固定等待,至少快了 700ms

💡 启动页的本质:启动页不是"让用户等",而是"利用用户等的时间做准备工作"。如果准备工作做完了,就别让用户继续等了。

1.3 首页预渲染:在启动页就准备首页

更进一步,我们能不能在启动页的时候,就把首页提前渲染好?跳转的时候直接切过去,连渲染的时间都省了?

答案是可以的——这就是预渲染技术。

思路

  • 启动页展示的时候,后台默默在后台加载并渲染首页
  • 启动页动画结束,直接把首页切到前台
  • 用户感觉:"啪"一下就打开了,没有切换的感觉
// 预渲染的大致思路(伪代码)
// 用 Stack 布局,启动页在上,首页在下
// 启动页动画结束,把启动页隐藏掉,首页就露出来了
// 连路由跳转都省了

不过这种方式实现起来比较复杂,而且有一些限制。对于「民族图鉴」这样的应用,先做好启动页优化 + 预加载数据就够用了。

1.4 资源预解析:让资源加载更快

冷启动的时候,很多资源是第一次加载,需要解析、解码。这也需要时间。

资源预解析的优化手段

优化手段 说明
图片格式优化 WebP 格式,更小更快
资源混淆 减小包体积,加载更快
启动页用本地资源 不要用网络图片
字体预加载 常用字体提前加载

这些我们在第 67 篇包体积优化里都讲过,这里就不重复了。

1.5 服务初始化优化:能延迟的就延迟

冷启动时,不要把所有服务都初始化了。能延迟的就延迟,能异步的就异步。

「民族图鉴」的服务初始化优化:

服务 是否必须启动时初始化 优化方案
StorageService ✅ 是 必须第一个初始化
ThemeService ✅ 是 主题影响 UI 展示,必须先初始化
I18nService ✅ 是 语言影响文案,必须先初始化
TTSEngineService ❌ 否 延迟到第一次使用 TTS 时再初始化
MusicService ❌ 否 延迟到进入音乐页再初始化
AIService ❌ 否 延迟到进入 AI 聊天页再初始化
ApiService ⚠️ 部分 基础配置初始化,网络请求懒加载

优化前:4 个服务同步初始化,约 200ms
优化后:3 个必须服务初始化,约 100ms

省了 100ms,而且 TTS、音乐、AI 这些服务,用户可能根本用不上,完全没必要启动时就加载。


步骤2:温启动优化——进程保活与状态保存

2.1 什么是温启动?

温启动是指:

  • 用户按了 Home 键,应用退到后台
  • 进程还在(没被杀)
  • 过一会儿用户又点开应用
  • 这时候不需要创建进程,但页面可能被系统回收了,需要重建

温启动比冷启动快,因为进程还在,但比热启动慢,因为页面要重建。

2.2 进程保活:让进程尽量不被杀

要让温启动尽量多、冷启动尽量少,核心就是:让应用进程尽量留在后台,不被系统杀掉

但注意:保活不是"永生"——系统内存不够的时候,该杀还是会杀。我们能做的是:降低被杀的概率

影响进程被杀概率的因素

因素 说明 我们能控制吗?
内存占用 占内存越多,越容易被杀 ✅ 能,减少内存占用
前台服务 有前台服务的进程优先级高 ⚠️ 能,但不要滥用
使用频率 用户经常用的,系统会保留 ❌ 不能,靠产品
系统可用内存 内存充足时杀得少 ❌ 不能
厂商策略 不同厂商杀后台策略不一样 ❌ 不能

「民族图鉴」能做的

  1. 减少后台内存占用(这个最重要)
  2. 合理使用前台服务(比如播放音乐的时候)
  3. 不要为了保活而保活(体验差了,用户会主动杀

💡 正确的心态:保活是"尽量延长后台存活时间,不是"永不被杀"。只要被杀是正常的,系统要做的是被杀后能快速恢复。

2.3 状态保存:被杀了也能快速恢复

进程被杀了没关系,只要状态保存好了,恢复起来也很快。

**什么状态需要保存?

  • 用户正在看的页面
  • 页面的滚动位置
  • 用户输入的内容
  • 列表的筛选条件
  • 等等

怎么保存?

  • 轻量状态 → Preferences
  • 大量数据 → 数据库
  • 页面实例 → 页面自己管理

「民族图鉴」的状态保存策略:

状态 保存方式 恢复时机
当前 Tab 选中哪个 Tab Preferences 启动时恢复
详情页看到哪个民族 Preferences 进入详情页时恢复
列表滚动位置 页面内保存 页面重建时恢复
搜索关键词 Preferences 进入搜索页时恢复
用户设置 Preferences 启动时恢复
// services/StateRestoreService.ets

/**
 * 状态恢复服务
 * 保存和恢复应用状态,让进程被杀后能快速恢复
 */
export class StateRestoreService {
  private static instance: StateRestoreService;

  private constructor() {}

  public static getInstance(): StateRestoreService {
    if (!StateRestoreService.instance) {
      StateRestoreService.instance = new StateRestoreService();
    }
    return StateRestoreService.instance;
  }

  // ========== 当前 Tab ==========

  async saveCurrentTab(index: number): Promise<void> {
    await StorageService.getInstance().saveNumber('current_tab', index);
  }

  async getCurrentTab(): Promise<number> {
    return await StorageService.getInstance().getNumber('current_tab', 0);
  }

  // ========== 最后浏览的民族 ==========

  async saveLastViewedEthnic(ethnicId: string): Promise<void> {
    await StorageService.getInstance().saveString('last_viewed_ethnic', ethnicId);
  }

  async getLastViewedEthnic(): Promise<string> {
    return await StorageService.getInstance().getString('last_viewed_ethnic', '');
  }

  // ========== 列表滚动位置 ==========

  async saveListScrollPosition(page: string, offset: number): Promise<void> {
    await StorageService.getInstance().saveNumber(`scroll_${page}`, offset);
  }

  async getListScrollPosition(page: string): Promise<number> {
    return await StorageService.getInstance().getNumber(`scroll_${page}`, 0);
  }

  // ========== 搜索历史 ==========

  async saveSearchHistory(history: string[]): Promise<void> {
    await StorageService.getInstance().saveObject('search_history', history);
  }

  async getSearchHistory(): Promise<string[]> {
    return await StorageService.getInstance().getObject<string[]>('search_history', []) || [];
  }
}

有了状态保存,就算进程被杀了,用户再打开的时候:

  1. 恢复到上次的 Tab
  2. 恢复列表滚动位置
  3. 恢复用户设置

用户感觉:“好像从没杀过一样”。

💡 温启动优化的核心:进程活着就快,死了也能快速恢复。两手准备,总有一款适合你。


步骤3:热启动优化——页面缓存与快速恢复

3.1 什么是热启动?

热启动是最快的:

  • 进程在
  • 页面也在
  • 用户切到后台再切回来
  • 几乎瞬间恢复

但热启动也有可以优化的地方吗?有!

3.2 页面缓存:浏览过的页面保留实例

用户浏览过的页面,如果保留实例,下次再进就不用重新创建了。

比如:

  • 用户从首页 → 民族列表 → 民族详情
  • 然后返回 → 返回
  • 如果页面都保留着,返回的时候直接显示,不用重新加载

这个 ArkUI 的路由栈本身就做了——页面 push 的时候,旧页面不会销毁,pop 的时候直接显示。

但有些场景需要注意:

  • 页面太多了,内存不够怎么办?
  • 页面数据过期了怎么办?

**页面缓存策略:

策略 说明
**路由栈内页面 自动保留,pop 时直接显示
**Tab 页面 一直保留(因为 Tab 切换不销毁
**频繁访问的页面 可以考虑预创建(提前创建好
3.3 数据刷新策略:什么时候刷新?

页面保留了,但数据可能过期了。什么时候刷新?

**刷新策略:

策略 适用场景 优点 缺点
**每次进入都刷新 对实时性要求高 数据总是最新 费流量、费电、慢
**进入时检查,有更新才刷新 大部分场景 平衡 实现稍复杂
**定时刷新 变化不频繁 省流量 可能不及时
**下拉手动刷新 用户主动 可控 用户要手动

「民族图鉴」的策略:

  • 民族基础数据 → 版本号检查,有新版本才更新(民族数据不常变
  • 收藏、历史记录 → 每次进入都刷新(数据量小,刷新快)
  • 用户设置 → 实时同步(用云数据库实时同步)
3.4 后台数据预加载:让热启动更快

用户还没打开页面,我们能不能提前把数据加载好?

比如:

  • 用户在首页,就预加载列表页的数据
  • 用户在列表页,就预加载详情页的数据
  • 用户点进去的时候,数据已经准备好了

预加载的时机

用户停在首页 2 秒以上 → 预加载列表页首屏数据
用户在列表页停留 → 预加载前后几条详情数据

预加载的好处

  • 用户点进去立刻看到内容,感觉"秒开"
  • 网络不好的时候尤其明显

预加载的注意事项

  • 不要预加载太多,费流量费内存
  • 只预加载用户大概率会看的
  • WiFi 下多预加载,移动数据下少预加载
  • 可以让用户选择开不开
// services/PreloadService.ets

/**
 * 预加载服务
 * 提前加载用户可能会看的内容
 */
export class PreloadService {
  private static instance: PreloadService;
  private preloadCache: Map<string, any> = new Map();
  private maxCacheSize: number = 20;  // 最多缓存 20 条

  private constructor() {}

  public static getInstance(): PreloadService {
    if (!PreloadService.instance) {
      PreloadService.instance = new PreloadService();
    }
    return PreloadService.instance;
  }

  /**
   * 预加载民族详情
   */
  async preloadEthnicDetail(ethnicId: string): Promise<void> {
    // 已经缓存了就不重复加载
    if (this.preloadCache.has(ethnicId)) {
      return;
    }

    try {
      // 加载详情数据
      const mockData = EthnicMockData.getInstance();
      const detail = mockData.getEthnicById(ethnicId);

      if (detail) {
        this.putCache(ethnicId, detail);
        console.log('[Preload] 预加载完成:', ethnicId);
      }
    } catch (e) {
      console.error('[Preload] 预加载失败:', ethnicId, JSON.stringify(e));
    }
  }

  /**
   * 获取预加载的数据
   */
  getPreloadedEthnic(ethnicId: string): EthnicGroup | undefined {
    return this.preloadCache.get(ethnicId);
  }

  /**
   * 批量预加载列表中的前几个
   */
  async preloadList(list: EthnicGroup[], count: number = 5): Promise<void> {
    const toPreload = list.slice(0, count);
    for (const ethnic of toPreload) {
      await this.preloadEthnicDetail(ethnic.id);
    }
  }

  /**
   * 放入缓存(控制大小)
   */
  private putCache(key: string, value: any): void {
    if (this.preloadCache.size >= this.maxCacheSize) {
      // 缓存满了,删最早的(简单的 LRU 简化版)
      const firstKey = this.preloadCache.keys().next().value;
      if (firstKey) {
        this.preloadCache.delete(firstKey);
      }
    }
    this.preloadCache.set(key, value);
  }

  /**
   * 清空缓存
   */
  clearCache(): void {
    this.preloadCache.clear();
  }
}

在列表页中使用:

// pages/EthnicListPage.ets

aboutToAppear(): void {
  // 页面加载完,预加载前 5 条详情
  if (this.ethnicList.length > 0) {
    PreloadService.getInstance().preloadList(this.ethnicList, 5);
  }
}

// 点击进入详情页时,先看缓存有没有
private goToDetail(ethnic: EthnicGroup): void {
  const preloaded = PreloadService.getInstance().getPreloadedEthnic(ethnic.id);
  if (preloaded) {
    // 有缓存,立刻跳转(数据已经有了)
    router.pushUrl({
      url: 'pages/EthnicDetailPage',
      params: { ethnicId: ethnic.id, preloaded: true }
    });
  } else {
    // 没缓存,正常跳转
    router.pushUrl({
      url: 'pages/EthnicDetailPage',
      params: { ethnicId: ethnic.id }
    });
  }
}

详情页拿到预加载数据,直接显示,不用再加载了。

💡 预加载的艺术:预加载是"猜测用户下一步会看什么,提前准备好"。猜得准,用户体验飙升;猜不准,浪费流量和内存。所以预加载策略很重要——只预加载高概率的内容。


步骤4:系统级快启能力——快启框架与预启动调度

4.1 鸿蒙7 的系统级快启

前面讲的都是应用层能做的优化。鸿蒙7 还提供了系统级的快启能力,让应用启动更快。

系统级快启主要包括:

能力 说明
快启框架 系统级的应用快速启动框架
预启动调度 系统根据用户使用习惯,提前预启动应用
热启动优化 系统层面的热启动加速
资源预加载 系统提前加载常用资源
4.2 预启动调度:系统帮你提前启动

鸿蒙7 有一个很厉害的能力:预启动调度

什么意思呢?

  • 系统会学习用户的使用习惯
  • 比如用户每天早上 8 点都会刷「民族图鉴」
  • 系统学到了,就会在 7:55 左右提前把「民族图鉴」的进程启动好
  • 用户 8 点点开的时候,已经是温启动甚至热启动了
  • 秒开!

**预启动调度是系统自动的吗?大部分是。但应用可以做一些配合:

应用配合预启动的最佳实践

  1. 正确实现生命周期

    • 该存的状态存好
    • 该释放的资源释放
    • 让系统知道你是什么"脾气"好预测
  2. 合理的进程分类

    • 告诉系统哪些服务是重要的
    • 让系统调度的时候心里有数
  3. 不要滥用后台

    • 你要是在后台偷偷跑,系统会"惩罚"你
    • 老实的应用,系统才愿意给你预启动

💡 **预启动就像系统给优质应用的"奖励"——表现好的应用,用户常用的应用,系统就帮你提前启动,用户体验更好;表现不好的应用,系统就不鸟你。

4.3 快启框架:开发者能做什么?

作为开发者,我们能做的主要是:

1. 适配快启框架配置

在 module.json5 中配置快启相关的信息,让系统更好地了解你的应用:

{
  "module": {
    // ...
    "abilities": [
      {
        "name": "EntryAbility",
        // 快启相关配置
        "fastStart": {
          "enabled": true,        // 开启快启
          "preloadResources": [  // 预加载的资源
            "pages/Index",
            "pages/EthnicListPage"
          ]
        }
      }
    ]
  }
}

(注:以上为示意配置,实际以官方文档为准)

2. 优化冷启动各个阶段的耗时
系统要预启动,也需要应用本身启动快。应用本身启动慢,系统再怎么帮也有限。

3. 正确处理 onNewWant

  • 应用已经启动了,用户又点了图标
  • 这时候走 onNewWant 而不是重新创建
  • 要正确处理,不要重复初始化
// entryability/EntryAbility.ets

onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // 应用已经在运行了,又收到新的启动请求
  // 这时候不要重新初始化,直接切换到对应的页面就行
  console.log('onNewWant:', JSON.stringify(want));
  // 根据 want 里的参数跳转到对应页面
}
4.4 「民族图鉴」的系统级快启适配

「民族图鉴」适配系统快启要做的事:

  1. ✅ 开启快启框架支持
  2. ✅ 正确配置预加载页面
  3. ✅ 优化冷启动速度(前面讲的那些)
  4. ✅ 正确实现生命周期
  5. ✅ 实现状态保存与恢复
  6. ✅ 减少后台内存占用

做好这些,系统就会"照顾"你的应用,给你更多预启动的机会,用户体验就更好了。

💡 系统级快启的心态:这是系统给的"福利",不是你能完全控制的。你能做的是"做好自己该做的,让系统愿意给你福利。就像好学生更容易拿到奖学金一样——你不能强迫系统给你,但你可以让自己值得被给。


步骤5:内存与快启的平衡——快启占内存,要平衡

5.1 快启和内存的矛盾

快启很爽,但快启是有代价的——占内存

  • 进程保活 → 进程占内存
  • 页面缓存 → 页面占内存
  • 预加载数据 → 数据占内存

内存占多了 → 系统更容易杀你 → 快启反而失效了

这是一个悖论:**越想快启,越占内存;越占内存,越容易被杀,反而快启不了。

所以,快启和内存要平衡。不能为了快启把内存撑爆了。

5.2 内存占用的几个档次

我们可以把内存占用分为几个档次:

档次 内存占用 被杀概率 快启效果
轻量 <100MB 好,不容易被杀
中等 100-200MB 一般,看系统心情
重度 >200MB 差,很容易被杀

对于「民族图鉴」这样的应用,目标是:控制在 150MB 以内

5.3 平衡策略:该放就放

平衡的核心是:重要的留着,不重要的放掉

什么该留?

  • 当前页面
  • 用户常用的页面
  • 关键服务

什么该放?

  • 不常用的页面
  • 大的图片缓存
  • 后台不需要的服务

具体策略

策略 说明
页面缓存上限 最多保留 N 个页面,多了就销毁最早的
图片缓存上限 图片缓存控制大小,超过了就清最早的
后台降级 退到后台后,释放一些非必要资源
内存告警时释放 收到系统内存告警,主动释放
5.4 后台内存优化

应用退到后台后,可以主动释放一些东西,减少被杀的概率。

后台释放策略

退到后台
    ↓
等 30 秒(防止用户马上切回来
    ↓
释放非必要资源
    ├─ 释放大图缓存(反正后台也看不到
    ├─ 释放不常用的页面实例
    ├─ 暂停后台定时器
    └─ 关闭不必要的服务
    ↓
内存占用降下来
    ↓
被杀概率降低
// entryability/EntryAbility.ets

onBackground(): void {
  console.log('应用退到后台');
  // 30 秒后执行后台优化
  setTimeout(() => {
    this.doBackgroundOptimization();
  }, 30000);
}

onForeground(): void {
  console.log('应用回到前台');
  // 恢复资源(如果需要的话)
  this.restoreFromBackground();
}

/**
 * 后台优化:释放非必要资源
 */
private doBackgroundOptimization(): void {
  console.log('执行后台内存优化');
  // 1. 清空图片缓存(只留一点,不要全清了回来还要重新加载慢)
  // 2. 释放不常用的页面缓存
  // 3. 暂停后台定时器
  // 4. 关闭非必要服务
}

/**
 * 从前台恢复
 */
private restoreFromBackground(): void {
  console.log('从前台恢复');
  // 恢复需要的资源
}
5.5 系统内存告警

系统内存不够的时候,会给应用发告警。收到告警,主动释放一些内存,可以降低被杀的概率。

// 监听内存告警
// 系统回调里主动释放

// 内存告警等级:
// - 轻度:清理一些缓存就行
// - 中度:多释放一些
// - 重度:能放的都放了,保命要紧

💡 平衡的艺术:快启和内存的平衡,就像做菜放盐——放少了没味道,放多了又咸。刚刚好的那个点,需要根据实际情况调整。不同的应用、不同的用户群体,平衡点都不一样。


步骤6:实战——「民族图鉴」快启优化全流程

讲了这么多理论,现在让我们来一个完整的实战——把「民族图鉴」的启动速度优化到极致。

6.1 优化路线图

我们分三个阶段来做:

第一阶段:基础优化(必做,ROI 最高)

  • 启动页优化(固定等待 → 按需等待)
  • 服务懒加载(非必须的服务延迟初始化)
  • 状态保存与恢复

第二阶段:进阶优化(推荐做)

  • 预加载数据
  • 页面缓存
  • 后台内存优化

第三阶段:系统级优化(可选)

  • 适配系统快启框架
  • 配合预启动调度
6.2 第一阶段:基础优化

优化1:启动页优化

把固定 2.2 秒等待改成:

  • 最短 500ms(品牌展示
  • 最长 1500ms(兜底)
  • 数据加载完 + 最短时间到了 → 立刻跳转

代码前面已经写过了,这里就不重复了。

预计收益:冷启动快 700ms 以上**(从 2.2s 到最多 1.5s,快的话 500ms)

优化2:服务懒加载

把 TTS、音乐、AI 这些服务,从启动时初始化改成第一次使用时再初始化。

// services/TTSEngineService.ets

export class TTSEngineService {
  private static instance: TTSEngineService;
  private isInitialized: boolean = false;

  // 单例,但不自动初始化

  public static getInstance(): TTSEngineService {
    if (!TTSEngineService.instance) {
      TTSEngineService.instance = new TTSEngineService();
    }
    return TTSEngineService.instance;
  }

  /**
   * 确保初始化(第一次使用时调用)
   */
  private async ensureInit(): Promise<void> {
    if (this.isInitialized) return;
    // 初始化 TTS 引擎...
    this.isInitialized = true;
  }

  /**
   * 播放 TTS
   */
  async speak(text: string): Promise<void> {
    await this.ensureInit();  // 第一次使用时才初始化
    // 播放...
  }
}

预计收益:启动快 50-100ms

优化3:状态保存与恢复

保存当前 Tab、滚动位置等,让温启动/冷启动后能快速恢复。

// pages/Index.ets

aboutToAppear(): void {
  // 恢复上次的 Tab
  this.restoreCurrentTab();
}

aboutToDisappear(): void {
  // 保存当前 Tab
  this.saveCurrentTab();
}

private async restoreCurrentTab(): Promise<void> {
  const tab = await StateRestoreService.getInstance().getCurrentTab();
  this.currentIndex = tab;
}

private saveCurrentTab(): void {
  StateRestoreService.getInstance().saveCurrentTab(this.currentIndex);
}

预计收益:温启动/冷启动后恢复状态,用户体验提升

6.3 第二阶段:进阶优化

优化4:列表详情预加载

列表页加载完,预加载前 5 条详情数据,用户点进去立刻显示。

代码前面 PreloadService 已经写过了。

预计收益:详情页"秒开"感,用户感知强

优化5:后台内存优化

退到后台 30 秒后,释放图片缓存等非必要资源。

// entryability/EntryAbility.ets

private backgroundTimer: number = -1;

onBackground(): void {
  if (this.backgroundTimer !== -1) {
    clearTimeout(this.backgroundTimer);
  }
  this.backgroundTimer = setTimeout(() => {
    this.doBackgroundOptimize();
  }, 30000); // 30 秒后优化
}

onForeground(): void {
  if (this.backgroundTimer !== -1) {
    clearTimeout(this.backgroundTimer);
    this.backgroundTimer = -1;
  }
}

private doBackgroundOptimize(): void {
  console.log('[Memory] 后台优化,释放非必要资源');
  // 1. 清空大图缓存
  // 2. 释放预加载缓存(保留少量)
  // ...
}

预计收益:后台内存占用减少 20-30%,被杀概率降低

6.4 第三阶段:系统级优化

优化6:适配快启框架

在配置文件中开启快启支持,配置预加载页面。

// module.json5(示意)
{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "fastStart": {
          "enabled": true
        }
      }
    ]
  }
}

优化7:正确实现 onNewWant

onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  console.log('onNewWant');
  // 处理新的启动请求
  // 比如从桌面小工具点击,跳转到对应页面
}

预计收益:系统预启动概率提升,冷启动减少,热启动增多

6.5 优化前后对比
指标 优化前 优化后 提升
冷启动时间 ~3000ms <1500ms 快了 50%
温启动时间 ~1500ms <500ms 快了 67%
热启动时间 <500ms <200ms 快了 60%
后台内存占用 ~180MB ~130MB 省了 28%
进程留存率 中等 较高 提升明显

怎么样,效果还不错吧?


⚠️ 常见问题与解决方案

问题1:快启有时候快有时候慢,不稳定?

现象
有时候点开 App 秒开,有时候又要等很久。为什么不稳定?

原因分析
快启效果取决于很多因素,不是每次都一样:

因素 影响
进程在不在 在就快,不在就慢
页面在不在 在就快,不在就慢
系统内存够不够 够就留着,不够就杀
上次用是什么时候 刚用过的留着,很久没用的杀
厂商杀后台策略 有的厂商严,有的松

解决方案

1. 接受现实,做好各种情况
快启是"尽力而为"的,不是 100% 能保证的。

  • 有快启的时候体验好
  • 没快启的时候也不能太差
  • 做好冷启动优化,保底体验

2. 提高进程留存率

  • 减少后台内存占用
  • 不要后台跑东西
  • 合规使用前台服务(需要的时候)
  • 做好状态保存,杀了也能快速恢复

3. 给用户一致的体验

  • 不管是冷启动还是热启动,用户看到的界面要一致
  • 不要热启动一个样,冷启动又一个样
  • 状态保存 + 快速恢复,让用户感觉不到差别

💡 正确的心态:快启是"加分项",不是"必选项"。有了更好,没有也不能用不了。把基础体验做好(冷启动也别太慢),快启是锦上添花。


问题2:快启导致数据不一致?

现象
用了页面缓存、预加载这些技术后,有时候数据不是最新的。

原因分析

  • 页面缓存了 → 数据可能过期了
  • 预加载了 → 数据可能不是最新的
  • 进程被杀了又恢复 → 数据可能是旧的

解决方案

1. 合适的刷新策略

  • 进入页面时检查数据版本
  • 有更新就刷新,没更新就用缓存
  • 重要数据实时刷新,不重要的可以缓存

2. 进入页面时静默刷新

  • 先显示缓存数据(立刻有内容看)
  • 后台静默刷新
  • 刷新完了更新 UI(用户可能还没看完,已经更新好了

3. 下拉刷新

  • 给用户手动刷新的选项
  • 用户想刷新的时候自己刷

4. 实时同步

  • 用云数据库的实时同步(上一篇讲的)
  • 数据变了自动推送
  • 永远是最新的

💡 数据一致性和速度的权衡:又要快又要新,是不可能的。要么快但可能不是最新,要么最新但要等。找到平衡点就好——大多数场景,先显示旧的,后台刷新,有新的再更新。用户看到内容先睹为快,数据也会及时更新,两全其美。


问题3:内存占用太高,怎么办?

现象
为了快启,搞了很多缓存、很多预加载,结果内存占用老高,更容易被杀了。

原因分析
过犹不及——快启搞过头了,内存爆了,反而适得其反。

解决方案

1. 做内存监控

  • 知道自己占了多少内存
  • 哪些地方占得多
  • 用 Profiler 测

2. 设置缓存上限

  • 图片缓存:最多 N 张,或者最多 M MB
  • 页面缓存:最多保留 N 个页面
  • 预加载数据:最多 N 条
  • 超过了就清最早的(LRU)

3. 分级释放

内存正常 → 全都留着
内存偏高 → 清一部分缓存
内存告警 → 能放的都放了
内存危急 → 保命要紧,能丢的都丢

4. 后台主动释放
退到后台后,主动释放非必要资源。前面讲过了。

5. 「民族图鉴」的内存目标

前台:< 150MB
后台:< 100MB
极限:不超过 200MB

💡 内存优化的原则:够用就好,不要贪多。缓存不是越大越好,大了反而占内存,得不偿失。找到那个"性价比最高"的点。


📝 本章小结

核心知识点

本文系统讲解了应用快启的技术原理与优化方法,核心内容包括:

1. 启动的三种形态

  • 冷启动:进程不存在,从头开始(最慢,优化空间最大)
  • 温启动:进程在,页面要重建(中等)
  • 热启动:进程和页面都在(最快)

2. 冷启动优化

  • 启动页极简:不要固定等待,数据加载完就跳转
  • 服务懒加载:非必须服务延迟初始化
  • 资源预解析:图片格式优化、资源混淆
  • 首页预渲染:启动页时准备首页

3. 温启动优化

  • 进程保活:减少内存占用,降低被杀概率
  • 状态保存:被杀了也能快速恢复
  • 关键状态:当前Tab、滚动位置、用户设置

4. 热启动优化

  • 页面缓存:浏览过的页面保留实例
  • 数据预加载:提前加载用户可能看的内容
  • 刷新策略:缓存 + 刷新,平衡速度与时效

5. 系统级快启

  • 快启框架:系统级启动加速
  • 预启动调度:系统学习用户习惯,提前启动
  • 应用配合:做好自己,让系统愿意给你快启

6. 内存与快启的平衡

  • 快启占内存,内存多了容易被杀
  • 缓存要有上限,不能无限大
  • 后台主动释放非必要资源
  • 内存告警时分级释放

最佳实践总结

✅ **启动页不要固定等

最短展示时间(保证品牌展示
最长展示时间(兜底
数据加载完就跳转
别让用户傻等

✅ **服务能懒就懒

启动时只初始化必须的
TTS、音乐、AI这些,用的时候再初始化
省时间省内存

✅ **状态要保存好

当前Tab、滚动位置、搜索历史
就算进程被杀了,回来也能恢复
用户感觉"从没杀过一样"

预加载要适度

只预加载高概率的内容
不要什么都预加载,费流量费内存
WiFi下多预加载,移动数据少预加载

内存要有上限

图片缓存有上限
页面缓存有上限
预加载有上限
过犹不及,多了反而坏事

后台要轻装上阵

退到后台30秒后
释放非必要资源
内存少了,被杀概率就低了

下一步预告

在下一篇文章中,我们将:

  • 🌐 深入学习 QUIC 协议原理
  • ⚡ 了解 QUIC 相比 TCP+TLS 的优势
  • 📱 掌握鸿蒙 QUIC 栈的使用方法
  • 🚀 学习弱网优化策略
  • 📊 对比 TCP vs QUIC 的性能差异
  • 🔧 实战:将「民族图鉴」的图片加载和 API 请求切换到 QUIC

🔗 相关链接


💡 提示:快启优化是一个"润物细无声"的工作——用户可能说不出哪里变好了,但就是觉得"这个 App 用着很顺手,点开就用"。这就是好的用户体验。快启不是一个功能,是一种感觉。做得好,用户感知不到;做得不好,用户立刻就感觉到了。把启动速度优化好,是给用户留下好第一印象的第一步,也是留住用户的重要一步。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐