14、快捷方式(Shortcut):PinnedShortcut与DynamicShortcut的区别、Shortcut的创建与更新、Shortcut的图标与标签
快捷方式这东西,说白了就是用户快速打开某个功能的“直达电梯”。我刚开始做Launcher开发时,总觉得它不就是个图标嘛,后来踩了坑才明白——Shortcut的水挺深的。尤其是PinnedShortcut和DynamicShortcut,很多人搞混。
今天咱们就把这块彻底捋清楚。我会结合我实际项目中的经验,把创建、更新、图标、标签这些细节都掰开揉碎讲给你听。
14.1 PinnedShortcut vs DynamicShortcut:到底有啥区别?
先看一张图,帮你快速建立整体认知:
嗯,这张图基本把关系说清楚了。我挑重点讲:
- 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