Android 7.1 - App Shortcuts

版权声明:本文为博主原创文章,未经博主允许不得转载。

微博:厉圣杰

源码:AndroidDemo/Shortcuts

文中如有纰漏,欢迎大家留言指出。

Android 7.1 新功能之一就是 App Shortcuts(应用快捷方式) ,该功能与 iPhone 上的 3D Touch 功能相似,通过长按应用图标,可弹出应用快捷方式,点击可以直接跳转到相应的界面。目前最多支持 5 个快捷方式,可以 getMaxShortcutCountPerActivity() 查看 Launcher 最多支持几个快捷方式,不同的是 Android 支持通过拖拽将快捷方式固定到桌面。

看似美好,其实应用快捷方式还是有很多缺陷的:

  1. 只能在 Google 的 Nexus 及 Pixel 设备上使用

  2. 系统必须是 Android 7.1 及以上(API Level >= 25)

  3. 已经被用户固定到桌面的快捷方式必须得到兼容性处理,因为你基本上失去了对其控制,除了升级时禁用

    Launcher applications allow users to "pin" shortcuts so they're easier to access. Both manifest and dynamic shortcuts can be pinned. Pinned shortcuts cannot be removed by publisher applications; they're removed only when the user removes them, when the publisher application is uninstalled, or when the user performs the "clear data" action on the publisher application from the device's Settings application.

    However, the publisher application can disable pinned shortcuts so they cannot be started. See the following sections for details.

应用快捷方式可分为 Static Shortcuts(静态快捷方式)Dynamic Shortcuts(动态快捷方式) 两种。

  • 静态快捷方式:又名 Manifest Shortcuts,在应用安装时创建,不能实现动态修改,只能通过应用更新相应的 XML 资源文件才能实现更新。
  • 动态快捷方式:应用运行时通过 ShortcutManager 实现动态添加、删除、禁用等操作。

下面分别来讲述如何创建静态快捷方式和动态快捷方式。

创建静态快捷方式

  1. 在 /res/xml 目录下创建 shortcuts.xml ,添加根元素 <shortcuts> ,其包含一组 <shortcut> 标签。每个 <shortcut> 标签为一个静态快捷方式,它包含相应的图标、描述以及对应的 intent

    <shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
    android:shortcutId="compose"
    android:enabled="true"
    android:icon="@drawable/compose_icon"
    android:shortcutShortLabel="@string/compose_shortcut_short_label1"
    android:shortcutLongLabel="@string/compose_shortcut_long_label1"
    android:shortcutDisabledMessage="@string/compose_disabled_message1">
    <intent
    android:action="android.intent.action.VIEW"
    android:targetPackage="com.example.myapplication"
    android:targetClass="com.example.myapplication.ComposeActivity" />
    <!-- If your shortcut is associated with multiple intents, include them
    here. The last intent in the list is what the user sees when they
    launch this shortcut. -->
    <categories android:name="android.shortcut.conversation" />
    </shortcut>
    <!-- Specify more shortcuts here. -->
    </shortcuts>
  2. 打开 AndroidManifest.xml 文件,找到其中 <intent-filter> 被设置为 android.intent.action.MAINandroid.intent.category.LAUNCHER 的 Activity

  3. 给这个 Activity 添加 <meta-data> ,引用资源 shortcuts.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">
    <application ... >
    <activity android:name="Main">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <!-- 在 meta-data 中设置 shortcuts -->
    <meta-data android:name="android.app.shortcuts"
    android:resource="@xml/shortcuts" />
    </activity>
    </application>
    </manifest>

补充:注意第 2 点的描述,也就是说如果 Manifest 中存在多个满足条件的 Activity ,那么就可以存在多组应用快捷方式,但资源文件必须不同,主要是 shortcutId 必须不同,否则不会显示。大家可以自己去尝试下~

标签属性含义如下:

