Android开发之桌面快捷键使用细则(原创)

  1. 云栖社区>
  2. 博客>
  3. 正文

Android开发之桌面快捷键使用细则(原创)

小猪看流星 2018-07-19 18:51:00 浏览430
展开阅读全文

本文已独家授权 郭霖 ( guolin_blog) 公众号发布!

首先声明,标题里的快捷键不是指开发人员使用频率极高的Ctrl+C和Ctrl+V;也不是IDE里Ctrl+D、Ctrl+F等常用快捷键。这里的快捷键,其实想要表达的是Android应用生成桌面快捷方式的细则。

试想,现在有一Windows用户想进入D盘——my文件夹里面的子文件去找文件(因为藏了些晦涩资源所以层级较深)。那么更加便利省力的操作是:点击选中文件夹——右键:发送到——桌面快捷方式,即可帮我们将快捷方式生成到桌面。该用户下次想使用这个文件夹,直接点击桌面上的快捷方式即可。这样做的好处在于,用户可以快速定位到某一应用具体的功能、干净利落。

当然,谷歌Android团队也考虑了这一点,给我们设计了原生API,方便我们开发人员更加便利的(Ctrl+C、V)生成桌面快捷方式。这样做的好处我想有以下几点,首先,提高了用户留存率,试想一个APP通过某种媒介生成了2个icon,这样是很容易吸引人的,因为生成桌面快捷方式的icon以及点击事件都是代码可控的,比如你的快捷方式的icon是一个萝莉或者御姐;正太或是直男?毕竟图片总有人会喜欢的嘛。其次,快捷方式的点击事件是控制的,跳转的界面控制在开发者(产品)手中等等。

言归正传,既然是生成桌面快捷方式,那么肯定需要权限,必要的权限如下:

    <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
    <!-- 添加快捷方式 -->
    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
    <!-- 移除快捷方式 -->
    <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
    <!-- 查询快捷方式 -->
    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />

接着,因为Android难以言表的碎片化和厂商定制,所以还需要加一些权限来增加健壮性,下面直接copy就行:

   <uses-permission android:name="com.android.launcher2.permission.READ_SETTINGS" />
    <uses-permission android:name="com.android.launcher2.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
    <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
    <uses-permission android:name="org.adw.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="org.adw.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.htc.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.qihoo360.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.qihoo360.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.lge.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.lge.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="net.qihoo.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="net.qihoo.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="org.adwfreak.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="org.adwfreak.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="org.adw.launcher_donut.permission.READ_SETTINGS" />
    <uses-permission android:name="org.adw.launcher_donut.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.huawei.launcher3.permission.READ_SETTINGS" />
    <uses-permission android:name="com.huawei.launcher3.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.fede.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.fede.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.sec.android.app.twlauncher.settings.READ_SETTINGS" />
    <uses-permission android:name="com.sec.android.app.twlauncher.settings.WRITE_SETTINGS" />
    <uses-permission android:name="com.anddoes.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.anddoes.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.tencent.qqlauncher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.tencent.qqlauncher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.huawei.launcher2.permission.READ_SETTINGS" />
    <uses-permission android:name="com.huawei.launcher2.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.android.mylauncher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.android.mylauncher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.ebproductions.android.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.ebproductions.android.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.oppo.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.oppo.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="com.huawei.android.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.huawei.android.launcher.permission.WRITE_SETTINGS" />
    <uses-permission android:name="telecom.mdesk.permission.READ_SETTINGS" />
    <uses-permission android:name="telecom.mdesk.permission.WRITE_SETTINGS" />
    <uses-permission android:name="dianxin.permission.ACCESS_LAUNCHER_DATA" />

好了,权限已经添加完毕,下面就可以上代码了,首先是创建桌面快捷方式:

    //创建桌面快捷方式
    private void createShortCut(){
        //创建Intent对象
        Intent shortcutIntent = new Intent();
        
        //设置点击快捷方式,进入指定的Activity
        //注意:因为是从Lanucher中启动,所以这里用到了ComponentName
        //其中new ComponentName这里的第二个参数,是Activity的全路径名,也就是包名类名要写全。
        
        shortcutIntent.setComponent(new ComponentName(this.getPackageName(), "这里是包名.类名"));
        
        //给Intent添加 对应的flag
        shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|Intent.FLAG_ACTIVITY_NEW_TASK);

        Intent resultIntent = new Intent();
        // Intent.ShortcutIconResource.fromContext 这个就是设置快捷方式的图标
        
        resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
                Intent.ShortcutIconResource.fromContext(this,
                        R.drawable.yuanbao));
        //启动的Intent
        resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
        
        //这里可以设置快捷方式的名称
        resultIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "快捷名称");

        //设置Action
        resultIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");

        //发送广播、通知系统创建桌面快捷方式
        sendBroadcast(resultIntent);
    }

