在存量运营时代,系统桌面是移动应用最关键的流量入口。开发者通常会引导用户在桌面固定“付款码”、“每日打卡”等功能快捷方式,但此前应用无法感知桌面已存在哪些快捷方式,导致无法提供精准的入口管理视图。HarmonyOS NEXT 6.1.1 (API 24) Beta1 中,AppGallery Kit 新增了应用内快捷方式查询与管理能力,通过 `productViewManager.getPinShortcutInfos` 接口让应用透视桌面,实现“投放→检测→优化”的闭环。
## 能力来源与核心价值
该能力来自 AppGallery Kit 的 `Distribution` 分发服务体系。传统快捷方式由 `bundleManager` 或 `abilityManager` 单向投放,而新增的反向查询机制实现了链路透明化(应用得知哪些快捷路径已在桌面活跃)、防重复引导(检测到已固定则自动隐藏“添加到桌面”按钮)和集中管理控制台(提供快捷方式管理看板)。
## API 与数据结构拆解
核心接口位于 `@kit.AppGalleryKit` 的 `productViewManager` 模块:
- `getPinShortcutInfos(): Promise<PinShortcutInfo[]>`——异步查询桌面快捷方式列表。
- `removePinShortcut(context: common.UIAbilityContext, shortcutId: string): Promise<void>`——移除指定快捷方式,需传入 UIAbility 上下文和目标 ID。
物理约束:不支持模拟器,真机或远程真机调试;模拟器中会触发 801 熔断错误。
返回的 `PinShortcutInfo` 对象包含 `shortcutId`、`label`、`icon` 等运营元数据。
## 避坑与环境前置指南
该接口属于华为系统级增值 Kit,当前覆盖手机、平板、PC/二合一设备,自 API 22 起新增了对智慧屏的支持。在模拟器上调用会立即触发 `code: 801` 能力熔断,因此业务代码必须使用 `try...catch` 或 `.catch((error: BusinessError))` 进行防御性降级处理。
## 项目实战:构建桌面快捷管理看板
以下代码实现了一个完整的“桌面入口侦测仪表盘”,包含状态流转(空闲→加载中→真机渲染/模拟器降级)和交互操作(扫描、移除)。需注意:必须使用真机或远程真机调试,模拟器下会优雅降级。
- import { BusinessError } from '@kit.BasicServicesKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { productViewManager } from '@kit.AppGalleryKit';
- import { common } from '@kit.AbilityKit';
- interface LocalPinShortcutInfo {
- shortcutId: string;
- label?: string;
- icon?: string;
- iconId?: number;
- }
- const TAG: string = 'ShortcutsDemo';
- @Entry
- @Component
- struct Index {
- private context = getContext(this) as common.UIAbilityContext;
- @State shortcutList: LocalPinShortcutInfo[] = [];
- @State isLoading: boolean = false;
- @State sysLog: string = '系统就绪,等待执行桌面透射查询。';
- @State isSimulatorNotice: boolean = false;
- async doScanShortcuts() {
- this.isLoading = true;
- this.isSimulatorNotice = false;
- this.sysLog = '正通过系统管线向桌面发起检索请求...';
- try {
- const result = await productViewManager.getPinShortcutInfos();
- this.shortcutList = result as LocalPinShortcutInfo[];
- this.sysLog = `✅ 查询成功!当前桌面挂载数:${this.shortcutList.length}`;
- hilog.info(0x0001, TAG, 'Retrieved shortcuts payload successfully.');
- } catch (err) {
- const error = err as BusinessError;
- if (error.code === 801 || error.message?.includes('not supported')) {
- this.isSimulatorNotice = true;
- this.sysLog = '🛑 熔断提示:当前处于模拟器环境,已拦截 Kit 发起请求。';
- } else {
- this.sysLog = `❌ 查询异常 - Code: ${error.code}, Msg: ${error.message}`;
- }
- } finally {
- this.isLoading = false;
- }
- }
- async doRemoveShortcut(shortcutId: string) {
- this.sysLog = `⏳ 正在请求移除: ${shortcutId}...`;
- try {
- await productViewManager.removePinShortcut(this.context, shortcutId);
- this.sysLog = `✅ 成功从桌面移除快捷方式 ID: ${shortcutId}`;
- this.doScanShortcuts();
- } catch (err) {
- const error = err as BusinessError;
- this.sysLog = `❌ 移除失败 Code: ${error.code}`;
- }
- }
- build() {
- Column() {
- Row() {
- Text('桌面入口透视仪表盘')
- .fontColor('#FFFFFF')
- .fontSize(20)
- .fontWeight(FontWeight.Bold)
- }
- .width('100%')
- .height(64)
- .backgroundColor('#0F172A')
- .justifyContent(FlexAlign.Center)
- .shadow({ radius: 10, color: '#80000000' })
- Column() {
- Text(this.sysLog)
- .fontColor(this.isSimulatorNotice ? '#F87171' : '#FBBF24')
- .fontSize(13)
- .backgroundColor('#1E293B')
- .padding(12)
- .borderRadius(8)
- .width('100%')
- .margin({ bottom: 16 })
- .border({ width: 1, color: this.isSimulatorNotice ? '#F87171' : '#334155' })
- Button() {
- Row() {
- if (this.isLoading) {
- LoadingProgress().width(24).height(24).color('#000000').margin({ right: 8 })
- }
- Text(this.isLoading ? '正在扫描桌面线缆...' : '🚀 即刻扫描桌面快捷状态')
- .fontColor('#000000')
- .fontWeight(FontWeight.Bold)
- }
- }
- .width('100%')
- .height(50)
- .backgroundColor('#38BDF8')
- .onClick(() => this.doScanShortcuts())
- .margin({ bottom: 24 })
- Text('桌面已挂载资源列表')
- .fontSize(14)
- .fontColor('#94A3B8')
- .width('100%')
- .margin({ bottom: 12 })
- Scroll() {
- Column({ space: 12 }) {
- if (this.isSimulatorNotice) {
- this.EmptyTipUI('环境不匹配', 'Kit 分发服务仅能在 [鸿蒙真机] 触发,当前环境已熔断以保护系统稳定。')
- } else if (this.shortcutList.length === 0 && !this.isLoading) {
- this.EmptyTipUI('暂无数据', '您的桌面上目前没有通过此应用创建的快捷入口。')
- } else {
- ForEach(this.shortcutList, (item: LocalPinShortcutInfo) => {
- Row() {
- Circle({ width: 40, height: 40 })
- .fill('#334155')
- .margin({ right: 12 })
- .overlay(
- Text(item.label ? item.label.charAt(0) : 'S')
- .fontColor('#FFFFFF')
- .fontWeight(FontWeight.Bold)
- )
- Column() {
- Text(item.label || '未定义标签')
- .fontColor('#F8FAFC')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- Text(`ID: ${item.shortcutId}`)
- .fontColor('#64748B')
- .fontSize(12)
- .margin({ top: 4 })
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- Column() {
- Text('ACTIVE')
- .fontSize(10)
- .fontColor('#10B981')
- .border({ width: 1, color: '#10B981' })
- .borderRadius(4)
- .padding({ left: 6, right: 6, top: 2, bottom: 2 })
- .margin({ bottom: 8 })
- Button('移除')
- .fontSize(12)
- .backgroundColor('#DC2626')
- .height(24)
- .onClick(() => {
- this.doRemoveShortcut(item.shortcutId);
- })
- }
- .justifyContent(FlexAlign.Center)
- }
- .width('100%')
- .padding(16)
- .backgroundColor('#1E293B')
- .borderRadius(12)
- }, (item: LocalPinShortcutInfo) => item.shortcutId)
- }
- }
- .width('100%')
- }
- .layoutWeight(1)
- }
- .width('100%')
- .layoutWeight(1)
- .padding(16)
- }
- .width('100%')
- .height('100%')
- .backgroundColor('#020617')
- }
- @Builder EmptyTipUI(title: string, msg: string) {
- Column() {
- Image($r('sys.media.ohos_app_icon'))
- .width(48)
- .height(48)
- .opacity(0.2)
- .margin({ bottom: 16 })
- Text(title)
- .fontColor('#64748B')
- .fontSize(16)
- .fontWeight(FontWeight.Bold)
- .margin({ bottom: 8 })
- Text(msg)
- .fontColor('#475569')
- .fontSize(13)
- .textAlign(TextAlign.Center)
- .lineHeight(20)
- }
- .width('100%')
- .padding({ top: 60 })
- .alignItems(HorizontalAlign.Center)
- }
- }
复制代码
## 运行效果与验证策略
在 API 24 Beta1 的真机上运行:点击“即刻扫描桌面快捷状态”按钮,系统会秒速拉取数据并渲染卡片列表;若未创建过快捷方式,则显示“暂无数据”空提示。可通过系统接口手动钉一个页面到桌面,再次点击刷新即可看到实时跃动的卡片。
## 总结
AppGallery Kit 通过 `getPinShortcutInfos` 和 `removePinShortcut` 接口,将桌面入口的“所有权”和“知情权”交给开发者,解决了快捷方式“放出去却看不见”的难题。对于追求极致拉新回流、构建高频服务矩阵的应用团队,此接口是运营链路升级的关键一步。 |