乐于分享
好东西不私藏

Flutter 解锁 App Shortcuts:让应用入口快人一步

Flutter 解锁 App Shortcuts:让应用入口快人一步

人生路上我们总在寻找捷径,当然,是合乎道德的那一种。但如果我告诉你,在你的移动应用中也能实现这种“捷径”呢?

本文将深度剖析 App Shortcuts 的原理、类型以及在 Flutter 中的完整落地方案。


过在学习这件事上,可没有捷径可言,所以请确保你读到了最后。

一、什么是 App Shortcuts?

作为开发者,我们可以定义快捷方式(Shortcuts)让用户直接跳转到应用内的特定页面或流程,大幅减少操作步骤。

最终目的:通过提升导航效率,极致优化用户体验。

你一定见过这种交互

长按任何应用图标,都会弹出一个快捷菜单。这些快捷方式分为两类:

  1. 系统快捷方式:如“应用锁”、“卸载”、“分享”等,各应用表现一致。
  2. 应用自定义快捷方式:如“扫一扫”、“付款”,由开发者定义。

它们不仅出现在系统桌面,还能被 Google 助手等语音助手调用,让用户快速触达核心功能。

命名差异:在 Android 上称为 Shortcuts;在 iOS 上,桌面快捷方式通常被称为 Quick Actions(或 Home Screen Quick Actions)。


二、Shortcuts 的三大类型

类型
特点
修改时机
可见性
静态 (Static)
固定不变
仅限安装时定义,运行时不可改
安装后立即显示
动态 (Dynamic)
随用户行为变化
运行时可增、删、改
依赖应用逻辑,需 App 主动创建
固定 (Pinned)
用户钉在桌面
用户手动固定或代码请求固定
始终可见,直达体验

1. 静态快捷方式 (Static Shortcuts)

提供对常用、预定义操作的快速访问。它们在应用安装后立即可用,即使从未打开过 App。

特点:因在编译时定义,运行时无法修改。任何变更都需要发版。

常见示例:Gmail 的“收件箱”、YouTube 的“搜索”。

Android 实现 (shortcuts.xml)

在 res/xml/ 目录下创建 shortcuts.xml

<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">

    <shortcut
        android:shortcutId="new_post_static"
        android:enabled="true"
        android:icon="@mipmap/ic_launcher"
        android:shortcutShortLabel="@string/shortcut_new_post_short"
        android:shortcutLongLabel="@string/shortcut_new_post_long">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.example.app_shortcuts"
            android:targetClass="com.example.app_shortcuts.MainActivity">
            <extra android:name="shortcutId" android:value="new_post" />
            <extra android:name="source" android:value="static" />
        </intent>
    </shortcut>

    <shortcut
        android:shortcutId="test_shortcut_static"
        android:enabled="true"
        android:icon="@mipmap/ic_launcher"
        android:shortcutShortLabel="@string/shortcut_test_short"
        android:shortcutLongLabel="@string/shortcut_test_long">
        <!-- 省略 intent -->
    </shortcut>

</shortcuts>

注意:由于无法访问外部 URL,此处为等效的本地实现示例。
并在 AndroidManifest.xml 中引用:

<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />

iOS 实现 (Info.plist)

在 Info.plist 中添加 UIApplicationShortcutItems 数组:

<key>UIApplicationShortcutItems</key>
<array>
    <dict>
        <key>UIApplicationShortcutItemType</key>
        <string>new_post_static</string>
        <key>UIApplicationShortcutItemTitle</key>
        <string>New Post</string>
        <key>UIApplicationShortcutItemSubtitle</key>
        <string>Create a new post</string>
        <key>UIApplicationShortcutItemIconType</key>
        <string>UIApplicationShortcutIconTypeCompose</string>
        <key>UIApplicationShortcutItemUserInfo</key>
        <dict>
            <key>shortcutId</key>
            <string>new_post</string>
            <key>source</key>
            <string>static</string>
        </dict>
    </dict>
    <!-- 更多 Shortcut ... -->
</array>

处理点击:需要通过 MethodChannel 在原生侧捕获启动事件,传递给 Flutter 侧进行路由跳转(完整代码见文末)。


2. 动态快捷方式 (Dynamic Shortcuts)

基于用户交互和应用内活动动态变化。与静态不同,它们可以在运行时创建、更新或删除。

特点:安装后不会立即可见,需等待应用逻辑触发。

常见示例:从最近的存档点恢复游戏,或打开用户上次阅读到的书籍页码。

Flutter 实现 (quick_actions 包)

  1. 初始化:在 App 生命周期早期初始化,并设置回调。
    final QuickActions quickActions = const QuickActions();
    quickActions.initialize((shortcutType) {
      if (shortcutType == 'action_main') {
        print('用户点击了 "主视图" 快捷方式');
      }
      // 更多处理逻辑...
    });
  2. 管理快捷方式
    quickActions.setShortcutItems(<ShortcutItem>[
      const ShortcutItem(type: 'action_main', localizedTitle: '主视图', icon: 'icon_main'),
      const ShortcutItem(type: 'action_help', localizedTitle: '帮助', localizedSubtitle: '点击获取帮助', icon: 'icon_help'),
    ]);
  3. 清除所有动态快捷方式
    await quickActions.clearShortcutItems();

