14、快捷方式(Shortcut):PinnedShortcut与DynamicShortcut的区别、Shortcut的创建与更新、Shortcut的图标与标签

快捷方式这东西,说白了就是用户快速打开某个功能的“直达电梯”。我刚开始做Launcher开发时,总觉得它不就是个图标嘛,后来踩了坑才明白——Shortcut的水挺深的。尤其是PinnedShortcut和DynamicShortcut,很多人搞混。

今天咱们就把这块彻底捋清楚。我会结合我实际项目中的经验,把创建、更新、图标、标签这些细节都掰开揉碎讲给你听。

14.1 PinnedShortcut vs DynamicShortcut:到底有啥区别?

先看一张图,帮你快速建立整体认知:

Android Shortcut 体系结构 ShortcutManager Static Shortcut 在 XML 中声明,不可动态修改 Dynamic Shortcut 运行时创建/更新/删除 Pinned Shortcut 用户手动固定到桌面 不可被用户删除 最多 4 个(短时) 可被用户长按拖拽固定 最多 4 个(短时) 用户主动固定,不可被移除 数量无限制 核心区别:Dynamic 由 App 控制生命周期,Pinned 由用户控制是否保留 PinnedShortcut 即使 App 卸载再重装,只要 ShortcutId 不变,系统会尝试恢复

嗯,这张图基本把关系说清楚了。我挑重点讲:

  • Static Shortcut:写在 res/xml/shortcuts.xml 里,编译时就定死了。适合“新建笔记”、“扫码”这种永远不变的功能。
  • Dynamic Shortcut:运行时通过 ShortcutManager 动态增删改。比如“最近打开的文档”、“常用联系人”。
  • Pinned Shortcut:用户长按应用图标,手动拖到桌面上的那个。App 可以请求用户固定,但不能强制。

最关键的差异:DynamicShortcut 可以被系统自动清理(比如用户 7 天没用),但 PinnedShortcut 一旦被用户固定到桌面,App 无权删除它。我曾经有个项目,产品经理要求“用户取消订阅后自动移除桌面快捷方式”,我解释了半天——技术上做不到,除非用户自己动手。

14.2 创建 Shortcut:两种方式,各有讲究

14.2.1 静态创建(Static Shortcut)

这种方式最简单,适合那些“永远存在”的入口。在 AndroidManifest.xml 里声明:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <meta-data
        android:name="android.app.shortcuts"
        android:resource="@xml/shortcuts" />
</activity>

然后在 res/xml/shortcuts.xml 里定义:

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:shortcutId="new_note"
        android:enabled="true"
        android:icon="@drawable/ic_new_note"
        android:shortcutShortLabel="新建笔记"
        android:shortcutLongLabel="快速新建一篇笔记"
        android:shortcutDisabledMessage="该功能暂不可用">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.example.app"
            android:targetClass="com.example.app.NewNoteActivity" />
        <!-- 支持多 intent,实现回退栈 -->
        <intent ... />
    </shortcut>
</shortcuts>

我的习惯:Static Shortcut 的 shortcutLongLabel 不要超过 25 个字符,否则在部分 Launcher 上会被截断。我吃过这个亏,测试时没注意,上线后被用户吐槽“标签显示不全”。

14.2.2 动态创建(Dynamic Shortcut)

动态创建才是重头戏。比如用户打开了一个文档,你想把它加到快捷方式列表里:

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);

ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "doc_123")
    .setShortLabel("项目方案 v3")
    .setLongLabel("打开项目方案文档 v3")
    .setIcon(Icon.createWithResource(context, R.drawable.ic_doc))
    .setIntent(new Intent(Intent.ACTION_VIEW)
        .setClass(context, DocumentActivity.class)
        .putExtra("doc_id", "123"))
    .build();

shortcutManager.addDynamicShortcuts(Arrays.asList(shortcut));

你想想看,这样做的好处是什么?用户下次想打开这个文档,不用进 App 翻半天,直接长按图标就看到了。

注意:Android 7.1(API 25)以上才支持 Shortcut。低版本怎么办?我建议用 ShortcutManagerCompat 做兼容处理,或者干脆不展示。别硬上,否则会 Crash。

14.3 更新 Shortcut:别让用户看到过时信息

Shortcut 不是创建完就完事了。比如文档被重命名了,或者状态变了,你得及时更新。

