今天接到这样一个需求,要求 APP 图标可以动态配置,也就是通过后台接口能灵活配置让用户看到不同的桌面启动 icon 。比如双11当天,用户看到自己桌面上的 icon 底部带了双11的标记 。

是不是很美好, 发版都不需要了。只需要运营配置好节日当天显示就可以了。节日过了之后再换回来 ,堪称完美。但是系统当真允许我们这样搞么 ?
前言
我们知道Android的启动图标是放到 AndroidManifest application 标签下的 android:icon 对应的图片, 并且这张图片必须是 apk 包里的,而且官方 api 没有方案可以更改这个属性值。那么跟产品怼回去说技术方案不可行 ?然而产品已经把市面上已经实现了此功能的方案APP拿到了面前。你告诉产品或许应用市场帮你把 APP 自动更新了, 产品:"我不听 我不听 我不听 "。
按照传统一顿 Google 之后 , 产品需要的效果是可以实现的。但是坑得自己踩。废话不再多说正文开始。
正文
上文已经说明,没有api 可以直接换 launcher icon , 那如何实现呢?
官方 api 是提供同一个app 多个入口能力的 ,上代码
<activity-alias
android:name=".changeicon.TwoLauncherAlias"
android:enabled="false"
android:icon="@drawable/logo_two"
android:label="Two"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".changeicon.OneLauncherAlias"
android:enabled="false"
android:icon="@drawable/logo_one"
android:label="One"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
通过配置多个 activity-alias
并且指定不同的 icon ,在enable 设置为true的情况下,app 会显示多个 icon ,跳转到的Activity 页面 对应 targetActivity value 。在以上 enable 都设置为true的情况下 桌面显示如下,图片是我随便放的

我们可以看到,不光 icon 可以动态, APP name 也是可以动态变化的。但是 icon 需要的图片 以及 name 对应的字符串,都必须预埋到 apk 包内 。
实现动态切换 icon 能力,就是从此功能延伸。代码可以动态控制 activity-alias
enable value ,从而可以实现看起来的动态 切换 icon 的效果。代码控制enable value 如下
getPackageManager().setComponentEnabledSetting(
new ComponentName(this, OneLauncherAlias.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
getPackageManager().setComponentEnabledSetting(
new ComponentName(this, MainActivity.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
也就是 上述 xml 中两个 activity-alias enable
均设置为 false ,我们将上述代码 放到 button 的 onClick 事件中 。app 主 icon 为Android机器人 , 点击按钮之后变为 对号 icon 。 动图如下 (请耐心等待最后效果)

效果这就结束了么? 当然不是, 这才刚刚开始。
从动图可以看出, onClick 后执行上述代码 , app icon 并没有马上切换,这台小米机器大概 10 秒左右的时间会切换 ,并且 会闪一下。
然后我们发现另外一个问题 , 在 icon 执行切换的时候 。点击 icon 会提示找不到此应用 ,一两秒后恢复。
问:什么时候执行icon切换代码 ?我可以在 app 启动的时候执行切换么?
细心的朋友已经发现了,动图上我手动退出到桌面等待 app 切换生效了,那如果我没有手动退出 app 到桌面会怎样呢 ?答案是 当前进程会被kill掉 。看起来跟你的app 闪退了一模一样 ,所以这个效果是绝对不能接受的
问:切换中代码 有标识 PackageManager.DONT_KILL_APP
为什么还会kill 掉app 呢?
这个真不清楚
问:那应该怎么办呢
我做过的尝试如下
1、把切换代码放到 Service 中执行是不是可以呢?
那我一条push 下来就可以了。 结果是失败的, 一样 kill 掉进程 。
2、尝试把service 运行到单独的进程中
结果也是一样 ,app 进程 kill ,看起来跟 挂了一样。
所以只能把 切换icon 的逻辑放到了 "再点一次退出APP" , 相信绝大多数APP都有这个逻辑 。这样是我找到的对用户影响最小的方式。(也就是还是会闪一下)。APP 启动后拉取配置缓存起来, 在 "再点一次退出APP" 这个时机读取配置进行 APP icon 更换。
问:那就没问题可以上线了吧
开始我也是这么觉得的,直到 QA 报了一个 Critical 的bug, 覆盖安装之后 APP 从手机上消失了。并且还 复现不了
现在你应该已经开始怀疑人生了,并且 你搜了国内外 论坛 & blog 发现有人遇到了此问题,并且没有找到解决方案 。人生就是这么奇妙。
最终我们还是定位到了这个问题,原因是 QA 首先安装了新包,从服务器拉到了配置并且 icon 切换到了活动 icon ,然后覆盖安装了前一天的包,一个不包含 activity-alias
的包,这个时候从桌面已经没有办法找到 我们的APP了。
那APP是被卸载了么?
答案是并没有,在应用程序列表还是能找到的,只是入口没了。
那我从应用市场重新下一个安装是不是就可以了呢?
如果应用市场上的包不包含 activity-alias
的话,市场覆盖安装也是没有用的,并且 连卸载都没法卸载 。
问:这种情况能避免么,怎么避免?
答案是可以的 。
1、代码中一旦配置过 activity-alias
是绝对不允许删除的。
解释一下上述结论原因,既然需要配置,配置就有不生效的可能性。假设这样一种场景 , 用户在活动当天打开 APP 并且成功切换到活动icon了, 之后再也没打开过 APP , 过了一段时间用户更新新版本(注意此时用户手机上还是活动icon),新版本把包含 活动icon的 activity-alias
代码移除掉了,升级成功后这个时候 APP 就从桌面永远消失了。 一旦出现,这个问题很难解决。
2、配置活动图标后,活动结束一定及时换回来,也就是换回 APP 的主 launcher icon 。
总结一下
优点
产品要求 icon 能变的效果是可以实现的
局限性 & 风险
1、需要把 icon 预埋到客户端
2、icon 切换的时机有限制
3、icon 切换的时候会闪一下
4、icon 切换的时候 ,会有1秒左右时间 点击 icon 会提示找不到此应用
5、需要及时换回来
6、代码管理不善,有 APP 从桌面再也找不到的风险
此方案过了QA 团队 , 经历了线上 百万活跃用户的考验。这个功能上的时候我还是很忐忑的,最终经受住了考验,还是很欣慰的。如果这篇文章能帮到你很开心,我是乐(yue)进 , 公众号"乐进说" ,有问题欢迎找我探讨。
网友评论