shortcutId:快捷方式的唯一标识。
当用户拖拽快捷方式到桌面,只要 shortcutId 不变,修改 <shortcut> 其余属性值,重新打包,修改之后的变化会体现到已拖拽到桌面的快捷方式上。
若存在多个 <shortcut> 但 shortcutId 相同,则只会显示一个 icon:快捷方式图标 shortcutShortLabel:快捷方式的 Label,当用户拖拽快捷方式到桌面时,显示的也是该字符串 shortcutLongLabel:长按 app 图标出现快捷方式时显示的图标 shortcutDisabledMessage:当快捷方式被禁用时显示的提示语 enabled:标识快捷方式是否可用,可用时,快捷方式图标正常显示,点击跳转到对应的 intent ;不可用时,快捷方式图标变灰,点击弹出 shortcutDisabledMessage 对应字符串的 Toast intent:快捷方式关联的 intent,当用户点击快捷方式时,列表中所有 intent 都会被打开,但用户看见的是列表中最后一个 intent 。 categories:用于指定 shortcut 的 category,目前只有 SHORTCUT_CATEGORY_CONVERSATION 一个值 补充:如果 <shortcuts> 中只有一个 <shortcut> 且 enabled = false ,那么长按 app 图标是不会弹出任意快捷方式

gradle 配置

apply plugin: 'com.android.application'

android {
//如果只是创建静态快捷方式,那么版本号任意
//即使 compileSdkVersion 、targetSdkVersion 为 23 ,在 Android 7.1 的 Nexus 和 Pixel 设备上也能使用。
//但是如果是创建动态快捷方式,因为则必须使 compileSdkVersion 为 25
compileSdkVersion 25
buildToolsVersion "25.0.0" defaultConfig {
applicationId "com.littlejie.shortcuts"
minSdkVersion 23
targetSdkVersion 25
versionCode 1
versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" }
// something else ...
}

关于 compileSdkVersion 、 minSdkVersion 以及 targetSdkVersion 的区别可参考这篇文章

创建动态快捷方式

创建动态快捷方式主要依靠 ShortManagerShortcutInfoShortcutInfo.Builder 这几个类来实现。ShortcutInfo 和 ShortcutInfo.Builder 主要用来构造快捷方式对象, ShortManager 是一个系统服务,用于管理应用快捷方式,ShortManager 可以通过以下方式获取:

ShortManager shortManager = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);

ShortManager 主要有以下几个功能:

  • 发布:通过调用 setDynamicShortcuts(List) 替换整个快捷方式列表或者通过 addDynamicShortcuts(List) 往已存在的快捷方式列表中添加快捷方式。
  • 更新:调用 updateShortcuts(List) 来更新已存在的快捷方式列表
  • 移除:调用 removeDynamicShortcuts(List) 移除列表中指定快捷方式,调用 removeAllDynamicShortcuts() 移除列表中所以快捷方式。
  • 禁用:因为用户可能将您任意的快捷方式拖拽到桌面,而这些快捷方式会将用户引导至应用中不存在或过期的操作,所以可以通过调用 disableShortcuts(List) 来禁用任何已存在的快捷方式。调用 disableShortcuts(List, Charsquence) 会给出错误提示。

下面代码主要演示了使用 ShortManager 实现动态发布、更新、移除以及禁用快捷方式。

动态创建快捷方式核心代码:

public class MainActivity extends Activity implements View.OnClickListener {