更新 Dynamic Shortcut 很简单:

ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, "doc_123")
    .setShortLabel("项目方案 v4(已审核)")
    .setLongLabel("打开已审核的项目方案 v4")
    .build();

shortcutManager.updateShortcuts(Arrays.asList(updatedShortcut));

注意,updateShortcuts() 会匹配 shortcutId。如果 ID 不存在,它不会创建新的,而是静默忽略。所以更新前最好确认一下。

那 Pinned Shortcut 怎么更新?

嗯,这里有个坑。Pinned Shortcut 的更新方式跟 Dynamic 一样,也是用 updateShortcuts()。但问题是——你根本不知道用户到底固定了哪些 Pinned Shortcut。我曾经在项目里加了一个日志,发现有些用户固定了 5、6 个,有些一个都没固定。

我的做法是:每次启动 App 时,调用 getPinnedShortcuts() 获取当前固定的列表,然后逐个更新。这样既不会漏掉,也不会做无用功。

List<ShortcutInfo> pinned = shortcutManager.getPinnedShortcuts();
for (ShortcutInfo info : pinned) {
    // 根据 info.getId() 判断是否需要更新
    if (shouldUpdate(info.getId())) {
        ShortcutInfo updated = buildUpdatedShortcut(info.getId());
        shortcutManager.updateShortcuts(Arrays.asList(updated));
    }
}

14.4 Shortcut 的图标与标签:细节决定体验

图标和标签是用户第一眼看到的东西。做不好,用户根本不想点。

14.4.1 图标规范

类型 建议尺寸 格式 说明
自适应图标 108x108 dp Vector Drawable / PNG Android 8.0 以上推荐
传统图标 48x48 dp PNG 兼容低版本
Bitmap 图标 按屏幕密度缩放 Bitmap 动态生成,比如用户头像

我个人建议:能用 Vector 就用 Vector。为什么?因为适配简单,一个文件搞定所有密度。而且自适应图标在 Android 8.0 以上的 Launcher 上会有漂亮的形状遮罩,视觉效果更好。

避坑指南:我曾经用 Bitmap 直接设置用户头像作为 Shortcut 图标,结果在部分机型上图标变得模糊。后来发现是没做屏幕密度适配。正确的做法是用 Icon.createWithAdaptiveBitmap(),系统会自动帮你裁剪和缩放。

14.4.2 标签长度控制

标签分两种:

  • ShortLabel:必填,最多 10 个字符。显示在图标下方。
  • LongLabel:可选,最多 25 个字符。在 Launcher 的“长按菜单”中显示。

如果 LongLabel 没设置,系统会自动用 ShortLabel 代替。但我不建议偷懒——长按菜单空间大,多写几个字能让用户更清楚这个快捷方式的功能。

14.4.3 动态图标:让 Shortcut “活”起来

你还可以给 Shortcut 设置动态图标。比如天气 App,快捷方式直接显示当前温度:

Bitmap bitmap = generateTemperatureBitmap("26°C");
Icon icon = Icon.createWithBitmap(bitmap);

ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "weather")
    .setShortLabel("天气")
    .setIcon(icon)
    .setIntent(new Intent(context, WeatherActivity.class))
    .build();

不过要注意,动态图标不要更新太频繁。我见过一个 App 每 5 分钟刷新一次 Shortcut 图标,结果导致 Launcher 频繁重绘,桌面掉帧严重。建议至少间隔 30 分钟以上。

14.5 实战经验总结

最后,分享几个我在项目中积累的经验:

  • Shortcut 数量限制:Dynamic + Static 总共不能超过 4 个(短时)。Pinned 无限制。如果超过 4 个,addDynamicShortcuts() 会抛出异常。记得用 getMaxShortcutCountPerActivity() 检查。
  • 卸载重装后的行为:Dynamic Shortcut 会丢失,但 Pinned Shortcut 会保留(系统会显示一个默认图标)。如果你改了 shortcutId,旧的 Pinned Shortcut 就变成“死链接”了。所以 shortcutId 一旦发布,尽量不要改。
  • 测试技巧:用 adb shell dumpsys launcher_inject 可以查看当前所有 Shortcut 的状态。我在调试时经常用这个命令,比看 Log 直观多了。

一句话总结:Static 用于固定功能,Dynamic 用于动态内容,Pinned 交给用户决定。图标用 Vector,标签别超长,更新别太勤。做到这几点,Shortcut 这块基本不会出大问题。


公众号:蓝海资料掘金营,微信 deep3321