App Shortcuts——实现应用快捷方式


之前玩手机的时候偶然发现了一个效果,长按应用图标会显示出几个快捷方式(类似IOS系统的3D Touch功能),点击后可以直接进入相应的页面,不需要点击应用图标来启动应用,并且快捷方式可以放到桌面上单独显示,就像下图这样:

索性就来研究一下这个效果是如何实现的,通过查阅网络上的资料,了解到该效果是通过Android 7.1(API level 25)版本的新特性App Shortcuts来实现的。纳尼!Android 7.1版本就已经添加了,我竟然现在才知道(o(╥﹏╥)o)。不过既然清楚了实现方式,下面就来具体研究一下这个App Shortcuts特性。

什么是App Shortcuts

App Shortcuts是Android 7.1的新特性,指长按app图标出现的快捷方式,可以为app的关键功能添加更快捷的入口而无需打开app,点击快捷方式可以访问相应的应用功能,,这种快捷方式也可以被拖拽到桌面单独放置,成为单独的桌面快捷方式。
每个快捷方式都引用了一个或多个Intent,点击快捷方式会启动Intent所指定的的操作,可以为任何Intent的操作创建一个快捷方式。

App Shortcuts

App Shortcuts的实现

Shortcuts的实现有三种形式:静态快捷方式动态快捷方式固定快捷方式,下面我们就具体看一下这三种类型的快捷方式分别是如何实现的。

静态快捷方式

静态快捷方式(Static shortcuts),是在资源文件中配置的快捷方式。创建静态快捷方式有以下几个步骤:
1)在资源文件res下新建xml目录,新建文件shortcuts.xml,文件名不是固定的。

<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:icon="@drawable/ic_settings"
        android:shortcutId="setting"
        android:shortcutLongLabel="@string/shortcut_setting_long"
        android:shortcutShortLabel="@string/shortcut_setting_short">
        <intent
            android:action="com.example.shortcutstest.SETTING"
            android:targetClass="com.example.shortcutstest.SettingActivity"
            android:targetPackage="com.example.shortcutstest" />
    </shortcut>
</shortcuts>

根节点shortcuts下可以添加一个或多个shortcut标签,每个shortcut表示一个快捷方式,内部配置了快捷方式的详细信息,包括名称、图标以及要启动的Intent等,shortcut标签常用的几个属性如下:
android:shortcutId:快捷方式的id,需要保证是唯一的。不能将此属性的值设置为资源字符串,例如@string/foo
android:shortcutLongLabel:快捷方式的长名称,如果有足够的空间,Launcher会优先显示长名称,如果可能,将长度限制为25个字符。此属性的值必须是资源字符串,例如@string/shortcut_long_label
android:shortcutShortLabel:快捷方式的短名称,长名称显示不下的情况下才会显示短名称,如果可能,将长度限制为10个字符。此属性的值必须是资源字符串,例如 @string/shortcut_short_label
android:icon:快捷方式的图标。
android:shortcutDisabledMessage:当用户尝试启动已禁用的快捷方式时提示的信息,用于向用户解释为什么禁用该快捷方式。如果android:enabled="true",即快捷方式是启用的,那么此属性的值无效。此属性的值必须是资源字符串,例如@string/shortcut_disabled_message
android:enabled:是否启用快捷方式,该值为true时,用户点击快捷方式可以启动指定的Intent操作;该值为false时,还需要指定android:shortcutDisabledMessage,当用户点击快捷方式时会提示该信息。禁用的快捷方式不会显示出来,因此如果要禁用快捷方式,一般还是直接从xml文件中移除比较好。
上面的几个属性中,android:shortcutIdandroid:shortcutShortLabel是必须要配置的,否则快捷方式不会显示,其他几个都是可选择的。
还需要在shortcut标签内部配置Intent,指定点击快捷方式时要执行的操作,和Activity的intent-filter类似,intent标签有以下几个属性:
android:action:指定Intent要启动的操作或任务,该属性必须要指定,否则不会显示快捷方式
android:targetClass:要跳转的目标类。
android:targetPackage:要跳转的目标应用包名。
关于Intent的配置测试了一下,如果只配置了action,在目标Activity的intent-filter中指定相同的action(注意不要忘记指定category为android.intent.category.DEFAULT),就像Activity的隐式跳转那样,点击快捷方式是可以启动Activity的;如果intent-filter中指定的action不匹配或者没有配置intent-filter,点击快捷方式会提示“未安装该应用”,这时如果同时配置了targetClass和targetPackage(两个都要配置,否则也会提示“未安装该应用”),就可以正常跳转了。
一个快捷方式可以配置多个Intent,点击快捷方式后会显示最后一个Intent指定的Activity,并将前几个Intent指定的Activity加入到返回栈中。比如下面的配置,点击快捷方式后,最后会显示SettingActivity,点击返回键会返回到MainActivity。

