OpenHarmony Launcher 应用完整分析报告

基于 applications/standard/launcher/ 源码

目录

术语

缩写 全称 说明
HAR Harmony Ability Resource OHOS 模块化共享包
AMS Ability Manager Service 组件管理服务(系统侧)
Rdb Relational Database 关系型数据库
HAP Harmony Ability Package OHOS 应用安装包
APL Ability Privilege Level 权限等级(system_basic/normal)

1. 概述

1.1 应用基本信息

属性
应用名称 Launcher(桌面启动器)
Bundle Name com.ohos.launcher
MainAbility com.ohos.launcher.MainAbility (ServiceExtension 类型)
APL级别 system_basic
开发语言 ArkTS (ETS)
模块化 HAR 多模块架构

1.2 核心职责

  1. 桌面显示: 显示应用图标、Widget、文件夹
  2. 应用启动: 启动任意应用
  3. 任务管理: 显示最近任务、切换任务、删除任务
  4. 窗口管理: 管理多窗口、分屏、悬浮窗
  5. 手势导航: 处理返回、主页、最近任务手势

2. 代码结构

2.1 完整目录结构

applications/standard/launcher/
│
├── AppScope/                              # 应用全局资源
│   ├── app.json5                         # 应用配置
│   └── resources/base/element/media/      # 应用图标等
│
├── common/                                # 公共HAR模块 (核心共享代码)
│   └── src/main/ets/default/
│       ├── manager/                      # 核心管理器
│       │   ├── WindowManager.ts          # 窗口管理
│       │   ├── LocalEventManager.ts      # 本地事件管理
│       │   ├── CommonEventManager.ts     # 系统事件管理
│       │   ├── AmsMissionManager.ts      # 任务管理(AMS封装)
│       │   ├── LauncherAbilityManager.ts # Launcher能力管理
│       │   ├── FormManager.ts           # Widget管理
│       │   ├── BadgeManager.ts          # 角标管理
│       │   ├── RdbStoreManager.ts       # 数据库管理
│       │   ├── ResourceManager.ts        # 资源管理
│       │   ├── DisplayManager.ts         # 显示管理
│       │   ├── CloseAppManager.ts       # 关闭应用管理
│       │   ├── InputMethodManager.ts    # 输入法管理
│       │   └── NavigationBarCommonEventManager.ts  # 导航栏事件
│       ├── constants/                     # 常量
│       ├── bean/                          # 数据结构
│       ├── model/                         # 数据模型
│       ├── layoutconfig/                  # 布局配置
│       ├── utils/                         # 工具类
│       ├── base/                          # 基类
│       ├── interface/                     # 接口定义
│       └── configs/                       # 配置
│           └── DefaultLayoutConfig.ts    # 默认布局配置
│
├── feature/                               # 功能模块HAR
│   ├── appcenter/                        # 应用中心
│   ├── bigfolder/                        # 大文件夹
│   ├── form/                             # Widget/卡片
│   ├── gesturenavigation/                # 手势导航 ★
│   ├── numbadge/                         # 数字角标
│   ├── pagedesktop/                      # 桌面布局 ★
│   ├── recents/                          # 任务管理器 ★
│   ├── settings/                         # 设置
│   └── smartdock/                        # 智能Dock栏 ★
│
├── product/                              # 产品差异化实现
│   ├── phone/                            # 手机
│   └── pad/                              # 平板
│
└── build-profile.json5

3. 模块架构

3.1 整体架构图

┌─────────────────────────────────────────────────────────┐
│                      phone_launcher                      │
│                       (product/phone/)                   │
│  MainAbility.ts  ← 入口,初始化所有模块                   │
└─────────────────────┬───────────────────────────────────┘
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
   ┌────────────┐ ┌────────┐ ┌──────────┐
   │   common   │ │ recents│ │ gesture  │
   │   (HAR)    │ │ (HAR)  │ │navigation│
   │ 共享核心    │ │ 任务管理│ │ 手势导航  │
   └──────┬─────┘ └───┬────┘ └────┬─────┘
          │            │           │
          ▼            ▼           ▼
   ┌─────────────────────────────────────────┐
   │  系统能力层 (@ohos.*)                     │
   │  window  /  ams  /  inputConsumer       │
   │  display  /  commonEvent  /  rdb        │
   └─────────────────────────────────────────┘
          │
          ▼
   ┌─────────────────────────────────────────┐
   │  OHOS 系统服务                            │
   │  WindowManagerService  /  AbilityMgrSvc │
   │  MultiModalInputService  /  DisplayMgr  │
   └─────────────────────────────────────────┘

3.2 模块列表与依赖关系

模块名 类型 依赖 功能
launcher_common HAR - 核心共享代码:窗口管理、事件管理、AMS封装
launcher_recents HAR common 任务管理器UI和逻辑
launcher_pagedesktop HAR common 桌面布局和显示
launcher_smartdock HAR common 底部Dock栏
launcher_appcenter HAR common 应用列表/网格
launcher_bigfolder HAR common 文件夹功能
launcher_form HAR common Widget/卡片
launcher_gesturenavigation HAR common 手势检测处理
launcher_settings HAR common 设置功能
launcher_numbadge HAR common 角标数字

所有 feature 模块都依赖 common(共享核心),通过 LocalEventManagerAppStorage 进行模块间通信。

3.3 布局配置体系

Launcher 使用三层继承的配置体系,由 common/layoutconfig/ 管理:

RecentsModeConfig (基础配置 - common层)
    属性: recentMissionsLimit = 20
          recentMissionsRowType = 'single'
    ↓ 继承
RecentModeFeatureConfig (功能配置 - feature层)
    用于: 手机
    ↓ 继承
RecentModePadConfig (产品配置 - pad)
    用于: 平板
    属性: recentMissionsRowType = 'double'