    private static final String TAG = MainActivity.class.getSimpleName();
private ShortcutManager mShortcutManager;
private ShortcutInfo[] mShortcutInfos; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //这里常量Context.SHORTCUT_SERVICE会报错,不用管,可正常编译。看着烦的话把minSdkVersion改为25即可
mShortcutManager = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE); mShortcutInfos = new ShortcutInfo[]{getShoppingShortcut(), getDateShortcut()};
findViewById(R.id.btn_set).setOnClickListener(this);
findViewById(R.id.btn_add).setOnClickListener(this);
findViewById(R.id.btn_update).setOnClickListener(this);
findViewById(R.id.btn_disabled).setOnClickListener(this);
findViewById(R.id.btn_remove).setOnClickListener(this);
findViewById(R.id.btn_removeAll).setOnClickListener(this);
findViewById(R.id.btn_print_max_shortcut_per_activity).setOnClickListener(this);
findViewById(R.id.btn_print_dynamic_shortcut).setOnClickListener(this);
findViewById(R.id.btn_print_static_shortcut).setOnClickListener(this);
} private ShortcutInfo getAlarmShortcut(String shortLabel) {
if (TextUtils.isEmpty(shortLabel)) {
shortLabel = "Python";
}
ShortcutInfo alarm = new ShortcutInfo.Builder(this, "alarm")
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.baidu.org/")))
.setShortLabel(shortLabel)
.setLongLabel("不正经的闹钟")
.setIcon(Icon.createWithResource(this, R.mipmap.ic_alarm))
.build();
return alarm;
} private ShortcutInfo getShoppingShortcut() {
ShortcutInfo shopping = new ShortcutInfo.Builder(this, "shopping")
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.taobao.com")))
.setShortLabel("双十一剁手")
.setLongLabel("一本正经的购物")
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shopping))
.build();
return shopping;
} private ShortcutInfo getDateShortcut() {
ShortcutInfo date = new ShortcutInfo.Builder(this, "date")
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")))
.setShortLabel("被强了")
.setLongLabel("日程")
.setIcon(Icon.createWithResource(this, R.mipmap.ic_today))
.build();
return date;
} @Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_set:
setDynamicShortcuts();
break;
case R.id.btn_add:
addDynamicShortcuts();
break;
case R.id.btn_update:
updateDynamicShortcuts();
break;
case R.id.btn_disabled:
disableDynamicShortcuts();
break;
case R.id.btn_remove:
removeDynamicShortcuts();
break;
case R.id.btn_removeAll:
removeAllDynamicShortcuts();
break;
case R.id.btn_print_max_shortcut_per_activity:
printMaxShortcutsPerActivity();
break;
case R.id.btn_print_dynamic_shortcut:
printDynamicShortcuts();
break;
case R.id.btn_print_static_shortcut:
printStaticShortcuts();
break;
}
} /**
* 动态设置快捷方式
*/
private void setDynamicShortcuts() {
mShortcutManager.setDynamicShortcuts(Arrays.asList(mShortcutInfos));
} /**
* 添加快捷方式
*/
private void addDynamicShortcuts() {
mShortcutManager.addDynamicShortcuts(Arrays.asList(getAlarmShortcut(null)));
} /**
* 更新快捷方式,类似于Notification,根据id进行更新
*/
private void updateDynamicShortcuts() {
mShortcutManager.updateShortcuts(Arrays.asList(getAlarmShortcut("Java")));
} /**
* 禁用动态快捷方式
*/
private void disableDynamicShortcuts() {
mShortcutManager.disableShortcuts(Arrays.asList("alarm"));
//因为shortcutId=compose2的快捷方式为静态,所以不能实现动态修改
//mShortcutManager.disableShortcuts(Arrays.asList("compose2"));
} /**
* 移除快捷方式
*
* 已被用户拖拽到桌面的快捷方式还是回继续存在,如果不在支持的话,最好将其置为disable
*/
private void removeDynamicShortcuts() {
mShortcutManager.removeDynamicShortcuts(Arrays.asList("alarm"));
} /**
* 移除所有动态快捷方式
*/
private void removeAllDynamicShortcuts() {
mShortcutManager.removeAllDynamicShortcuts();
} /**
* 只要 intent-filter 的 action = android.intent.action.MAIN
* 和 category = android.intent.category.LAUNCHER 的 activity 都可以设置 Shortcuts
*/
private void printMaxShortcutsPerActivity() {
Log.d(TAG, "每个 Launcher Activity 能显示的最多快捷方式个数:" + mShortcutManager.getMaxShortcutCountPerActivity());
} /**
* 打印动态快捷方式信息
*/
private void printDynamicShortcuts() {
Log.d(TAG, "动态快捷方式列表数量:" + mShortcutManager.getDynamicShortcuts().size() + "信息:" + mShortcutManager.getDynamicShortcuts());
} /**
* 打印静态快捷方式信息
*/
private void printStaticShortcuts() {
Log.d(TAG, "静态快捷方式列表数量:" + mShortcutManager.getManifestShortcuts().size() + "信息:" + mShortcutManager.getManifestShortcuts());
} }