<intent
    android:action="android.intent.action.MAIN"
    android:targetClass="com.example.shortcutstest.MainActivity"
    android:targetPackage="com.example.shortcutstest" />
<intent
    android:action="com.example.shortcutstest.SETTING"
    android:targetClass="com.example.shortcutstest.SettingActivity"
    android:targetPackage="com.example.shortcutstest" />

此外,shortcut标签下还可以配置一个categories标签,为应用程序快捷方式执行的操作类型提供分组,例如创建新的聊天消息,目前官方只提供了android.shortcut.conversation
2)在AndroidManifest.xml文件中配置

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <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>
</application>

需要注意,配置快捷方式的Activity必须满足action是android.intent.action.MAIN并且category是android.intent.category.LAUNCHER,即应用启动页面。
这样就创建好了静态快捷方式,最终实现的效果如下:


静态快捷方式的配置虽然很简单,但也是有数量限制的,最多只能显示4个,如果配置了超过4个,多余的则不会显示,应用并不会报错。

动态快捷方式

除了静态快捷方式,系统还提供了更加灵活的方式来添加快捷方式,即动态快捷方式(Dynamic Shortcuts),可以在应用运行过程中添加快捷方式。官方提供了一个类ShortcutManager,可以实现快捷方式的创建、更新和删除等操作。

  • 创建快捷方式

首先通过getSystemService(ShortcutManager.class)获得ShortcutManager对象。然后使用ShortcutInfo.Builder来构建快捷方式,采用建造者模式配置快捷方式的信息,方法名都很清楚,和静态快捷方式的配置对照着看就可以,这里就不具体说了。最后调用ShortcutManager的setDynamicShortcuts()方法来设置快捷方式。完整代码如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
    ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
    Intent settingIntent = new Intent(this, SettingActivity.class);
    settingIntent.setAction("com.example.shortcutstest.SETTING");
    ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, "setting")
            .setShortLabel("设置")
            .setLongLabel("设置")
            .setIcon(Icon.createWithResource(this, R.drawable.ic_settings))
            .setIntent(settingIntent)
            .build();
    shortcutManager.setDynamicShortcuts(Arrays.asList(shortcutInfo));
}

如果需要配置多个Intent也是可以的,在构建ShortcutInfo时调用setIntents()方法就可以了,参数传入一个Intent数组。实现的效果和静态快捷方式一样,显示最后一个Intent指定的Activity,并将前几个Intent指定的Activity添加到返回栈中。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
    ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
    Intent mainIntent = new Intent(this, MainActivity.class);
    mainIntent.setAction(Intent.ACTION_MAIN);
    Intent settingIntent = new Intent(this, SettingActivity.class);
    settingIntent.setAction("com.example.shortcutstest.SETTING");
    ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, "setting")
            .setShortLabel("设置")
            .setLongLabel("设置")
            .setIcon(Icon.createWithResource(this, R.drawable.ic_settings))
            .setIntents(new Intent[]{mainIntent, settingIntent})
            .build();
    shortcutManager.setDynamicShortcuts(Arrays.asList(shortcutInfo));
}

动态快捷方式也是有数量限制的,调用ShortcutManager的getMaxShortcutCountPerActivity()方法可以获取Shortcut的最大个数(静态和动态总共),该方法返回值是5,也就是最多可以添加5个快捷方式,但是实际上每个Launcher最多只能显示4个快捷方式。针对快捷方式数量限制,我在自己的手机上测试了一下,情况如下:

1.静态快捷方式等于或超过5个,不配置动态快捷方式,应用不会报错,最后只显示4个快捷方式。
2.动态快捷方式配置5个,不配置静态快捷方式,应用不会报错,最后只显示4个快捷方式。
3..动态快捷方式超过5个,不配置静态快捷方式,应用抛出错误Max number of dynamic shortcuts exceeded
4.静态快捷方式+动态快捷方式等于5个,最后只显示4个快捷方式。
5.静态快捷方式+动态快捷方式超过5个,应用抛出错误Max number of dynamic shortcuts exceeded

由此可以发现,静态快捷方式和动态快捷方式最多能添加5个,但是实际上最多只能显示4个,因此我们还是应该控制快捷方式的数量不超过4个,否则可能会导致应用Crash。
对于快捷方式的显示顺序,最前面显示的是静态快捷方式,然后是动态快捷方式,对于同一类快捷方式,内部默认是按照添加的顺序排列的,即先添加的快捷方式显示在前面,后添加的显示在后面。这里所说的“前面”和“后面”是先相对于应用图标而言的,越靠近应用图标则顺序越靠前(靠上还是靠下与应用图标在屏幕中的位置有关),反之则靠后,如下图所示。