样式常量feature/recents/src/main/ets/default/common/constants/RecentsStyleConstants.ts):

常量 单列值 双列值
SINGLE_LIST_APP_IMAGE_WIDTH 216 282
SINGLE_LIST_APP_IMAGE_HEIGHT 453 176
SINGLE_LIST_MISSION_HEIGHT 497 214
RECENT_IMAGE_RADIUS 20 20
RECENT_DELETE_IMAGE_SIZE 24 24

4. 入口分析与初始化流程

4.1 入口初始化流程

AbilityStage 负责 Want 路由分发(仅返回 'Launcher_MainAbility'),核心逻辑在 MainAbility 中:

export default class MainAbility extends ServiceExtension {
  onCreate(want: Want): void {
    this.context.area = 0;
    this.initLauncher();
  }

  async initLauncher(): Promise<void> {
    // 1. 保存Launcher上下文
    globalThis.desktopContext = this.context;

    // 2. 初始化数据库
    let dbStore = RdbStoreManager.getInstance();
    await dbStore.initRdbConfig();
    await dbStore.createTable();

    // 3. 创建桌面窗口 (EntryView)
    windowManager.createWindow(
      globalThis.desktopContext,
      windowManager.DESKTOP_WINDOW_NAME,
      windowManager.DESKTOP_RANK,
      'pages/' + windowManager.DESKTOP_WINDOW_NAME,
      true,
      registerWinEvent
    );

    // 4. 初始化Preferences
    await PreferencesHelper.getInstance().initPreference(this.context);

    // 5. 初始化手势导航
    this.startGestureNavigation();

    // 6. 注册窗口事件和导航栏事件
    windowManager.registerWindowEvent();
    navigationBarCommonEventManager.registerNavigationBarEvent();

    // 7. 预创建任务管理器窗口
    windowManager.createRecentWindow();

    // 8. 注册输入消费者(按键事件)
    this.registerInputConsumer();
  }
}

5. 桌面显示

Launcher 最核心的职责是显示桌面——展示应用图标、Widget 和文件夹。桌面显示由 EntryView 窗口承载,通过 PageDesktopViewModel 管理桌面数据,pagedesktop 模块负责具体 UI 呈现。

5.1 窗口创建与初始化

文件: product/phone/src/main/ets/MainAbility.ts

桌面窗口在 initLauncher() 流程中创建,作为第一个子窗口:

// 创建桌面窗口 EntryView,类型 TYPE_DESKTOP
windowManager.createWindow(
  globalThis.desktopContext,
  windowManager.DESKTOP_WINDOW_NAME,  // 'EntryView'
  windowManager.DESKTOP_RANK,        // 0 (TYPE_DESKTOP)
  'pages/' + windowManager.DESKTOP_WINDOW_NAME,
  true,                              // 创建后立即显示
  registerWinEvent
);

DESKTOP_RANK = 0 是整个窗口体系中最低 rank,确保桌面始终在底层。

5.2 EntryView 页面组件

文件: product/phone/src/main/ets/pages/EntryView.ets

桌面页面使用 @Entry 注解的 ArkTS 组件,通过 PageDesktopViewModel 加载数据和状态:

@Entry
@Component
struct EntryView {
  // 桌面数据模型(单例)
  private mPageDesktopViewModel: PageDesktopViewModel =
    PageDesktopViewModel.getInstance();

  onPageShow(): void {
    this.mPageDesktopViewModel.loadDesktopInfo();
  }

  build() {
    Stack() {
      // 桌面应用图标网格
      PageDesktopGrid({
        viewModel: this.mPageDesktopViewModel
      })
      // 底部 Dock 栏
      SmartDock()
      // 文件夹弹出层
      BigFolder()
    }
  }
}

5.3 桌面数据加载

文件: feature/pagedesktop/src/main/ets/default/viewmodel/PageDesktopViewModel.ts

ViewModel 采用单例模式,通过 loadDesktopInfo() 同时从多个数据源加载桌面数据:

class PageDesktopViewModel {
  private static instance: PageDesktopViewModel;

  static getInstance(): PageDesktopViewModel { ... }

  async loadDesktopInfo(): Promise<void> {
    // 1. 从数据库读取用户自定义桌面布局
    this.layoutInfo = await RdbStoreManager.getInstance()
      .queryLayoutConfig();

    // 2. 获取已安装应用列表
    let appList: BundleInfo[] = [];
    try {
      appList = await bundleManager.getBundleInfoList(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY
      );
    } catch (err) { ... }

    // 3. 获取 Widget 数据
    let formList = await FormManager.getInstance().getForms();

    // 4. 合并数据,通过 AppStorage 驱动 UI 刷新
    AppStorage.SetOrCreate('desktopAppList', appList);
    AppStorage.SetOrCreate('desktopFormList', formList);
  }
}

5.4 图标网格与文件夹

桌面图标网格由 PageDesktopGridfeature/pagedesktop 模块)渲染,通过 LazyForEach 实现高性能列表:

PageDesktopGrid
  └── LazyForEach(desktopAppList)
      ├── AppItem (单个应用图标)
      │   ├── Image(appIcon)      ← bundleManager 获取
      │   └── Text(appName)
      │
      ├── FolderItem (文件夹)
      │   └── 展开后显示 BigFolder 弹出层
      │       └── LazyForEach(folderApps) → AppItem
      │
      └── WidgetItem (卡片)
          └── FormComponent(formId)

图标网格布局从 common/layoutconfig/DefaultLayoutConfig.ts 读取行列配置,支持翻页和拖拽排序。文件夹功能由 feature/bigfolder 模块实现,长按图标可将其拖入文件夹。

5.5 壁纸无关性

桌面本身不渲染壁纸——壁纸是独立的 WINDOW_TYPE_WALLPAPER (2000) 窗口,由 SceneBoard 创建,位于 Z-order 最底层。Launcher 对壁纸没有任何渲染逻辑,仅通过窗口类型体系中的 Z-order 实现"壁纸在桌面之下"的视觉效果。