代码基本涵盖了动态创建快捷方式的所有情况,组合测试一下就可以了。

注意代码中的 addDynamicShortcuts() 方法,该方法调用 getAlarmShortcut(String shortcutLabel) 方法生成 ShortcutInfo ,该方法生成的 ShortcutInfo 的 id 是在变化的,如果多次点击超过 mShortcutManager.getMaxShortcutCountPerActivity() 的值,就会抛出如下异常:

java.lang.IllegalArgumentException: Max number of dynamic shortcuts exceeded
at android.os.Parcel.readException(Parcel.java:1688)
at android.os.Parcel.readException(Parcel.java:1637)
at android.content.pm.IShortcutService$Stub$Proxy.addDynamicShortcuts(IShortcutService.java:430)
at android.content.pm.ShortcutManager.addDynamicShortcuts(ShortcutManager.java:535)

所以在动态添加快捷方式之前最好先检查一下是否超过最大值。

还有,disableDynamicShortcuts() 注释了使用 ShortManager 动态修改静态快捷方式的代码,因为静态快捷方式时不允许在运行时进行修改的,如果执行了修改会抛出如下异常:

java.lang.IllegalArgumentException: Manifest shortcut ID=compose2 may not be manipulated via APIs
at android.os.Parcel.readException(Parcel.java:1688)
at android.os.Parcel.readException(Parcel.java:1637)
at android.content.pm.IShortcutService$Stub$Proxy.disableShortcuts(IShortcutService.java:540)
at android.content.pm.ShortcutManager.disableShortcuts(ShortcutManager.java:615)
at com.littlejie.shortcuts.MainActivity.disableDynamicShortcuts(MainActivity.java:135)

值得注意的地方

前面讲了创建静态快捷方式和动态快捷方式,可能某些要点还没讲到,这里做下总结。

被禁用的快捷方式还计入已经创建快捷方式里嘛

addDynamicShortcuts()disableDynamicShortcutsprintDynamicShortcuts() 测试,发现被禁用的快捷方式时不算已经创建的快捷方式的。

总结

简单的总结了一下 Android 7.1 中应用快捷方式的创建及注意点,但某些不太常用的没怎么去研究,有兴趣的可以参考 Android 官方文档

参考:

  1. ShortcutManager
  2. App Shortcuts