创建桌面快捷方式的代码,理论上就是上面这些,ComponentName这个类用的较少,简单理解ComponentName的作用是,可以启动其他应用的Activity、Service(前提是要知道包名),然后搭配Intent使用,完成跳转。关于ComponentName与Activity、Service的参考代码如下:

    ComponentName  componentName = new ComponentName(param1,param2);
    
        param1:Activity、Service 所在应用的包名
        //获取应用的包名可以通过 this.getPackageName(); this代表当前的Activity
        param2:Activity、Service的包名+类名 
        //这里是全路径名:对应的就是 this.getPackageName()+"YourActivity"
        //this.getPackageName()+"YourService"

    //ComponentName结合Activity的写法如下:     
    ComponentName componentName = new ComponentName(this.getPackageName(), this.getPackageName()+"YourActivity");

        Intent intent =new Intent();

        intent.setComponent(componentName);

        startActivity(intent);
    
    //ComponentName结合Service的写法如下:  
    ComponentName componentName = new ComponentName(this.getPackageName(), this.getPackageName()+"YourService");

        Intent intent = new Intent();

        intent.setComponent(componentName);

        startService(intent);

另外,还需要在清单配置文件里面,对应的Activity和Service需要加上android:exported = "true",这个android:exported标签,是用来指示该服务是否能够被其他应用程序组件调用或跟它交互。设置成true,则能够被调用或交互;设置false,也就意味不能被其他组件交互。APP入口的Activity不声明默认就是android:exported="true"。关于ComponentName的说明就到此为止,还不是很好理解的可以自行谷歌百度。综上,还需要在清单文件配置一些代码,参考如下:

            <activity
            android:exported="true"
            android:excludeFromRecents="true"
            android:name=".TestCreatIconActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" 
                <action android:name="test.intent.action.SHORTCUT" />
                <category android:name="android.intent.category.LAUNCHER" />
                <!-- 必须加上这个。否则无法直接使用自定的action -->
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            </activity>

            <activity
            android:name=".MainActivity"
            android:exported="true"
            android:excludeFromRecents="true"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
            </activity>

这里的MainActivity,就是我点击快捷方式进入的Activity。可能你会说,我该如何传值给,快捷方式点进去的Activity?那么也是传统套路根据Intent来传值,也就是上面的shortcutIntent,参考代码如下:

                    //传参到指定界面,通过标识和具体数据执行
                    shortcutIntent.putExtra("key1","xxx");
                    shortcutIntent.putExtra("key2",true);
                    shortcutIntent.putExtra("key3",233);
                    ...

好了,创建快捷方式的代码是通过sendBroadcast(resultIntent);来实现的,因此可以判断这是系统的广播。

删除桌面快捷方式
首先,用户可以直接在手机桌面拖拽快捷方式,进行删除
另外,上面的代码也实现了当删除APK以后,自动删除快捷方式(因为你跳转没有了目标,所以需要删除)由于代码删除快捷方式,网上的一些代码都有问题(又是令人无语的碎片化和厂商定制),所以这里不提供删除快捷方式的代码,理论上,上面2种手动删除实现即可。
值得注意的是,如果用户没有手动打开权限,也会创建失败,也需要提示用户手动对应用进行快捷方式权限授权设置。

Android 7.1系统快捷方式的变化:

从Android 7.1(API 25,也就是 minSdkVersion 25 )开始,新增了ShortcutManager,ShortcutManager,顾名思义,翻译过来就是快捷方式管理。
这个类可以对桌面久按应用图标弹出的快捷方式进行管理。

那么ShortcutManager的实现方式有2种:

第一种:XML注册
首先, 需要在res/xml目录下创建一个新的xml文件,根标签是shortcuts,参考代码如下:

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:shortcutId="setting"
        android:enabled="true"
        android:icon="@drawable/yuanbao"
        android:shortcutShortLabel="@string/set_short_name"
        android:shortcutLongLabel="@string/set_long_name"
        android:shortcutDisabledMessage="@string/set_disable_msg">

        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.share.shortcut"
            android:targetClass="com.share.shortcut.TargetActivity" />
        <categories android:name="android.shortcut.conversation"/>

    </shortcut>
</shortcuts>