6. 应用启动

Launcher 通过 LauncherAbilityManager 封装应用启动逻辑。启动入口有两种:点击桌面图标和最近任务卡片。

6.1 LauncherAbilityManager 实现

文件: common/src/main/ets/default/manager/LauncherAbilityManager.ts

class LauncherAbilityManager {
  static startApp(appInfo: AppItemInfo): void {
    let want: Want = {
      bundleName: appInfo.bundleName,
      abilityName: appInfo.abilityName,
      parameters: {
        'abilityLaunchType': 'desktop',
        'launcherSource': 'Launcher',
        'appIndexId': appInfo.appIndexId ?? 0
      }
    };
    abilityManager.startAbility(want).catch((err) => {
      Logger.error('startApp failed: ' + JSON.stringify(err));
    });
  }

  static startAbilityByWant(want: Want): void {
    abilityManager.startAbility(want);
  }
}

Want 参数解析:

参数 来源 说明
bundleName AppItemInfo.bundleName 目标应用的包名
abilityName AppItemInfo.abilityName 目标 Ability 名称(通常是 MainAbility/EntryAbility)
abilityLaunchType 硬编码 'desktop' 标示启动来源为桌面
launcherSource 硬编码 'Launcher' 启动者标识
appIndexId AppItemInfo.appIndexId 多开应用索引(默认 0)

6.2 启动入口路由

桌面图标和应用中心点击都经过统一入口路由:

桌面图标点击 (AppItem.onClick)
    │
    ├── 普通应用 → LauncherAbilityManager.startApp(appInfo)
    │               └── abilityManager.startAbility(want)
    │
    ├── 系统设置 → want.abilityName = 'SettingsAbility'
    │               └── startAbility(want)
    │
    ├── 快捷操作 → want.parameters.shortcutId = shortcutId
    │               └── startAbility(want)
    │
    └── 文件夹内图标 → 同上,但先折叠文件夹弹出层

6.3 启动流程与 AMS 交互

LauncherAbilityManager.startAbility(want)
    │
    ├── 组装 Want(bundleName + abilityName + 参数)
    │
    ▼
abilityManager.startAbility(want)           ← IPC 调用系统 AMS
    │
    ▼
AMS (AbilityManagerService)
    │
    ├── 检查进程是否存在
    │   ├── 是 → 调用已存在 Ability 的 onNewWant(want)
    │   └── 否 → 孵化新进程 → AppMain → UIAbility.onCreate()
    │
    ├── 权限校验(Launcher 有 START_ABILITIES_FROM_BACKGROUND)
    │
    └── 返回值 → Promise<void>
        ├── 成功 → 目标应用 UI 显示
        └── 失败 → Launcher 打印错误日志

关键点: Launcher 具有 ohos.permission.START_ABILITIES_FROM_BACKGROUND 权限(APL system_basic),才能从 ServiceExtension 后台启动应用。普通三方应用不具备此权限。

7. 任务管理

任务管理器(RecentView)是 Launcher 最复杂的功能模块。负责展示运行中应用的快照列表,支持切换/关闭任务。以下从架构、触发、数据、UI 和交互细节五个维度展开。

7.1 核心架构

7.1.1 类职责明细

RecentView.ets (页面入口)
    │
    ├── RecentMissionsStage         ← 生命周期管理(onCreate/onDestroy/onWindowEvent)
    ├── RecentMissionsViewModel     ← 业务逻辑(单例,获取/转换/更新任务数据)
    ├── RecentMissionsPreLoader     ← 预加载(页面显示前提前加载数据)
    ├── RecentsStyleConstants       ← 样式配置(尺寸/间距/圆角常量)
    │
    ├── RecentMissionsSingleLayout  ← 手机单列布局(横向 List 堆叠)
    ├── RecentMissionsDoubleLayout  ← 平板双列布局(Grid 双列排列)
    │
    └── RecentMissionCard           ← 单个任务卡片(快照+图标+名称+关闭按钮)

7.1.2 RecentMissionsStage 生命周期管理

文件: feature/recents/src/main/ets/default/stage/RecentMissionsStage.ts

Stage 模式是 OHOS 模块间生命周期管理的标准做法。RecentMissionsStage 封装了任务管理器的生命周期流程:

class RecentMissionsStage {
  private mRecentMissionsViewModel: RecentMissionsViewModel =
    RecentMissionsViewModel.getInstance();
  private mRecentMissionsPreLoader: RecentMissionsPreLoader =
    new RecentMissionsPreLoader();

  onCreate(): void {
    // 1. 启动预加载
    this.mRecentMissionsPreLoader.onStart();

    // 2. 初始化 ViewModel 状态
    this.mRecentMissionsViewModel.init();

    // 3. 注册窗口变化监听(全屏/分屏切换时刷新布局)
    this.registerWindowSizeChange();
  }

  onDestroy(): void {
    this.mRecentMissionsPreLoader.onStop();
    this.mRecentMissionsViewModel.clear();
    this.unregisterWindowSizeChange();
  }

  onWindowSizeChange(newWidth: number): void {
    // 窗口大小变化时重新计算布局模式(single ↔ double)
    this.mRecentMissionsViewModel.updateLayoutMode(newWidth);
  }
}

生命周期触发时机:

回调 触发时机 行为
onCreate() RecentView 的 onPageShow() 预加载启动 + ViewModel 初始化
onDestroy() RecentView 的 onPageHide() 清除预加载 + ViewModel 清理
onWindowSizeChange() 窗口宽度变化 切换单列/双列布局模式

7.1.3 RecentMissionsPreLoader 预加载机制

文件: feature/recents/src/main/ets/default/preloader/RecentMissionsPreLoader.ts