3. 固定快捷方式 (Pinned Shortcuts)

允许用户将特定操作直接钉在桌面上,实现一键直达。通常用于高度个性化或高频使用的功能。

示例:将特定聊天置顶、钉住支付流程入口。

  • 平台支持:主要支持 Android。iOS 无直接等价物,但可通过 Widget 或 Siri Shortcuts 实现类似效果。
  • 用户操作:长按应用图标 -> 长按快捷方式 -> 拖到桌面。

Flutter 实现 (pinned_shortcuts 包)

// 请求系统添加固定快捷方式。用户会看到系统确认弹窗。
// 注意:我们无法得知用户是接受还是取消,因此不要假设操作一定成功。
Future<void> createNetworkPinnedShortcut() {
  return FlutterPinnedShortcuts.createPinnedShortcut(
    id: AppConstants.defaultPinnedShortcutId,
    label: AppConstants.defaultPinnedShortcutLabel,
    imageSource: AppConstants.pinnedShortcutImageUrl,
    imageSourceType: ImageSourceType.network,
    adaptiveIconForeground: AppConstants.pinnedShortcutImageUrl,
    adaptiveIconBackground: AppConstants.pinnedShortcutBgColor,
    adaptiveIconBackgroundType: AdaptiveIconBackgroundType.color,
    extraData: const {'type': 'adaptive_color'},
  );
}

检测支持性与处理点击

Future<void> _initPinned() async {
  // 监听固定快捷方式的点击事件
  _pinnedSub = FlutterPinnedShortcuts.onShortcutClick.listen((data) {
    final id = data['id']?.toString();
    if (id == null) return;
    // 处理跳转逻辑...
  });

  await FlutterPinnedShortcuts.initialize();
  final supported = await FlutterPinnedShortcuts.isSupported();
  if (!supported) {
    // 部分启动器或旧设备不支持固定。静默退出,不影响其他功能。
    debugPrint('当前设备不支持固定快捷方式');
    await _pinnedSub?.cancel();
    _pinnedSub = null;
    return;
  }
}

三、集成前的关键考量

  1. 数量限制:可见的快捷方式数量受 OS 或启动器限制。通常最多显示 4 个(静态与动态混合)。
  2. 静态不可变:静态快捷方式在运行时无法修改,任何更改都需要应用更新。
  3. 动态排名:动态快捷方式的可见性取决于系统排名因素(如使用历史、最近使用、频率)。
  4. 排序规则:静态快捷方式遵循 XML 中的定义顺序;动态快捷方式通过代码分配排名。
  5. 固定快捷方式的差异:它们可以支持原生资源、Flutter 资源甚至网络图片(取决于实现)。
  6. quick_actions 包的局限性
    • 不支持禁用快捷方式。
    • 不暴露备份相关标志。
    • 对于此类高级用例,需使用 平台通道 (Platform Channels) 自行实现。

四、最佳实践

  1. 确保唯一 ID:如果新快捷方式使用了现有 ID,它将更新旧的那个。
  2. 优先使用 Deep Link:对于动态快捷方式,使用深度链接而非硬编码的路由字符串。这能让你对导航有更好的灵活性和控制力。
  3. 妥善处理冷启动与热启动:确保快捷方式回调在 App 未运行(冷启动)和已运行(热启动)时都能正确初始化并跳转。
  4. 使用有意义的图标:静态和动态快捷方式应引用原生资源(iOS 的 ‎xcassets 或 Android 的 ‎drawable 资源)。
  5. 保持动态快捷方式的相关性:根据用户的近期行为和频率更新它们。
  6. 拓展至搜索与助手:若想在启动器之外(如 Google 搜索)展示快捷方式,可集成 Google Shortcuts Integration Library

五、结语

有人创建了一个示例应用,演示了静态、动态和固定快捷方式的实现,并处理了冷启动与热启动场景。它还包含使用 SharedPreferences 进行持久化的逻辑。感兴趣的可以看看https://github.com/anupam92402/app_shortcuts

掌握 App Shortcuts 不仅能提升你应用的用户体验,更是让你的产品融入系统生态、提升用户留存的关键一步。


💡 架构师视角 (Bonus)

  1. Deep Link 路由设计
    处理快捷方式点击的核心在于路由。推荐使用 ‎go_router 等支持深度链接的路由库,将快捷方式传递的 ‎extraData 映射为统一的路由路径。

  2. 原生与 Flutter 的桥梁
    对于 ‎quick_actions 或 ‎pinned_shortcuts 包未覆盖的高级功能(如禁用、备份标志),你需要编写 Platform Channel (‎MethodChannel) 代码,在 Android 侧调用 ‎ShortcutManager 或 iOS 侧调用 ‎UIApplication 的相关 API。

  3. 国内 Android 生态适配
    华为、小米、OPPO 等定制 ROM 对快捷方式的支持程度和交互略有差异。务必在主流国产机型上进行真机测试,并参考各厂商的桌面快捷方式适配文档