Android 7.1 - App Shortcuts的更多相关文章

  1. Android 7.1 App Shortcuts使用

    Android 7.1 App Shortcuts使用 Android 7.1已经发了预览版, 这里是API Overview: API overview. 其中App Shortcuts是新提供的一 ...

  2. App Shortcuts 快捷方式:Android 的 '3D Touch'

    Hello Shortcuts 从Android7.1(API level25)开始,开发者可以为自己的app定制shortcuts.shortcuts使用户更便捷.快速的使用app.我个人感觉有点像 ...

  3. Android 7.1 快捷方式 Shortcuts

    转载请注明出处:王亟亟的大牛之路 前些天就看到相关内容了,但是最近吸毒比较深(wow),所以没有紧跟潮流,今天补一篇. 先安利:https://github.com/ddwhan0123/Useful ...

  4. Android中实现APP文本内容的分享发送与接收方法简述

    谨记(指定选择器Intent.createChooser()) 开始今天的内容前,先闲聊一下: (1)突然有一天头脑风暴,对很多问题有了新的看法和见解,迫不及待的想要分享给大家,文档已经写好了,我需要 ...

  5. 在布局中使用android.support.v4.app.Fragment的注意事项

    1.Activity必须继承android.support.v4.app.FragmentActivity 2.fragment标签的name属性必须是完全限定包名,如下: <LinearLay ...

  6. Android 中如何计算 App 的启动时间?

    (转载) 已知的两种方法貌似可以获取,但是感觉结果不准确:一种是,adb shell am start -w packagename/activity,这个可以得到两个值,ThisTime和Total ...

  7. Android版-支付宝APP支付

    此项目已开源 赶快来围观 Start支持下吧 [客户端开源地址-JPay][服务端端开源地址-在com.javen.alipay 包名下] 上一篇详细介绍了微信APP支付 点击这里 此篇文章来详细介绍 ...

  8. app:transformClassesWithJarMergingForDebug uplicate entry: android/support/v4/app/BackStackState$1.class

    .Execution failed for task ':app:transformClassesWithJarMergingForDebug'.> com.android.build.api. ...

  9. android.support.v4.app.Fragment和android.app.Fragment区别

    1.最低支持版本不同 android.app.Fragment 兼容的最低版本是android:minSdkVersion="11" 即3.0版 android.support.v ...

随机推荐

  1. 编写高质量代码:改善Java程序的151个建议(第5章:数组和集合___建议75~78)

    建议75:集合中的元素必须做到compareTo和equals同步 实现了Comparable接口的元素就可以排序,compareTo方法是Comparable接口要求必须实现的,它与equals方法 ...

  2. 开源一个跨平台运行的服务插件 - TaskCore.MainForm

    本次将要很大家分享的是一个跨平台运行的服务插件 - TaskCore.MainForm,此框架是使用.netcore来写的,现在netcore已经支持很多系统平台运行了,所以将以前的Task.Main ...

  3. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  4. JS继承之原型继承

     许多OO语言都支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.如前所述,由于函数没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支 ...

  5. Linux主机上使用交叉编译移植u-boot到树莓派

    0环境 Linux主机OS:Ubuntu14.04 64位,运行在wmware workstation 10虚拟机 树莓派版本:raspberry pi 2 B型. 树莓派OS: Debian Jes ...

  6. 纸箱堆叠 bzoj 2253

    纸箱堆叠 (1s 128MB) box [问题描述] P 工厂是一个生产纸箱的工厂.纸箱生产线在人工输入三个参数 n, p, a 之后,即可自动化生产三边边长为 (a mod P, a^2 mod p ...

  7. C++常见笔试面试要点以及常见问题

    1. C++常见笔试面试要点: C++语言相关: (1) 虚函数(多态)的内部实现 (2) 智能指针用过哪些?shared_ptr和unique_ptr用的时候需要注意什么?shared_ptr的实现 ...

  8. Open-Test 测试驱动模式与版本号管理机制

    以测试用例驱动项目开发,coding/case俩条线并走模式.   1.开发人员只负责功能实现:   2.测试人员提供自测用例,研发人员jenkins持续集成项目后自动化执行自测用例,通过后方可转测试 ...

  9. 浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因

    本文出处:http://www.cnblogs.com/wy123/p/6189100.html 标题有点拗口,来源于一个开发人员遇到的实际问题 先抛出问题:一个查询没有明确指定排序方式,那么,第二次 ...

  10. 一切从“简”,解放IT运维人员

    运维人的神技 运维既是个技术活儿也是个苦差事,而运维人员被期望有着无限的技能:主机.存储.网络.操作系统样样精通,而且还要会写SQL.shell.开发语言java..net.python等等,对业务更 ...