最近任务窗口需要快速响应,因此引入预加载——页面显示前提前请求任务列表:

class RecentMissionsPreLoader {
  private isStarted: boolean = false;

  onStart(): void {
    this.isStarted = true;
    // 立即发起首次数据预加载
    this.preload();
    // 定时轮询刷新(每 5 秒检查前台应用变化)
    this.startPolling();
  }

  onStop(): void {
    this.isStarted = false;
    this.stopPolling();
  }

  private async preload(): Promise<void> {
    let missionInfos = await AmsMissionManager.getMissionInfos();
    if (this.isStarted) {
      RecentMissionsViewModel.getInstance()
        .updateMissionList(missionInfos);
    }
  }

  private startPolling(): void {
    setInterval(() => {
      if (this.isStarted) this.preload();
    }, 5000);  // 5s 轮询
  }
}

预加载的核心意义:大多数场景下用户从桌面/应用上滑进入最近任务,在 onPageShow() 被调用时数据已经在 AppStorage 中,UI 可以立即渲染而无需等待 IPC 往返。

7.2 三种触发方式

  • 按键触发inputConsumer.on('key', KEYCODE_FUNCTION) → 主流实体按键设备(平板+键盘)
  • 手势触发GestureNavigationExecutors.recentEventCall() → 全屏手势设备的主流触发方式
  • 系统事件触发CommonEventManager 订阅 CREATE_RECENT_WINDOW_EVENT → 分屏模式切换、导航栏事件等

统一汇聚点:

按键:  inputConsumer → windowManager.createWindowWithName('RecentView')
手势:  GestureNavigationExecutors → minimizeAllApps → createWindowWithName('RecentView')
事件:  CommonEventManager → WindowManager.createRecentWindow()
                                              │
                                              ▼
                              ┌───────────────────────────────┐
                              │  findWindow('RecentView')?    │
                              │  ├── 存在 → win.show()        │
                              │  └── 不存在 → createWindow()  │
                              └───────────────────────────────┘

关键细节:手势触发比按键触发多了一步 minimizeAllApps(),因为触摸上滑时用户正在应用内,需要先将当前应用窗口最小化。而按键触发通常发生在桌面模式下,无需最小化。

7.3 数据获取与状态管理

7.3.1 MissionInfo 数据结构(系统侧)

MissionInfo 是系统 AMS 定义的数据结构,通过 abilityManager.getMissionInfos() 获取:

字段 类型 说明
missionId number 任务唯一标识
runningState number 运行状态:0=未运行, 1=运行中, 2=冻结
label string 应用名称
icon PixelMap 应用图标
snapShot PixelMap 窗口快照截图(由 WindowManagerService 异步截取)
want Want 启动该任务的原始 Want,含 bundleName/abilityName
lockedState boolean 是否锁定(锁定任务不可被一键清除)
time number 最后活动时间戳

TaskManager 仅过滤 runningState === 1(运行中),跳过冻结(2)和后台暂存(0)的任务。snapShot 是系统 WMS 在前台窗口变化时自动截取,用于显示应用切换时的视觉预览。

7.3.2 AmsMissionManager 封装

文件: common/src/main/ets/default/manager/AmsMissionManager.ts

class AmsMissionManager {
  static async getMissionInfos(limit = 20): Promise<MissionInfo[]> {
    let infos: MissionInfo[] = [];
    try {
      infos = await abilityManager.getMissionInfos('', limit);
      // 只保留正在运行的任务(runningState === 1)
      return infos.filter(info => info.runningState === 1);
    } catch (err) {
      Logger.error('getMissionInfos failed: ' + JSON.stringify(err));
      return [];
    }
  }

  static async moveMissionToFront(missionId: number): Promise<void> {
    try {
      await abilityManager.moveMissionToFront(missionId);
      // 成功后 Launcher 自动隐藏 RecentView 窗口
    } catch (err) {
      Logger.error('moveMissionToFront failed: ' + JSON.stringify(err));
    }
  }

  static async killMission(missionId: number): Promise<void> {
    try {
      // 注意:此处调用的是 killProcess(杀死进程)
      // 而非 removeMission(移除任务)
      await abilityManager.killProcess(missionId);
    } catch (err) {
      Logger.error('killMission failed: ' + JSON.stringify(err));
    }
  }
}

7.3.3 RecentMissionsViewModel 单例

文件: feature/recents/src/main/ets/default/viewmodel/RecentMissionsViewModel.ts

class RecentMissionsViewModel {
  private static instance: RecentMissionsViewModel;

  static getInstance(): RecentMissionsViewModel { ... }

  async getRecentMissionsList(): Promise<void> {
    let missionInfos = await AmsMissionManager.getMissionInfos();
    let recentMissionsList = missionInfos.map(info => {
      let recentMissionInfo = new RecentMissionInfo();
      recentMissionInfo.missionId_ = info.missionId;
      recentMissionInfo.appName_ = info.label;
      recentMissionInfo.snapShot_ = info.snapShot;
      return recentMissionInfo;
    });
    AppStorage.Set('recentMissionsList', recentMissionsList);
  }

  updateMissionList(missionInfos: MissionInfo[]): void {
    let list = missionInfos
      .filter(info => info.runningState === 1)
      .map(info => ({ missionId: info.missionId, ... }));
    AppStorage.Set('recentMissionsList', list);
  }

  startApp(missionInfo: RecentMissionInfo): void {
    AmsMissionManager.moveMissionToFront(missionInfo.missionId_);
  }

  removeMission(missionInfo: RecentMissionInfo): void {
    AmsMissionManager.killMission(missionInfo.missionId_);
  }

  getRecentMissionsRowType(): string {
    return RecentsModeConfig.getInstance().getRowType();
  }
}

7.4 UI 呈现

7.4.1 布局模式选择

布局模式由 RecentsModeConfig 配置体系决定(见 §3.3):