快捷方式排列顺序

可以通过ShortcutInfo的getRank()方法获得快捷方式的顺序,数值为非负的连续整数,从0开始,值越小则顺序越靠前。

// 获得所有的静态快捷方式
List<ShortcutInfo> staticShortcutList = shortcutManager.getManifestShortcuts();
for (ShortcutInfo shortcutInfo : staticShortcutList) {
    Log.e("TAG", "静态快捷方式" + shortcutInfo.getId() + "的顺序为:"
            + shortcutInfo.getRank());
}
// 获得所有的动态快捷方式
List<ShortcutInfo> dynamicShortcutList = shortcutManager.getDynamicShortcuts();
for (ShortcutInfo shortcutInfo : dynamicShortcutList) {
    Log.e("TAG", "动态快捷方式" + shortcutInfo.getId() + "的顺序为:"
            + shortcutInfo.getRank());
}

在构建ShortcutInfo时可以调用ShortcutInfo.Builde的setRank()方法来改变默认的顺序。虽然ShortcutInfo本身也有setRank()方法,但是该方法是@hide的,因此只有动态快捷方式才能改变显示顺序。
此外,addDynamicShortcuts()方法也可以用于添加动态快捷方式,从字面意思上我们也能知道它和setDynamicShortcuts()的不同,同样可以添加快捷方式,setDynamicShortcuts()会覆盖原有的快捷方式,而addDynamicShortcuts()是将快捷方式添加到原有的快捷方式列表中。

  • 更新快捷方式

通过ShortcutManager的updateShortcuts(List<ShortcutInfo> shortcutInfoList)方法可以更新现有的快捷方式,构建ShortcutInfo时需要指定相同的id,根据id去找到要更新的快捷方式。需要注意只有动态快捷方式和后面要说的固定快捷方式才能更新,如果传入了一个静态快捷方式的id,应用会抛出错误Manifest shortcut ID=XX may not be manipulated via APIs

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
Intent intent = new Intent(this, SettingActivity.class);
intent.setAction("com.example.shortcutstest.SETTING");
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, "setting")
        .setShortLabel("设置更新")
        .setLongLabel("设置更新")
        .setIcon(Icon.createWithResource(this, R.drawable.ic_settings))
        .setIntent(intent)
        .build();
shortcutManager.updateShortcuts(Arrays.asList(shortcutInfo));
  • 删除快捷方式

通过ShortcutManager的removeDynamicShortcuts(List<String> shortcutIds)方法可以删除动态快捷方式,参数传入要删除的快捷方式id列表。和更新一样,如果传入静态快捷方式的id会报错。

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
mShortcutManager.removeDynamicShortcuts(Arrays.asList("setting"));

此外,还有一个removeAllDynamicShortcuts()方法,可以用来删除所有的动态快捷方式。
需要注意,如果在删除之前将快捷方式固定到了桌面上(这种称作固定快捷方式,后面还会提到),在删除之后该快捷方式的图标不会消失,点击后依然可以完成正常跳转。这种固定在桌面上的快捷方式只能用户手动来移除,因此更好的一种做法是在删除快捷方式后判断该快捷方式是否被固定到了桌面上,如果是的话就再禁用该快捷方式,并提示用户该快捷方式已被删除,禁用快捷方式的方法下面会提到。

// 获得所有的固定快捷方式
List<ShortcutInfo> pinnedShortcutList = shortcutManager.getPinnedShortcuts();
for (ShortcutInfo shortcutInfo : pinnedShortcutList) {
    if (shortcutInfo.getId().equals("setting")) {
        // 禁用被删除的快捷方式
        mShortcutManager.disableShortcuts(Arrays.asList("call"), "该快捷方式已被删除");
    }
}
  • 禁用快捷方式

通过ShortcutManager的disableShortcuts()方法可以禁用快捷方式,第一个参数传入要禁用的快捷方式id列表,第二个参数是disabledMessage,用于提示用户禁用快捷方式的原因,该参数也可以不传。同样地,不能通过该方法禁用静态快捷方式,禁用静态快捷方式是通过在xml文件中配置来实现的。

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
shortcutManager.disableShortcuts(Arrays.asList("setting"), "该快捷方式已被禁用");

禁用之后,长按应用图标不会显示该快捷方式,如果此前将快捷方式固定到了桌面上,禁用后图标会变成灰色,点击图标会弹出提示信息,即上面传入的第二个参数disabledMessage,没有传的话就提示默认信息。

固定快捷方式

固定快捷方式(Pinned shortcuts)指的是在桌面上显示为一个图标的快捷方式,下图为官方给出的示意图。我们已经知道了可以通过拖拽来把创建好的静态/动态快捷方式固定在桌面上,接下来介绍一下如何直接创建固定快捷方式。

