谷歌官方在Android 7.1(25)新增了桌面长按弹出菜单,并且在8.0(26)以后可以固定快捷方式至桌面上。围绕桌面快捷方式的需求也比较多,例如微信将联系人、小程序都可以添加至桌面;简书将“写文章”添加至桌面;高德将“坐标信息”添加到桌面。
相关Sample代码链接 https://github.com/scauzhangpeng/Shortcut
权限
谷歌原生系统、三星系统不需要用户主动打开权限,在AndroidManifest声明即可
华为、小米、OPPO、vivo、魅族需要“创建桌面快捷方式”权限
由于各厂家ROM的Launcher改动并且原生也不需要申请权限,所以:
无法检测用户是否开启了“创建桌面快捷方式”权限(已有方法检查,可以检查华为、小米、OPPO、VIVO,目前手上测试机都测试通过,后面会给出机型和版本列表,360手机助手这个APP可以有提示权限未开启。根据http://www.lefo.me/2017/05/19/shortcut-permission/>的思路已经整理好相关代码)- 无法按照运行时权限一样申请“创建桌面快捷方式”权限
- 魅族:系统自动弹窗询问是否可以添加桌面快捷方式
借鉴各大APP的做法,大致上都是弹窗文案指引,并且可以跳转至系统权限设置:
微信 | 支付宝 | 简书 |
---|---|---|
微信在华为手机上没有“前往设置” | 支付宝都没有做权限设置跳转 | 华为跳转到具体的“设置单项权限”界面 需要华为接口权限,因此只能跳转如下界面 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WItqF4BH-1579749312003)(https://s2.ax1x.com/2019/06/02/V8W2Yd.png =150x)] | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RfGtCtNT-1579749312005)(https://s2.ax1x.com/2019/06/02/V8W6Te.png =150x)] | |
OPPO 8.1.0 ColorOs 5.2.1 | 小米 8.1.0 MIUI 9.5 | 华为 8.1.0 EMUI 8.1.0 |
跳转至系统权限设置,在不同厂商机型表现得略有不同(OPPO和vivo只有路径二和三)
- 路径一:设置 - 应用列表 - XX应用 - 权限 - 创建桌面快捷方式
- 路径二:设置 - 权限列表 - 创建桌面快捷方式 - xx应用
- 路径三:自带手机管家 - 权限列表 - 创建桌面快捷方式 - xx应用
图标
自定义图标以及形状(图片bitmap不能太大,不然会超过binder最大数据量显示导致异常)
- 圆形图片:部分机型(三星、vivo、小米)会带一个圆角白色的背景,导致非常丑
- 圆角图片:如果图片不会过大,正常的Launcher的Icon标准是可以的
正方形图片(推荐):让系统Launcher自动适配裁剪(三星手机部分版本会存在创建的快捷方式图标偏大,猜测由于正常App图标加了Padding,推荐直接根据桌面图标进行合成裁剪)
系统自动添加应用Icon在快捷方式图标的右下角
- 8.0以上系统自动,并且无法设置不添加
- 8.0以前可以代码设置是否添加,
@see ShortcutInfoCompat.Builder#setAlwaysBadged()
图标的删除方式
- 用户手动清除应用数据会一并清除所有的桌面快捷方式(仅支持8.0以上)
- 用户手动删除桌面快捷方式
- 用户卸载应用
唯一性
在Android 8.0(26)以下,创建快捷方式的方法是发广播给Launcher,无法对快捷方式进行管理;在Android 8.0以上才有相应的API支持管理桌面快捷方式。
是否创建成功
- 8.0以下采用发广播给Launcher,是否创建成功无法知道或者说回调广播不准确
- 8.0以上采用系统API,可以同步返回操作是否成功,然后成功后异步广播回调。
根据ID去重还是根据名称去重
- 8.0以下都是厂商的Launcher的做法,去重依据是根据名称是否一致,去重的效果可能是:
OPPO | 华为 |
---|---|
无提示,不更新 | 有提示已存在,不更新 |
-
8.0以上可以根据ID进行判断是否存在,然后再进行创建或者更新操作
@see ShortcutManager#getPinnedShortcuts()
@see ShortcutInfo#getId()
-
华为有点特殊,
8.0以上(8.0以及8.1的bug,9.0已经正常)按照ID + 名称去重,不允许同名称的桌面快捷方式即使ID不一致(微信将两个联系人改同样的备注名,然后添加到桌面,即使ID不一致也会提示重复)(目前的做法是检测到重名的,随机生成一个UUID,然后创建成功后再进行更新原名称操作)
源码分析
Support包下的兼容类:ShortcutManagerCompat#requestPinShortcut()
/*** Request to create a pinned shortcut.* <p>On API <= 25 it creates a legacy shortcut with the provided icon, label and intent. For* newer APIs it will create a {@link android.content.pm.ShortcutInfo} object which can be* updated by the app.** <p>Use {@link android.app.PendingIntent#getIntentSender()} to create a {@link IntentSender}.** @param shortcut new shortcut to pin* @param callback if not null, this intent will be sent when the shortcut is pinned** @return {@code true} if the launcher supports this feature** @see #isRequestPinShortcutSupported* @see IntentSender* @see android.app.PendingIntent#getIntentSender()*/public static boolean requestPinShortcut(@NonNull final Context context,@NonNull ShortcutInfoCompat shortcut, @Nullable final IntentSender callback) {if (Build.VERSION.SDK_INT >= 26) {return context.getSystemService(ShortcutManager.class).requestPinShortcut(shortcut.toShortcutInfo(), callback);}if (!isRequestPinShortcutSupported(context)) {return false;}Intent intent = shortcut.addToIntent(new Intent(ACTION_INSTALL_SHORTCUT));// If the callback is null, just send the broadcastif (callback == null) {context.sendBroadcast(intent);return true;}// Otherwise send the callback when the intent has successfully been dispatched.context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {try {callback.sendIntent(context, 0, null, null, null);} catch (IntentSender.SendIntentException e) {// Ignore}}}, null, Activity.RESULT_OK, null, null);return true;}
源码简洁并配以简单易懂的注释,显得十分优雅。
- 8.0以上调用系统API,返回结果;
- 8.0以下判断当前系统Launcher是否支持创建固定桌面快捷方式
- 当前支持创建固定桌面快捷方式,发送广播;
分析2,源码中如何判断Launcher是否支持创建固定桌面快捷方式
/*** @return {@code true} if the launcher supports {@link #requestPinShortcut},* {@code false} otherwise*/public static boolean isRequestPinShortcutSupported(@NonNull Context context) {if (Build.VERSION.SDK_INT >= 26) {return context.getSystemService(ShortcutManager.class).isRequestPinShortcutSupported();}if (ContextCompat.checkSelfPermission(context, INSTALL_SHORTCUT_PERMISSION)!= PackageManager.PERMISSION_GRANTED) {return false;}for (ResolveInfo info : context.getPackageManager().queryBroadcastReceivers(new Intent(ACTION_INSTALL_SHORTCUT), 0)) {String permission = info.activityInfo.permission;if (TextUtils.isEmpty(permission) || INSTALL_SHORTCUT_PERMISSION.equals(permission)) {return true;}}return false;}
这个检测相当严谨,本人有点想将源码复制出来,去掉这个检测,因为发送广播的方式也不会影响程序运行,只管尝试发广播即可。
- 8.0以上根据新API进行检测
- 8.0以下检测是否有注册权限,如果没有则返回fasle
- 检测是否有Launcher接收
ACTION_INSTALL_SHORTCUT
这个Action的广播,如果有再判断一次它是否注册了权限
分析3,发送广播之前重新构造了Intent,Intent intent = shortcut.addToIntent(new Intent(ACTION_INSTALL_SHORTCUT));
@VisibleForTestingIntent addToIntent(Intent outIntent) {outIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, mIntents[mIntents.length - 1]).putExtra(Intent.EXTRA_SHORTCUT_NAME, mLabel.toString());if (mIcon != null) {Drawable badge = null;if (mIsAlwaysBadged) {PackageManager pm = mContext.getPackageManager();if (mActivity != null) {try {badge = pm.getActivityIcon(mActivity);} catch (PackageManager.NameNotFoundException e) {// Ignore}}if (badge == null) {badge = mContext.getApplicationInfo().loadIcon(pm);}}mIcon.addToShortcutIntent(outIntent, badge);}return outIntent;}
源码也就是将Intent的各种参数设置进去,然后再返回即可
- action :
@see Intent#ACTION_INSTALL_SHORTCUT
- Intent.EXTRA_SHORTCUT_INTENT :快捷方式启动的Intent
- Intent.EXTRA_SHORTCUT_NAME :快捷方式的名称
- 如果设置
@see ShortcutInfoCompat.Builder#setAlwaysBadged()
则将Activity组件的Icon或者APP的Icon和给定的图画在一起,Badged在右下角,@see IconCompat#addToShortcutIntent()