RecentsModeConfig.recentMissionsRowType
    │
    ├── 'single' → RecentMissionsSingleLayout → 手机:单列横向堆叠
    │                每个任务占满宽度,通过 LazyForEach 滚动浏览
    │
    └── 'double' → RecentMissionsDoubleLayout → 平板:双列 Grid
                    每行两个任务卡片,充分利用宽屏空间

文件: feature/recents/src/main/ets/default/common/layoutconfig/RecentsModeConfig.ts

class RecentsModeConfig {
  // 手机默认单列
  recentMissionsRowType = 'single';
  recentMissionsLimit = 20;

  getRowType(): string {
    return this.recentMissionsRowType;
  }
}

平板 Pad 版本通过继承覆盖为 'double'

7.4.2 RecentView 完整实现

文件: product/phone/src/main/ets/pages/RecentView.ets

@Entry
@Component
struct RecentView {
  @StorageLink('recentMissionsList') recentMissionsList: RecentMissionInfo[] = [];
  private mRecentMissionsStage: RecentMissionsStage = new RecentMissionsStage();
  private mRecentMissionsViewModel?: RecentMissionsViewModel;
  @State mRecentMissionsRowType: string = '';

  onPageShow(): void {
    this.mRecentMissionsStage.onCreate();
    this.mRecentMissionsViewModel = RecentMissionsViewModel.getInstance();
    this.mRecentMissionsRowType =
      this.mRecentMissionsViewModel.getRecentMissionsRowType();
    this.mRecentMissionsViewModel.getRecentMissionsList();
  }

  onPageHide(): void {
    this.mRecentMissionsStage.onDestroy();
  }

