虽然开发过程中经常会中奖踩坑,但是这次真的踩了很大的坑啊,感觉填了很久踩勉强填好。当然也只是对我来说,在大神眼里根本不算什么。事情是这样的,两天前我需要在 App 被安装的时候,向桌面添加一个快捷图标,来增强在用户眼里的存在感。。。由于在此之前我正好给 App 换了个图标,虽然是 png 格式,但是宽高病不同,分辨率大概是 620×610 的样子,基本是个正方形,而且用于 App 的桌面图标也没有什么问题,可以正常显示。但是在用于给 App 添加桌面快捷方式的时候,却死活也不显示,明明 log 显示确实已经添加了,代码执行良好,也没有报错和警告,但就是死活看不到。这是第一个坑。
于是重新创建了一个小工程,用了完全相同的代码,只不过 App 图标是自带的,我没有更换。一执行,居然成功了!我简直惊呆了。仔细对比觉得唯一的可能就是出在照片上了。于是把原来的图标裁剪了一下,改成完全的正方型 610×610 ,还是不显示。没办法,直接用了另外一张照片,标准的 MD 风格的 png,再一执行,果然创建成功了。看来 AS 对照片的检查还真不是一般的严格,之前在 Eclipse 里还能用的 .9 图片,在 AS 里会报错,这次看来也一样,只是 AS 却没有任何表示,以后在照片的使用上必须多加注意。第一个坑填好。
然后就要给快捷图标添加点击 Intent ,在这里尝试了很多次,才找到正确的方法。具体的常识过程太啰嗦,就不详细讲了,主要讲一下在这个过程得到的经验吧。
- 首先需要在点击 Intent 要开启的 Activity 的 manifest 里添加 IntentFilter 标签;
- manifest 的 IntentFilter 标签里必须包含一个 Action ,这个 Action 的名字可以随便命名;
- manifest 的 IntentFilter 添加 category 不是必须的,可以不加;
- 在代码里创建的 点击 Intent 可以通过构造方法添加隐式 Intent 来开启目标 Activity ,隐式 Intent 里就是在 manifest 的 IntentFilter 设置的 Action ;但是这种开启方法不能与自己的 App 绑定,也就是 App 卸载后图标不会自动被移除;
- 点击 Intent 通过 setClass() 设置源和目标 Activity 来开启,并且可以与自己的 App 绑定,App 卸载后图标会被移除;
- 点击 Intent 可以不用再设置 action 和 category;
- 使用快捷方式打开的 Activity 最好设为 Single Instance 启动模式,或者为点击 Intent 设置必要的 flag ,以免重复打开相同的 Activity ;
- 在sendBoradcast()中的 intent 设置 intent.putExtra(“duplicate”, false) 不能保证可以避免重复创建快捷方式,这是最后一个坑,大坑啊,见后文。
所以我的添加快捷方式的代码是这样的:
1 | Intent intent = new Intent(); |
因为 intent.putExtra("duplicate", false);
这一句代码不能保证可以避免重复创建快捷方式(在虚拟机测试有效,但在 htc 真机上无效),所以还需要自己来判断是否已经存在相同的快捷方式。这这里耗费了将近一天的时间来测试,真的无所不用其极,感觉已经翻遍 google 了。这里不得不吐槽了 Android 各种机型想要完全适配工作量真的是太大了,现在屏幕上的适配反而比较容易,但是对各大厂商自己定制的 ROM ,那才是最大的沟渠啊,就桌面 Launcher 来说,每种手机都有自己的定制版,那么对于相应的权限,提供认证的 provider 都可能不同,适配起来也就无比麻烦。同时 Android 不同的版本也可能存在不同的 Launcher ,就现在的 Android 原生来说,已经有 3 个版本的 Launcher 了:launcher、launcher2 和 launcher3 。
下面是碰到的几种错误 log 和解释:
Failed to find provider info for com.android.launcher.settings
这个是因为 Android SDK 版本的不同,系统 launcher 版本也有变化,可能回事 launcher2,或者 launcher3,或者其他 ROM 的自家 launcher;
getAuthorityFromPermission 返回 null
这个方法是现在网络上普遍使用的可以根据权限来识别认证的 provider,但是网络版本还不够完善,因为经过测试发现,传入的 permission 不一定能够匹配所有的机型的 permission ,也就不能保证能够传回想要的 provider ,在这里我做了一些改进,最终能够在 htc 真机和虚拟机测试通过;
Permission Denial: opening provider com.android.launcher3.LauncherProvider from ProcessRecord{f264d45 13880:com.example.alpha.mobilesafe/u0a68} (pid=13880, uid=10068) requires com.google.android.launcher.permission.READ_SETTINGS or com.google.android.launcher.permission.WRITE_SETTINGS
很明显,因为虚拟机是 Nexus4,还需要添加 google 的权限:com.google.android.launcher.permission.READ_SETTINGS
接下来主要说说适配的一点小技巧,也是最后一个坑:
网络版本的 getAuthorityFromPermission
是直接传入一个完整的 permission ,而我只是传入了半个,比如:.launcher.permission.READ_SETTINGS
,因为我发现不管是虚拟机,还是真机,在权限上都包含这一部分,既然如此,就通过是否包含来判断对应烦人 authority ,但是在 htc 真机上又有个问题, authority 总是传回 com.htc.launcher.ExternalApp
而不是我想要的com.htc.launcher.settings
,所以在回传结果的再加一次判断:
1 | if (provider.authority.contains("launcher.settings") |
不过这样虽然可以达到我们想要的结果,但是这个方法的适用范围就仅限于此了。这个就需要大家自己斟酌了。
最后贴上完整的代码:
manifest:
1 | <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /> |
添加 shortcut
1 | /** |
1 | /** |
这样无论是真机还是虚拟机,都可以判断是否已经存在快捷方式,而且根据我的方法,我想还是存在一定的广泛型,可以匹配大部分机型,当然仅限这一功能了。