在Android 8.0(API level 26)及更高版本上,系统支持直接创建固定快捷方式,创建的步骤如下:
1)通过ShortcutManager的isRequestPinShortcutSupported()方法判断是否支持固定快捷方式,返回true表示支持,false表示不支持,该方法是Android 8.0才有的,因此在调用前需要判断一下。也可以使用ShortcutManagerCompat.isRequestPinShortcutSupported(context)方法,该方法兼容了Android 8.0以下版本。
2)创建一个ShortcutInfo对象,指定要固定的快捷方式,如果要固定的是已经创建好的快捷方式,那么在构建ShortcutInfo时只需要传id就可以了,需要注意固定的快捷方式不能是已禁用的,否则应用会报错;如果要固定一个新的快捷方式,就像创建动态快捷方式那样构建一个ShortcutInfo就好了。
3)通过requestPinShortcut()方法来固定快捷方式,该方法有两个参数对象,第一个参数是我们上面创建好的ShortcutInfo对象,第二个参数是一个IntentSender对象,可以通过PendingIntent.getIntentSender()来获得,当固定快捷方式成功后,会执行该Intent指定的操作,包括启动Activity、发送广播等,如果固定快捷方式成功后不需要做额外处理的话该参数传null就可以。
完整代码如下:

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
if (shortcutManager.isRequestPinShortcutSupported()) {
Intent intent = new Intent(this, SettingActivity.class);
intent.setAction("com.example.shortcutstest.NAVIGATION");
ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(this, "navigation")
        .setShortLabel("导航")
        .setLongLabel("导航")
        .setIcon(Icon.createWithResource(this, R.drawable.ic_navigation))
        .setIntent(intent)
        .build();
// 注册固定快捷方式成功广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.shortcutstest.PINNED_BROADCAST");
PinnedReceiver receiver = new PinnedReceiver();
registerReceiver(receiver, intentFilter);

Intent pinnedShortcutCallbackIntent = new Intent("com.example.shortcutstest.PINNED_BROADCAST");
PendingIntent successCallback = PendingIntent.getBroadcast(this, 0,
        pinnedShortcutCallbackIntent, 0);
shortcutManager.requestPinShortcut(pinShortcutInfo, successCallback.getIntentSender());

这里注册一个广播接收器,当固定快捷方式成功时会发送广播,我们可以在onReceive()方法中添加一些逻辑。

public class PinnedReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "固定快捷方式成功", Toast.LENGTH_SHORT).show();
    }
}

固定快捷方式会弹出一个提示对话框,用户点击确认后才会将快捷方式添加到桌面,并执行requestPinShortcut()方法第二个参数指定的操作。


和静态/动态快捷方式不同,固定快捷方式是没有数量限制的。动态快捷方式的更新和禁用方法也可以用在固定快捷方式中,但是固定快捷方式是无法通过代码动态删除的,只能通过用户的手动操作才能删除,以下三种方式可以删除固定快捷方式:

  • 长按图标移除
  • 进入应用设置页面,选择清除数据
  • 卸载应用

总结

本文主要介绍了Android 7.1版本的新特性——App shortcuts,即应用快捷方式,为用户提供了App常用功能的快捷入口,省去了打开App跳转指定页面的操作。快捷方式有三种形式:
1)静态快捷方式:在xml文件中配置,应用安装之后无法进行更新、删除和禁用,有数量限制。
2)动态快捷方式:在应用运行时通过代码动态创建快捷方式,可以动态更新、删除和禁用,有数量限制。
3)固定快捷方式:以桌面图标的形式显示,可以通过拖拽来把已创建好的静态/动态快捷方式固定在桌面上。Android 8.0及以上版本支持直接创建固定快捷方式,需要用户手动确认后才会创建桌面图标,可以动态更新和禁用,删除只能通过用户手动操作,没有数量限制。
最后说一下我自己的看法,虽然这个特性非常方便,但是它有一个很大的缺点就是不容易被发现,估计很多人可能还不知道这个功能,毕竟我们只有在卸载应用时才会长按应用图标,而且现在使用了该特性的应用也不是很多,连微信这种主流App都没有使用。作为开发者我们不光自己要了解这些特性,也应该让更多的人了解到这样的一些便捷特性。

本文的相关代码我已经上传到了github,大家如果有需要的话可以参考一下。
Demo地址

参考文章

App shortcuts官方文档
是时候来了解android7了:shortcuts(快捷方式)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,698评论 5 463
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,711评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,795评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,699评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,567评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,423评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,822评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,467评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,755评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,789评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,577评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,418评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,837评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,054评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,355评论 1 254
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,820评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,027评论 2 338

推荐阅读更多精彩内容