嗯,可以看到新增了很多标签,这些标签代表什么意思咧?首先是shortcut的外部标签:

shortcutId, 快捷方式的id

enabled:表示这个shortcut是否可用,一般设置为true即可

shortcutShortLabel:配置快捷方式的短名称, 如果长名称显示不下, 就显示短名称

shortcutLongLabel: 配置快捷方式的长名称, launcher会优先选择长名称显示(优先级高于shortcutShortLabel)

shortcutDisabledMessage:这个标签的意思是指:当我们点击一个不可用的shortcut时,给用户一个有效提示

内部还有个intent标签:

 android:action,这里的action需要配置,否则会崩溃,默认写法是android.intent.action.VIEW

 targetPackage:目标应用的包名,

 targetClass:点击快捷方式要跳转的目标类, 这里要注意的是android:action一定要配置, 否则会崩溃

 categories:这个标签谷歌团队仅提供了android.shortcut.conversation 这一种,所以直接复制即可

好了,现在通过XML已经写好了快捷方式,那现在该如何使用,让快捷方式生效?
那么,只需要在清单文件中启动Activity的里面,加入以下2行代码(也就是通过meta-data配置进去)即可看到效果:

        <activity
            android:launchMode="singleInstance"
            android:name=".MainActivity"
            android:excludeFromRecents="true"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <!--<action android:name="test.intent.action.SHORTCUT" />-->
                <category android:name="android.intent.category.LAUNCHER" />
                <!-- 必须加上这个。否则无法直接使用自定的action -->
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        静态注册
            <meta-data
                android:name="android.app.shortcuts"
                android:resource="@xml/shortcut"/>
        </activity>

这里的meta-data就是配置了桌面快捷方式的一些信息。代码运行后,执行创建快捷方式的操作即可看到效果:


img_f8e8044339652c34788aba0fb9019d89.png
7.1系统效果图

说完了第一种XML注册在来说第二种注册:代码动态注册
首先,获取系统服务,固定写法:getSystemService(ShortcutManager.class);获取ShortcutManager类对象
接着,获取完ShortcutManager对象以后,通过setDynamicShortcuts( List<ShortcutInfo> )方法去设置Shortcut快捷方式具体的名字、icon以及逻辑。
既然,现在要通过ShortcutInfo去完成信息填充,那么我们就先看下ShortcutInfo的基本写法:

//获取系统服务得到ShortcutManager对象
 ShortcutManager  systemService = getSystemService(ShortcutManager.class);

        if (Build.VERSION.SDK_INT >= 25) {
            
            //设置Intent跳转逻辑
            Intent intent = new Intent(ShortCut7_0Activity.this, TargetActivity.class);
            intent.setAction(Intent.ACTION_VIEW);

            //设置ID
            ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, "onlyId")
                    //设置短标题        
                    .setShortLabel("雅蠛蝶")
                    //设置长标题
                    .setLongLabel("江山留胜迹")
                    //设置icon
                    .setIcon(Icon.createWithResource(this, R.drawable.yuanbao))
                    //设置Intent
                    .setIntent(intent)
                    .build();
            //这样就可以通过长按图标显示出快捷方式了
            systemService.setDynamicShortcuts(Arrays.asList(shortcutInfo));

以上代码就可以创建桌面快捷方式,通过ShortcutInfo的Builder写法可以动态的插入快捷方式的名称、icon、以及Intent逻辑。值得一提的是,ShortcutManager最大可以创建5个,但桌面上只能最多只能显示4个快捷方式,可能是谷歌团队基于何种情况的考虑进行的限制,这里就不做深入研究。另外,快捷方式的顺序排列是:前面添加的显示在下方,后面添加的显示在上面。


img_0acdc0c2389cc56330ec0752d81c81b2.png
7.1系统多快捷方式效果图

另外,移除快捷方式可以调用如下API:
void removeDynamicShortcuts(@NonNull List shortcutIds);

Android 8.0系统快捷方式的变化:

可能是谷歌出新版本需要优化增加既有版本的一些功能从Android 8.0(API 26,也就是 minSdkVersion 26,也就是Android O )开始,对ShortcutManager这个类新增了更多的管理措施。
那么O系统主要的扩充说明有那些?我这里就简单说明下,

一:

APP可以使用requestPinShortcut(ShortcutInfo,IntentSender)将现有的快捷方式(静态或动态)或全新的快捷方式固定到支持的启动器。这俩参数的意思是:
ShortcutInfo对象 - 如果快捷方式已存在,则该对象仅包含快捷方式的ID。如果快捷方式不存在,新的ShortcutInfo对象必须包含新快捷方式的ID,意图和短标签。
PendingIntent对象 - 此意图表示如果快捷方式成功固定到设备的启动器,您的应用程序将收到回调。