  build() {
    Stack() {
      if (this.recentMissionsList.length) {
        if (this.mRecentMissionsRowType === 'single') {
          RecentMissionsSingleLayout({ ... })
        } else {
          RecentMissionsDoubleLayout({ ... })
        }
      } else {
        // 空状态:无运行中应用
        Text($r('app.string.No_running_apps_recently'))
          .fontSize(16)
          .fontColor('#666666')
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

7.4.3 RecentMissionCard 任务卡片

文件: feature/recents/src/main/ets/default/uicomponents/RecentMissionCard.ets

@Component
struct RecentMissionCard {
  private recentMissionInfo: RecentMissionInfo;
  private recentMissionsViewModel: RecentMissionsViewModel =
    RecentMissionsViewModel.getInstance();

  build() {
    Stack() {
      // 快照缩略图(圆角矩形)
      Image(this.recentMissionInfo.snapShot_)
        .width('100%')
        .borderRadius(20)

      // 底部信息栏(应用名 + 删除按钮)
      Row() {
        // 应用图标 + 名称
        RecentMissionAppIcon({ icon: this.recentMissionInfo.appIcon_ })
        RecentMissionAppName({ name: this.recentMissionInfo.appName_ })
        Blank()
        // 关闭按钮(X)
        Button({ type: ButtonType.Circle }) {
          Image($r('app.media.ic_close')).width(20).height(20)
        }
        .width(24).height(24)
        .onClick(() => this.removeMission())
      }
      .alignItems(VerticalAlign.Center)
      .padding({ left: 16, right: 16, bottom: 12 })
    }
    .onClick(() => {
      this.recentMissionsViewModel.startApp(this.recentMissionInfo)
    })
    .margin({ bottom: 12 })
  }

  removeMission() {
    // 关闭任务 → 刷新列表
    this.recentMissionsViewModel.removeMission(this.recentMissionInfo);
    this.recentMissionsViewModel.getRecentMissionsList();
  }
}

7.4.4 进入/退出动画

任务窗口显示时带有过渡动画。RecentView 通过系统窗口动画框架实现:

进入:窗口从屏幕底部向上滑入(SlideInUp),背景渐变
      ├── RecentView 窗口 show() 时触发
      └── 卡片依次从下方滑入,有轻微延迟(stagger 效果)

退出:点击卡片或边缘下滑
      ├── 点击卡片 → 窗口立即 hide() → 目标应用显示
      └── 下滑退出 → 窗口动画滑出 → hide()

动画参数由 RecentsStyleConstants 配置,包括卡片圆角(20vp)、间距、消除按钮大小(24vp)等。

7.4.5 空状态与边界行为

场景 表现
无运行中应用 显示 "暂无最近运行的应用" 灰色提示文本
仅一个应用 单列布局正常显示,仅一个卡片
超过 20 个应用 limit=20 截断,只显示最近 20 个任务
所有应用都冻结 runningState=2,过滤后为空 → 显示空状态
系统应用不可关闭 killProcess() 对系统关键进程可能失败

8. 窗口管理

Launcher 作为 ServiceExtension,每个功能模块(桌面、最近任务、应用中心、Widget 管理等)都是独立的窗口,由 WindowManager 统一管理。Launcher 的窗口体系是其最独特的设计之一——不依赖系统 Activity 栈,而是自主控制窗口的创建、显示、隐藏和销毁。

8.1 WindowManager 核心实现

文件: common/src/main/ets/default/manager/WindowManager.ts

class WindowManager {
  static DESKTOP_WINDOW_NAME = 'EntryView';
  static RECENT_WINDOW_NAME = 'RecentView';
  static DESKTOP_RANK = 0;         // TYPE_DESKTOP
  static RECENT_RANK = 2000;       // TYPE_LAUNCHER_RECENT

  createWindow(context, name, rank, page, isShow, callBack): void {
    let config = {
      name: name,
      windowType: window.WindowType.TYPE_DIALOG,
      ctx: context,
      displayId: 0
    };
    window.createWindow(config).then((win) => {
      win.setUIContent(page);
      if (isShow) win.show();
      this.registerWindowEvent(win, callBack);
    });
  }

  createRecentWindow(): void {
    try {
      let win = window.findWindow(this.RECENT_WINDOW_NAME);
      win.show();  // 窗口已存在 → 显示
    } catch (err) {
      this.createWindow(..., this.RECENT_WINDOW_NAME, this.RECENT_RANK, ...);
    }
  }

  minimizeAllApps() {
    let dis = display.getDefaultDisplaySync();
    window.minimizeAll(dis.id);
  }
}

8.2 窗口生命周期

Launcher 的窗口生命周期由 WindowManager 全权管理,分为四个阶段:

创建 (createWindow)
  │  window.createWindow(config) + setUIContent(page)
  │
  ▼
显示 (show)
  │  win.show() / win.hide()
  │  ├── 首次创建时 isShow=true 直接显示
  │  └── 复用已有窗口时 findWindow → show
  │
  ▼
隐藏 (hide/minimize)
  │  minimizeAllApps()  → 通过 display.minimizeAll 隐藏所有应用
  │  onWindowEvent('hide') → 移动端返回时触发
  │
  ▼
销毁 (destroy)
  │  window.destroyWindow()
  │  (通常由系统触发:onDestroy 时清理)

关键设计点:

  1. 窗口复用createRecentWindow() 先尝试 window.findWindow() 查找已有窗口,存在则直接 show(),避免重建。这是服务常驻模型的必然要求——窗口不随应用切换销毁。

  2. 滚动窗口:窗口创建后不销毁,仅 hide/show 切换。每次进入最近任务时,minimizeAllApps() 先最小化所有应用窗口,再 show() RecentView。

8.3 窗口事件注册

registerWindowEvent 在窗口创建时为每个窗口绑定系统回调:

private registerWindowEvent(window: window.Window, callBack): void {
  window.on('windowEvent', (data) => {
    switch (data.windowEvent) {
      case window.WindowEvent.SHOWN:
        // 窗口可见后回调(如记录可见状态)
        callBack?.('shown');
        break;
      case window.WindowEvent.HIDDEN:
        // 窗口隐藏后回调(如释放资源)
        callBack?.('hidden');
        break;
    }
  });
}

除了逐窗口事件,registerWindowEvent() 方法(不带参数)还注册系统级窗口事件:

public registerWindowEvent(): void {
  commonEventManager
    .createSubscribeInfo([commonEvent.event.SPLIT_SCREEN], (err, data) => {
      // 分屏模式切换 → 调整窗口布局
      if (data.event === 'SPLIT_SCREEN') {
        this.createRecentWindow();
      }
    });
}

8.4 窗口类型与 Z-order 体系

所有子窗口使用 TYPE_DIALOG 创建,通过 rank 参数控制 Z-order 层级:

窗口名 rank 类型含义 用途
EntryView 0 TYPE_DESKTOP 主桌面
RecentView 2000 TYPE_LAUNCHER_RECENT 最近任务
AppCenterView - TYPE_DIALOG 应用中心
FormManagerView - TYPE_DIALOG Widget管理
FormServiceView - TYPE_DIALOG Widget服务

rank 值越低越靠下(桌面 0 在最底层,RecentView 2000 在其之上)。同一 rank 的窗口按创建时间叠加。

8.5 minimizeAll 与窗口切换

当用户触发最近任务时,minimizeAllApps() 将当前显示的所有应用窗口最小化:

minimizeAllApps() {
  // 获取主屏 Display 信息
  let dis = display.getDefaultDisplaySync();
  // 调用系统 API 最小化该 Display 上所有应用窗口
  window.minimizeAll(dis.id);
}

此操作通过系统 API 实现,无需 Launcher 逐个窗口操作。最小化后,Launcher 的 RecentView 窗口再显示到最上层,实现"桌面覆盖"效果。

窗口切换流程:

上滑手势触发最近任务
    │
    ├── GestureNavigationExecutors.recentEventCall()
    │
    ├── WindowManager.minimizeAllApps()           ← 最小化所有应用
    │   └── window.minimizeAll(displayId)
    │
    └── WindowManager.createRecentWindow()       ← 显示 RecentView
        ├── findWindow('RecentView')? show()
        └── 不存在 → createWindow(...)

9. 手势导航

文件: feature/gesturenavigation/

手势导航负责处理返回、主页、最近任务的触摸手势,独立为一个 HAR 模块。这是 Launcher 实现"全屏手势"的核心——系统不拦截触摸事件,由 Launcher 自行判断用户的触摸意图。

9.1 GestureNavigationManager 初始化

文件: feature/gesturenavigation/src/main/ets/default/GestureNavigationManager.ts

MainAbility.initLauncher() 中通过 startGestureNavigation() 启动手势导航:

startGestureNavigation(): void {
  let gestureMgr = GestureNavigationManager.getInstance();
  gestureMgr.initWindowSize(this.context);
}

initWindowSize 获取屏幕尺寸,确定手势检测的"生效区域"边界值(22vp 底部、16vp 左右边缘等),然后注册触摸事件监听:

class GestureNavigationManager {
  static getInstance(): GestureNavigationManager { ... }

  initWindowSize(context): void {
    let display = display.getDefaultDisplaySync();
    this.windowWidth = display.width;
    this.windowHeight = display.height;

    // 计算手势区域阈值(vp)
    this.edgeWidth = vp2px(16);    // 左右边缘宽度
    this.bottomBar = vp2px(22);    // 底部区域高度
    this.bottomCenterWidth = ...;  // 底部中央区域 → 回桌面

    // 注册全局触摸监听
    GestureNavigationExecutors.getInstance()
      .touchEventCallback(context, this.handleTouch.bind(this));
  }
}

9.2 触摸事件处理

文件: feature/gesturenavigation/src/main/ets/default/GestureNavigationExecutors.ts

触摸事件由系统 MultiModalInput 服务分发,Launcher 通过 inputConsumer.on('touch') 接收:

class GestureNavigationExecutors {
  touchEventCallback(context, handler): void {
    inputConsumer.on('touch', {
      touchType: 'all',   // 所有触摸类型(DOWN/MOVE/UP)
      windowId: 0         // 全局监听,不绑定特定窗口
    }, (touchEvent) => {
      let x = touchEvent.touches[0].x;      // 触摸 X 坐标
      let y = touchEvent.touches[0].y;      // 触摸 Y 坐标
      let action = touchEvent.touches[0].type; // DOWN/MOVE/UP

      if (action === TouchType.DOWN) {
        // 记录起始点位置
        this.touchStartX = x;
        this.touchStartY = y;
      }

      if (action === TouchType.UP) {
        // 计算滑动距离和方向
        let dx = x - this.touchStartX;
        let dy = y - this.touchStartY;
        handler({ startX: this.touchStartX, startY: this.touchStartY,
                  endX: x, endY: y, dx, dy });
      }
    });
  }
}

9.3 区域判定与执行器

触摸结束时,handleTouch 根据起始点位置和滑动方向进行区域判定:

触摸事件触发
    │
    ▼
handleTouch(touchData)
    │
    ├── 是否从底部 22vp 区域内启动?
    │   ├── 是 → 上滑?
    │   │   ├── 底部中央(水平居中 60% 区域)
    │   │   │   └── homeEventCall()  → 回桌面
    │   │   └── 左右边缘 16vp
    │   │       └── recentEventCall() → 最近任务
    │   └── 否 → 忽略(可能只是点击底部)
    │
    └── 是否从左右边缘 16vp 启动?
        ├── 是 → 左右横滑?
        │   └── backEventCall() → 返回
        └── 否 → 忽略

9.4 三种手势的执行器实现

手势 方法 触发动作 源码位置
上滑回桌面 homeEventCall() WindowManager.minimizeAllApps() GestureNavigationExecutors.ts:120-130
上滑进最近任务 recentEventCall() minimizeAllApps()createWindowWithName('RecentView') GestureNavigationExecutors.ts:200-210
左右滑返回 backEventCall() AbilityManager.terminateAbility() GestureNavigationExecutors.ts:150-160
// 回桌面:最小化所有应用窗口
homeEventCall() {
  windowManager.minimizeAllApps();
}

// 最近任务:最小化应用 → 显示 RecentView
recentEventCall() {
  windowManager.minimizeAllApps();
  windowManager.createWindowWithName(windowManager.RECENT_WINDOW_NAME, ...);
}

// 返回:终止当前前台 Ability
backEventCall() {
  abilityManager.terminateAbility();  // 仅结束最上层 Ability
}

10. 事件系统

Launcher 使用两层事件机制:模块间用 LocalEventManager,系统事件用 CommonEventManager。核心思路:feature 模块之间不直接相互引用,通过事件总线解耦。

10.1 事件常量定义

事件名 来源
CREATE_RECENT_WINDOW_EVENT 'createRecentWindow' 本地事件
STATUSBAR_CHANGE_EVENT 'statusbarChange' 系统事件
SPLIT_SCREEN 'usual.event.SPLIT_SCREEN' 系统事件

10.2 LocalEventManager 本地事件

文件: common/src/main/ets/default/manager/LocalEventManager.ts

class LocalEventManager {
  static registerEventListener(event: string, callback: Function): void {
    // 注册模块间事件监听
  }
  static unregisterEventListener(event: string): void {
    // 取消注册
  }
  static sendEvent(event: string, params?: Object): void {
    // 发送事件到其他模块
  }
}

典型事件流:手势导航 → 窗口管理

以手势触发最近任务为例,展示跨厂模块事件解耦:

GestureNavigationExecutors    LocalEventManager           WindowManager
       │                           │                          │
       │  sendEvent(               │                          │
       │   'createRecentWindow')   │                          │
       │──────────────────────────>│                          │
       │                           │  回调已注册的监听器         │
       │                           │──────────────────────────>│
       │                           │                          │
       │                           │  createRecentWindow()    │
       │                           │                          │
       │                           │  └── show() / create()   │
       │                           │                          │

gesturenavigation 模块发送 CREATE_RECENT_WINDOW_EVENT,WindowManager 作为订阅者响应——两者通过事件名称解耦,无需直接调用对方方法。

10.3 CommonEventManager 系统公共事件

文件: common/src/main/ets/default/manager/CommonEventManager.ts

class CommonEventManager {
  subscribeEvent(event: string, callback: Function): void {
    commonEventManager.createSubscribeInfo([event], callback);
  }
}

系统事件用于接收系统级广播,如分屏模式切换、导航栏状态变化等。

典型场景:分屏模式切换时打开最近任务

// WindowManager.registerWindowEvent() 中
commonEventManager.createSubscribeInfo(
  [commonEvent.event.SPLIT_SCREEN],  // 'usual.event.SPLIT_SCREEN'
  (err, data) => {
    if (data.event === 'SPLIT_SCREEN') {
      // 分屏模式下打开最近任务,让用户选择分屏应用
      this.createRecentWindow();
    }
  }
);

10.4 事件应用场景汇总

发送方 事件 接收方 响应
GestureNavigation CREATE_RECENT_WINDOW_EVENT WindowManager 打开 RecentView
系统 (SplitScreen) SPLIT_SCREEN WindowManager 打开最近任务选应用
系统 (StatusBar) STATUSBAR_CHANGE_EVENT EntryView 调整桌面布局避开状态栏
AppCenter (本地) PageDesktop 安装/卸载后刷新桌面列表

11. 关键系统API依赖与权限

11.1 API 依赖速查表

API 模块 调用方 用途
window WindowManager 创建/显示窗口、minimizeAll
abilityManager AmsMissionManager 任务管理:getMissionInfos、moveMissionToFront、killProcess
abilityManager LauncherAbilityManager 应用启动:startAbility
inputConsumer MainAbility / GestureNavigation 按键监听(HOME、FUNCTION)+ 触摸事件
display WindowManager 获取 Display 信息(minimizeAll 需要 displayId)
commonEventManager CommonEventManager 系统公共事件订阅
bundleManager PageDesktopViewModel 获取已安装应用列表
rdb RdbStoreManager 布局配置持久化
formManager FormManager Widget 管理:获取/绑定/更新
data.preferences PreferencesHelper 偏好设置存储

11.2 调用路径与系统服务交互

Launcher (ServiceExtension 进程)
    │
    ├── window.* ──────────► WindowManagerService (WMS)
    │   └── createWindow / findWindow / show / minimizeAll / destroyWindow
    │
    ├── abilityManager.* ──► AbilityManagerService (AMS)
    │   ├── getMissionInfos() → 返回 MissionInfo[]
    │   ├── moveMissionToFront(id) → 切换前台
    │   ├── killProcess(id) → 关闭任务
    │   └── startAbility(want) → 启动应用
    │
    ├── display.* ─────────► DisplayManagerService (DMS)
    │   └── getDefaultDisplaySync() → Display
    │
    ├── inputConsumer.* ───► MultiModalInputService
    │   └── on('key') / on('touch') → 按键 / 触摸事件回调
    │
    └── bundleManager.* ──► BundleManagerService (BMS)
        └── getBundleInfoList() → BundleInfo[]

11.3 权限清单

权限 APL级别 用途
ohos.permission.START_ABILITIES_FROM_BACKGROUND system_basic 后台启动 Ability
ohos.permission.GET_BUNDLE_INFO_PRIVILEGED system_basic 获取已安装应用信息
ohos.permission.INTERACT_ACROSS_LOCAL_ACCOUNTS system_basic 跨用户交互
ohos.permission.MANAGE_MISSIONS system_basic 管理任务栈(需 system_basic + signature)

12. 与 Android 对比

维度 OpenHarmony Launcher Android Launcher
进程模型 ServiceExtension(常驻服务) Activity(可被系统回收)
窗口类型 TYPE_DIALOG + rank Z-order Activity window stack
窗口管理 手动创建/显示/销毁 ActivityManager 自动管理
任务管理 API abilityManager.getMissionInfos()(AMS 查询) ActivityManager.getRecentTasks()
桌面布局 RdbStore 持久化 SharedPreferences / DataStore
手势导航 Launcher 自身实现 SystemUI 处理(高通/MTK 自定义)
应用启动 Want 模型 Intent 模型
模块化 HAR 多模块 APK + Library
权限等级 APL system_basic privileged 权限

关键区别一:OHOS Launcher 以 ServiceExtension 形式常驻运行,不随应用切换被回收,这是与服务窗口挂载模型一致的设计——Launcher 需要持续管理多个独立窗口。

关键区别二:OHOS 的手势导航由 Launcher 自身(gesturenavigation 模块)实现,而非由桌面环境层(SystemUI)统一处理。

13. 总结

13.1 架构特点

  1. ServiceExtension 常驻模型— Launcher 不随应用切换销毁,始终管理窗口体系
  2. 多窗口架构— 每个功能模块独立窗口,WindowManager 统一调度
  3. 三层配置体系— 基类→功能→产品,手机/平板差异化
  4. 事件驱动— 本地事件 + 系统事件两层,模块间解耦
  5. MVVM 模式— ViewModel 单例管理状态,AppStorage 绑定 UI

13.2 触发入口汇总

功能 触发方式 核心路径
桌面显示 onCreate 自动创建 MainAbility.initLauncher() → createWindow('EntryView')
最近任务 按键/手势/系统事件 createWindowWithName('RecentView')
应用启动 点击图标 LauncherAbilityManager.startAbility(want)
手势导航 触摸事件 GestureNavigationExecutors.touchEventCallback
返回手势 边缘滑动 backEventCall() → terminateAbility()

13.3 FAQ

Q: Launcher 是什么类型的 Ability?
A: ServiceExtension。非 UIAbility,以服务形式常驻。

Q: Launcher 的窗口类型是什么?
A: 使用 TYPE_DIALOG + rank,而非 TYPE_APPLICATION。rank 0 为桌面,2000 为最近任务。

Q: 手势识别在哪里实现?
A: feature/gesturenavigation/ 模块,通过 GestureNavigationExecutors.ts 的触摸事件回调实现区域判定。

Q: 任务管理数据从哪来?
A: 通过 IPC 调用系统 AMS(abilityManager.getMissionInfos()),非本地进程内维护。

Q: 壁纸绘制由谁负责?
A: 系统 SceneBoard 创建 WINDOW_TYPE_WALLPAPER,Launcher 不参与壁纸逻辑。

Q: 桌面布局数据如何持久化?
A: 使用 RdbStore(关系型数据库),非 JSON 文件或 Preferences。

13.4 核心文件速查表

模块 文件 功能
入口 product/phone/MainAbility.ts 初始化、注册监听
窗口管理 common/manager/WindowManager.ts 创建/显示/查找窗口
任务管理 feature/recents/ RecentView 页面 + ViewModel
手势导航 feature/gesturenavigation/ 触摸检测 + 区域判定
应用启动 common/manager/LauncherAbilityManager.ts Want 组装 + StartAbility
事件管理 common/manager/LocalEventManager.ts 模块间事件通信
AMS封装 common/manager/AmsMissionManager.ts 任务数据获取
桌面数据 feature/pagedesktop/viewmodel/PageDesktopViewModel.ts 应用图标+Widget 加载
布局配置 common/layoutconfig/ 手机/平板差异化配置
数据库 common/manager/RdbStoreManager.ts 布局持久化
posted @ 2026-05-28 16:03  getmoon  阅读(53)  评论(0)    收藏  举报