虽然开发过程中经常会中奖踩坑,但是这次真的踩了很大的坑啊,感觉填了很久踩勉强填好。当然也只是对我来说,在大神眼里根本不算什么。事情是这样的,两天前我需要在 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Intent intent = new Intent(); intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name)); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_shortcut); intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); intent.putExtra("duplicate", false);
Intent actionIntent = new Intent(); actionIntent.setClass(SplashActivity.this, HomeActivity.class);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, actionIntent); sendBroadcast(intent);
|
因为 intent.putExtra("duplicate", false); 这一句代码不能保证可以避免重复创建快捷方式(在虚拟机测试有效,但在 htc 真机上无效),所以还需要自己来判断是否已经存在相同的快捷方式。这这里耗费了将近一天的时间来测试,真的无所不用其极,感觉已经翻遍 google 了。这里不得不吐槽了 Android 各种机型想要完全适配工作量真的是太大了,现在屏幕上的适配反而比较容易,但是对各大厂商自己定制的 ROM ,那才是最大的沟渠啊,就桌面 Launcher 来说,每种手机都有自己的定制版,那么对于相应的权限,提供认证的 provider 都可能不同,适配起来也就无比麻烦。同时 Android 不同的版本也可能存在不同的 Launcher ,就现在的 Android 原生来说,已经有 3 个版本的 Launcher 了:launcher、launcher2 和 launcher3 。
下面是碰到的几种错误 log 和解释:
这个是因为 Android SDK 版本的不同,系统 launcher 版本也有变化,可能回事 launcher2,或者 launcher3,或者其他 ROM 的自家 launcher;
getAuthorityFromPermission 返回 null
这个方法是现在网络上普遍使用的可以根据权限来识别认证的 provider,但是网络版本还不够完善,因为经过测试发现,传入的 permission 不一定能够匹配所有的机型的 permission ,也就不能保证能够传回想要的 provider ,在这里我做了一些改进,最终能够在 htc 真机和虚拟机测试通过;
很明显,因为虚拟机是 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 2 3 4
| if (provider.authority.contains("launcher.settings") || provider.authority.contains("launcher2.settings") || provider.authority.contains("launcher3.settings")) { return provider.authority;
|
不过这样虽然可以达到我们想要的结果,但是这个方法的适用范围就仅限于此了。这个就需要大家自己斟酌了。
最后贴上完整的代码:
manifest:
1 2 3 4 5 6 7 8 9 10 11
| <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.launcher2.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" /> <uses-permission android:name="com.google.android.launcher.permission.READ_SETTINGS"/>
<activity android:name=".Activity.HomeActivity" android:launchMode="singleInstance"> <intent-filter> <action android:name="com.example.alpha.mobilesafe.home"/> </intent-filter> </activity>
|
添加 shortcut
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
private void addShortCut() { if (AppInfoUtils.hasShortCut(this)) { return; }
Intent intent = new Intent(); intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name)); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_shortcut); intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); intent.putExtra("duplicate", false);
Intent actionIntent = new Intent(); actionIntent.setClass(SplashActivity.this, HomeActivity.class);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, actionIntent); sendBroadcast(intent); Log.d(TAG, "addShortCut: 桌面图标添加完成"); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
public static boolean hasShortCut(Context context) { boolean result = false; String authority = getAuthorityFromPermission(context, ".launcher.permission.READ_SETTINGS"); Log.d(TAG, "hasShortCut: authority是什么呢?"+authority);
String uriStr = "content://" + authority + "/favorites?notify=true";
try { Cursor cursor = context.getContentResolver().query( Uri.parse(uriStr), null, "title=?", new String[]{context.getString(R.string.app_name)}, null); result = cursor != null && cursor.getCount() > 0; Log.d(TAG, String.valueOf("hasShortCut: 有没有图标啊?" + result)); if (cursor != null) { cursor.close(); } } catch (Exception e) { e.printStackTrace(); } return result; }
private static String getAuthorityFromPermission(Context context, String permission) { if (permission == null) return null; List<PackageInfo> installedPackages = context.getPackageManager() .getInstalledPackages(PackageManager.GET_PROVIDERS); if (installedPackages != null) { for (PackageInfo pack : installedPackages) { ProviderInfo[] providers = pack.providers; if (providers != null) { for (ProviderInfo provider : providers) { Log.d(TAG, "getAuthorityFromPermission: " + provider.readPermission); if (provider.readPermission != null && provider.readPermission.contains(permission)) { if (provider.authority.contains("launcher.settings") || provider.authority.contains("launcher2.settings") || provider.authority.contains("launcher3.settings")) { return provider.authority; } } } } } } return null; }
|
这样无论是真机还是虚拟机,都可以判断是否已经存在快捷方式,而且根据我的方法,我想还是存在一定的广泛型,可以匹配大部分机型,当然仅限这一功能了。