分析:也就是说,谷歌要求新的快捷方式需要编写的严谨,然后该快捷方式是否固定到设备加了新的判断标准

二:

由于Android O中引入的后台执行限制,最好使用清单声明的接收器来接收回调。
另外,为了防止其他应用程序调用接收器,需要将属性赋值android:exported =“false”添加到接收者的清单条目中。

分析:针对O系统对于服务的严格限制,这是对快捷方式功能的扩充

三:

并不是所有的启动器都支持固定快捷方式!!!所以,如果要确定该APP是否支持国定快捷方式,可以使用isRequestPinShortcutSupported()这个方法的返回值。
根据返回值,可以决定隐藏App中允许,用户固定快捷方式的选项。该方法返回TRUE,则意味着,桌面支持requestPinShortcut;
但是用户也可能会更改,更改选项以后,再次启动APP返回值可能就会发生变化。另外,系统版本低于Android O,那么它支持使用旧的私有意图com.android.launcher.action.INSTALL_SHORTCUT。
requestPinShortcut这个方法请求创建固定的快捷方式。默认启动器将收到该请求,并要求用户批准。如果用户批准,将创建快捷方式,并且将发送resultIntent。但是,如果请求被用户拒绝,则不会向呼叫者发送任何响应。需要注意的是,只有具有前台活动或前台服务的应用程序才能调用此方法。否则,它将抛出IllegalStateException。

分析:也就是说,这种启动器的固定快捷方式能否成功很大程度跟用户的操作密切相关(这个可以跟产品吹一波了)。另外可以看到O系统对于服务的严格控制,严格控制后台服务需要使用前台服务的举措在于让应用更加安全(因此IntentService在新版本上要慎用。当然谷歌在开发者文档上针对后台服务这一问题也给了对应的解决办法),这也看到了谷歌技术团队对后续版本使用应用安全的决心。
综上:可以有以下代码

  @RequiresApi(api = Build.VERSION_CODES.O)
    public static void testShortCut(Context context) {
        ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
        boolean requestPinShortcutSupported = shortcutManager.isRequestPinShortcutSupported();
        Log.i(TAG, "启动器是否支持固定快捷方式: "+requestPinShortcutSupported);

        if (requestPinShortcutSupported) {

            Intent shortcutInfoIntent = new Intent(context, TargetActivity.class);

            shortcutInfoIntent.setAction(Intent.ACTION_VIEW);

            ShortcutInfo info = new ShortcutInfo.Builder(context, "tzw")
                    .setIcon(Icon.createWithResource(context, R.drawable.yuanbao))
                    .setShortLabel("O系统短")
                    .setLongLabel("O系统长")
                    .setIntent(shortcutInfoIntent)
                    .build();

            //当添加快捷方式的确认弹框弹出来时,将被回调CallBackReceiver里面的onReceive方法
            PendingIntent shortcutCallbackIntent = PendingIntent.getBroadcast(context, 0, new Intent(context, CallBackReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT);

            shortcutManager.requestPinShortcut(info, shortcutCallbackIntent.getIntentSender());

        }
    }
 class CallBackReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "onReceive: 固定快捷方式的回调");
        }
    }

下面是应用启动后,Android Studio自带8.0模拟器,首先创建快捷方式的逻辑顺序:

img_8188eba836f8c24440d262478f8e5514.png
O系统启动器快捷方式

点击ADD...以后,我们的应用就显示这样了,可以看到快捷方式向卫星一样挂靠在应用icon上面:

img_dd0f4c662b6afe33a3c725fc3e5871b8.png
允许之后

如果点击CANCEL,那么就没有创建,退出以后icon还是原样,当我们第二次打开应用,选择申请桌面快捷方式的时候,又会弹出上面的申请界面。

这篇博客花了一些时间,因为要考虑三种不同情况(如果想要看到三种情况的效果,需要手动更改 minSdkVersion版本号、以及对应版本的模拟器资源),当然也参考了一些作者的文章。源码里面有所有代码,如果写的不好或者有任何问题可以直接在评论区或者github指出。

文章用到的源码

如果这篇文章对你有帮助,希望各位看官留下宝贵的star,谢谢。

Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用,也希望大家尊重笔者的劳动成果。

网友评论

登录后评论
0/500
评论
小猪看流星
+ 关注