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 核心职责
- 桌面显示: 显示应用图标、Widget、文件夹
- 应用启动: 启动任意应用
- 任务管理: 显示最近任务、切换任务、删除任务
- 窗口管理: 管理多窗口、分屏、悬浮窗
- 手势导航: 处理返回、主页、最近任务手势
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(共享核心),通过 LocalEventManager 和 AppStorage 进行模块间通信。
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 图标网格与文件夹
桌面图标网格由 PageDesktopGrid(feature/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 时清理)
关键设计点:
-
窗口复用:
createRecentWindow()先尝试window.findWindow()查找已有窗口,存在则直接show(),避免重建。这是服务常驻模型的必然要求——窗口不随应用切换销毁。 -
滚动窗口:窗口创建后不销毁,仅
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 架构特点
- ServiceExtension 常驻模型— Launcher 不随应用切换销毁,始终管理窗口体系
- 多窗口架构— 每个功能模块独立窗口,WindowManager 统一调度
- 三层配置体系— 基类→功能→产品,手机/平板差异化
- 事件驱动— 本地事件 + 系统事件两层,模块间解耦
- 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 |
布局持久化 |

浙公网安备 33010602011771号