Android 7.1 新特性:快捷方式 Shortcuts 详解
一、Shortcuts 介绍
Android 7.1 允许 App 自定义 Shortcuts,类似 iOS 的 3D touch。通过在桌面长按 App 弹出 Shortcut 列表,点击某个 Shortcut 快速进入某项操作,同时 Shortcut 可以拖动到桌面进行固定,如下图:
1. Shortcuts 作用及分类
Shortcuts 为 App 常用操作提供了快速访问的方式,如上面日历的新建提醒。
这个功能目前只能在 Android 7.1 系统桌面进行使用,这个依然保留着“应用抽屉”古老设计的产品国内应该没多少用户。三方桌面可以通过 API 接入这个功能。
目前支持 Shortcut 的应用主要还是 Google 的 App,看到有即刻的朋友说他们在 7.1 系统发布时快速支持了这个功能并上线,速度很赞。
类似 BroadcastReceiver 可通过静态和动态方式注册,Shortcuts 也可以通过静态和动态方式添加。
2. 静态 Shortcuts(Static Shortcuts)
静态 ShortcutsStatic Shortcuts通过在 Manifest 中声明添加。缺点是不可以修改,只能通过应用升级来添加新的静态 Shortcuts。添加主要分为两步:
2.1 AndroidManifest.xml 的 Main Launcher 对应的 Activity 内添加 meta-data meta-data name
为android.app.shortcuts
,如下:
<application
……>
<activity android:name=".main.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>
</application>
必须在 Main Launcher 对应的 Activity 内设置,其中android:resource指向定义了 shortcuts 的资源文件。
2.2 资源文件中定义具体的 shortcuts
res 目录下新建 xml 文件夹,并新建 shortcuts.xml 文件,内容如下:
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/search"
android:shortcutId="search"
android:shortcutDisabledMessage="@string/disabled"
android:shortcutLongLabel="@string/menu_label"
android:shortcutShortLabel="@string/launcher_label">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="cn.trinea.android.demo.SearchActivity"
android:targetPackage="cn.trinea.android.demo"/>
<intent
……/>
</shortcut>
……
</shortcuts>
以shortcuts元素为根,可以包含多个shortcut元素,每个shortcut元素表示一个 shortcut。其中属性分别表示:
- shortcutId表示 shortcut 唯一标识符,相同的 shortcutId 会被覆盖。必须字段。
- shortcutShortLabel为将 shortcut 拖动到桌面时显示的名字,官方建议不超过 10 个字符,必须字段。
- shortcutLongLabel为 shortcut 列表中每个 shortcut 的名字,不宜过长,如果过长或未设置默认会显示 ShortLabel,官方建议不超过 25 个字符。可选字段。
- icon为 shortcut 的 icon,在列表展示和拖动到桌面时显示需要,可选字段。
- enabled表示 shortcut 是否可用,false 表示禁用。xml 中这个属性几乎没有被设置为 false 的实际场景,具体原因可见6.7 如何更好的删除(废弃)老的 Shortcut中介绍。
- shortcutDisabledMessage为已固定在桌面的 shortcut 被 Disabled 后点击时的 Toast 提示内容。可选字段。
- intent为点击 shortcut 时响应的 Intent,必须字段。
这里可以添加多个 Intent,但点击时不会启动所有 Intent,而是启动最后一个 Intent,在这个 Intent 回退时会启动它前面一个 Intent,相当于自动将所有 Intent 添加到了堆栈。
对于先跳转到某个页面,Back 键希望退回主页而不是结束 App 这类场景,多个 Intents 挺实用的。
intent可设置属性包括:
android:action、android:data、android:mimeType、android:targetClass、android:targetPackage
其中android:action为必须属性。
3. 动态 Shortcuts(Dynamic Shortcuts)
动态 ShortcutsDynamic Shortcuts 通过 ShortcutManager API 进行操作。可以动态添加、修改、删除。
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
return;
}
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "id1")
.setShortLabel("trinea.cn")
.setLongLabel("Open trinea.cn")
.setDisabledMessage("Disabled")
.setIcon(Icon.createWithResource(context, R.drawable.trinea_cn))
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.trinea.cn/")))
.build();
shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
通过ShortcutInfo.Builder新建 ShortcutInfo,再通过shortcutManager添加即可。其他:
- setDynamicShortcuts(List)可以替换并添加所有 shortcut 列表;
- addDynamicShortcuts(List)可以添加新的 shortcut 到列表,超过最大个数会报异常;
- updateShortcuts(List)可以更新一组 shortcuts;
- removeDynamicShortcuts(List)和removeAllDynamicShortcuts() 可以删除部分或所有 shortcuts。
ShortcutInfo的属性与 xml 中定义字段含义一致,shortcutId shortcutShortLabel intent 是必须设置的字段,并且intent必须设置Action。
4. 固定的 Shortcuts(Pinned Shortcuts)
指通过拖动固定到桌面的 Shortcuts,App 不可以添加、修改、删除这些 Shortcuts,只能禁用他们。即便 App 内删除了某个 Shorcut,对应的已固定到桌面的 Shortcuts 也不会被删除。
可以通过:
- getPinnedShortcuts()得到所有固定的 Shortcuts 的信息。
- disableShortcuts(List)或disableShortcuts(List, CharSequence)禁用动态的 Shortcuts。
对于静态的 Shortcuts 需要在资源文件中设置android:enabled="false"
进行禁用,不过没有必要,静态 Shortcuts 可直接通过删除达到禁用的效果,具体原因可见6.7 如何更好的删除(废弃)老的 Shortcut中介绍。
静态 Shortcuts 和动态 Shortcuts 是有最大个数限制的,默认为 5,超过最大个数后添加会报异常。而固定的 Shortcuts 并没有个数限制,并且固定的 Shortcut 对应的 Shortcut 即便被动态删除了,依然可以通过 id 进行 Update 操作。
5. 其他
5.1 动态 Shortcuts 与静态 Shortcuts 区别
- 静态 Shortcuts 只能通过升级应用修改,动态 Shortcuts 随时可以修改;
- 静态 Shortcuts 的 Intent 无法设置 Flag,默认为
FLAG_ACTIVITY_NEW_TASK
和FLAG_ACTIVITY_CLEAR_TASK Flag
,即若应用运行中会清除所有已存在的 Activity。动态 Shortcuts 的 Intent 可以设置 Flag; - 静态 Shortcuts 的rank系统默认根据声明顺序设置,动态 Shortcuts 的rank可以通过setRank(int rank)接口主动设置,rank 不能小于 0,值越大表示在 shortcut 列表展示时离 App Icon 越远。静态 Shortcuts 默认比动态 Shortcuts 离 App Icon 更近。
- 静态 Shortcuts 删除可以直接删除,动态 Shortcuts 建议通过禁用删除;
5.2 动态 Shortcuts 操作的频率问题
当应该完全退到后台(无 Activity 或 Service 在前台时),其操作 Shortcut(包括添加、删除、修改) 的频率是受限的。可通过isRateLimitingActive()查询是否已受限,true表示已受限。
5.3 跟踪 Shorcut 使用情况
在 Shortcut 被选择或者其关联的操作被操作时需调用reportShortcutUsed(String shortcutId)接口上报数据,为了方便启动器收集应用 Shortcuts 使用情况,以便未来进行预测或者向开发者展示哪些操作适合作为 Shortcuts 以及其优先级。
PS:这个接口其实挺尴尬的,一方面需要 App 主动上报,侵入性太强。另一方面这个预测功能未来也不好加到 Shortcuts 推荐里,更多是个开发工具相关功能。
最好是由启动器自己纯粹收集 Shortcut 被选择的使用情况数据,而不需要统计 Shortcut 被关联操作通过其他方式调用的使用情况数据。至于哪些操作适合作为 Shortcuts,开发者大可通过其他监控 SDK 去判断。
5.4 应用备份
如果应用通过备份恢复到另外一台机器上,固定的 Shortcuts 是可以直接恢复的,不过启动器不保存这些 Shortcut 的 icon,所以应用内需要存在这些 icon 对应的资源以便启动器能找到。
静态 Shortcuts 需要应用重新安装、升级才能生效。
动态 Shortcuts 需要相应代码被执行过才能生效。
二、Shortcuts 一些实践&问题
6. 最佳实践
这块官网已经给出了一部分建议,包括:
- 设计上和系统 App 的 Shortcuts 保持一致。
- 最多添加 4 个 Shortcuts 以保持在启动器中显示的样式最佳
目前虽然说是 5 个,但实际最多只能添加 4 个,可见7.2 Shortcut 添加或修改无效中介绍。 - 限制 Label 长度
其中shortcutShortLabel建议不超过 10 个字符,shortcutLongLabel 建议不超过 25 个字符。这块可能有些问题,可见7.1 LongLabel 和 ShortLabel中介绍。 - 记录 Shortcut 及其对应操作使用记录。
这个在5.3 跟踪 Shorcut 使用情况中已经介绍了。 - 只在 Shortcut 意义不变的情况下更新,否则新增。
- 动态 Shortcuts 在 BackUp 恢复后不可以直接恢复,考虑适时新增或更新已有的 Shortcuts
除了以上这些外,个人觉得还有几点需要遵守: - 如何更好的删除(废弃)老的 Shortcut
这里主要考虑到删除老的 Shortcut,可能会影响已经固定的 Shortcut。
对于静态 Shortcuts,直接删除配置文件中对应的 Shortcut 即可,系统桌面会将已固定的该 Shortcut 置灰,点击会提示 shortcutDisabledMessage。
对于动态 Shortcuts 建议通过禁用的方式而不是直接删除的方式,因为已经删除的动态 Shortcut 如果被固定了依然是可用的,所以希望该入口不可用最好的方式是禁用。 - 始终设置shortcutDisabledMessage
根据上面的介绍废弃老的 Shortcut 较好的方式是禁用,通过自定义shortcutDisabledMessage去更友好的提示用户。 - 动态添加 Shortcut 前需要判断 API 版本不小于 25
否则在低版本会报 ClassNotFoundException 异常。
7. 一些问题
7.1 LongLabel 和 ShortLabel
LongLabel和ShortLabel的含义,官方 API 文档解释的并不是很清楚。
在 Nexus 6 上测试,当 LongLabel 长度大于 17 个小写字符时,会显示 ShortLabel,而不是 LongLabel。这里的界限长度跟大小写、空格都有关,应该是受限于桌面 Shortcuts 列表 Item 的宽度!
7.2 Shortcut 添加、修改、点击无效
可能原因:
- shortcutId 被覆盖
shortcutId 是唯一标识,相同 shortcutId 会被覆盖。 - intent 不对
intent 必须设置 android:action 属性,同时目标 Activity 必须有效即已在 Manifest 中声明。 - 后台 App 有频率限制
当应该完全退到后台(无 Activity 或 Service 在前台时),其操作 Shortcut(包括添加、删除、修改) 的频率是受限的。可通过isRateLimitingActive()查询是否已受限,true表示已受限。
若已受限,可通过开发者选项中“重置 ShortcutManager 调用频率限制”或命令行adb shell cmd shortcut reset-throttling [ --user USER-ID ]
解决。 - Shortcut 个数限制
虽然官方文档介绍静态和动态 Shortcut 总和不能超过 5 个,通过getMaxShortcutCountPerActivity()得到的也是 5,但实际测试下来是不超过4个!即静态和动态shortcuts加起来总数最多是五个.
当我们尝试添加第六个shortcut时, 应用会抛出异常:
java.lang.IllegalArgumentException: Max number of dynamic shortcuts exceeded.
虽然总数限制是5个, 但是当我正好有5个(2个静态 + 3个动态)的时候, 长按只显示了4个shortcuts.
7.3 getIntents() 有 Bug
从 getIntents() 实现 中可以看出未做mIntents是否为 null 及 empty 的判断,在 null 时会出现:
java.lang.NullPointerException: Attempt to get length of null array
的异常。
8. 三方桌面支持 Shortcuts——LauncherApps
如果三方桌面希望支持这个特性,请参考 LauncherApps API 介绍,不过只有系统默认桌面才有权限得到其他 App Shortcuts 信息。
可通过hasShortcutHostPermission()查看是否拥有权限,如果没有权限,会报如下异常:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xx/com.xx.XXActivity}:
java.lang.SecurityException: Caller can't access shortcut information
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
通过getShortcuts API 获取到所有 Shortcuts 信息。
参考:
App Shortcuts的官方文档: App Shortcuts
Exploring Android Nougat 7.1 App Shortcuts