经常听到大家讨论类似的需求,怀疑大厂是不是用了此方案,据我个人了解,多数头部 app 其实都是发版来更新节假日的 icon。当然本方案也是一种可选的方案,以前我也调研过,存在问题和作者所述差不多,此外原文链接作者也回复了很多疑问,可以同时了解。

效果图

产品需求

市面上很多App能根据特定活动,动态切换应用图标达到宣传目的,例如淘宝双十一,国庆节等等。那么我们怎样才能在不发新版本的情况下,动态切换应用图标呢?

具体方案

1.图标更换:在AndroidManifest设置应用入口Activity的别名,然后通过setComponentEnabledSetting动态启用或禁用别名进行图标切换。

2.控制图标显示:冷启动App时,调用接口判断是否需要切换icon。

3.触发时机:监听App前后台切换,当App处于后台时切换图标,使得用户无感知。

代码实现

在AndroidManifest.xml中给入口Activity设置activity-alias

<applicationandroid:name=".MyApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:supportsRtl="true"android:theme="@style/Theme.SwitchIcon"><!-- 原MainActivity --><activity android:name=".MainActivity" /><!-- 固定设置一个默认的别名,用来替代原MainActivity --><activity-aliasandroid:name=".DefaultAliasActivity"android:enabled="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:targetActivity=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity-alias><!-- 别名1,特定活动需要的图标如:双11,国庆节等 --><activity-aliasandroid:name=".Alias1Activity"android:enabled="false"android:icon="@mipmap/ic_launcher_show"android:label="@string/app_name"android:targetActivity=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity-alias></application>

activity-alias标签中的属性如下:

标签

作用

android:name

别名,命名规则同Actively

android:enabled

是否启用别名,这里的主要作用的控制显示应用图标

android:icon

应用图标

android:label

应用名

android:targetActivity

必须指向原入口Activity

在MainActivity中,通过启用或禁用别名进行图标切换

/*** 设置默认的别名为启动入口*/
public void setDefaultAlias() {PackageManager packageManager = getPackageManager();ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);}/*** 设置别名1为启动入口*/
public void setAlias1() {PackageManager packageManager = getPackageManager();ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}

ForegroundCallbacks监听App前后台切换

/*** 监听App前后台切换*/
public class ForegroundCallbacks implements Application.ActivityLifecycleCallbacks {public static final long CHECK_DELAY = 500;public static final String TAG = ForegroundCallbacks.class.getName();public interface Listener {void onForeground();void onBackground();}private static ForegroundCallbacks instance;private boolean foreground = false, paused = true;private Handler handler = new Handler();private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();private Runnable check;public static ForegroundCallbacks init(Application application) {if (instance == null) {instance = new ForegroundCallbacks();application.registerActivityLifecycleCallbacks(instance);}return instance;}public static ForegroundCallbacks get(Application application) {if (instance == null) {init(application);}return instance;}public static ForegroundCallbacks get(Context ctx) {if (instance == null) {Context appCtx = ctx.getApplicationContext();if (appCtx instanceof Application) {init((Application) appCtx);}throw new IllegalStateException("Foreground is not initialised and " +"cannot obtain the Application object");}return instance;}public static ForegroundCallbacks get() {if (instance == null) {throw new IllegalStateException("Foreground is not initialised - invoke " +"at least once with parameterised init/get");}return instance;}public boolean isForeground() {return foreground;}public boolean isBackground() {return !foreground;}public void addListener(Listener listener) {listeners.add(listener);}public void removeListener(Listener listener) {listeners.remove(listener);}@Overridepublic void onActivityResumed(Activity activity) {paused = false;boolean wasBackground = !foreground;foreground = true;if (check != null)handler.removeCallbacks(check);if (wasBackground) {Log.d(TAG, "went foreground");for (Listener l : listeners) {try {l.onForeground();} catch (Exception exc) {Log.d(TAG, "Listener threw exception!:" + exc.toString());}}} else {Log.d(TAG, "still foreground");}}@Overridepublic void onActivityPaused(Activity activity) {paused = true;if (check != null)handler.removeCallbacks(check);handler.postDelayed(check = new Runnable() {@Overridepublic void run() {if (foreground && paused) {foreground = false;Log.d(TAG, "went background");for (Listener l : listeners) {try {l.onBackground();} catch (Exception exc) {Log.d(TAG, "Listener threw exception!:" + exc.toString());}}} else {Log.d(TAG, "still foreground");}}}, CHECK_DELAY);}@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Overridepublic void onActivityStarted(Activity activity) {}@Overridepublic void onActivityStopped(Activity activity) {}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Overridepublic void onActivityDestroyed(Activity activity) {}
}

需要在Application中调用ForegroundCallbacks.init(this)进行初始化。

在MainActivity中实现ForegroundCallbacks.Listener对App进行监听,当处于后台判断是否切换应用图标

完整的MainActivity代码:

public class MainActivity extends AppCompatActivity implements ForegroundCallbacks.Listener {private int position = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//添加app前后台监听ForegroundCallbacks.get(this).addListener(this);findViewById(R.id.tv_default).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {position = 0;}});findViewById(R.id.tv_alias1).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {position = 1;}});}@Overrideprotected void onDestroy() {// 移除app前后台监听ForegroundCallbacks.get(this).removeListener(this);super.onDestroy();}@Overridepublic void onForeground() {}@Overridepublic void onBackground() {//根据具体业务需求设置切换条件,我公司采用接口控制icon切换if (position == 0) {setDefaultAlias();} else {setAlias1();}}/*** 设置默认的别名为启动入口*/public void setDefaultAlias() {PackageManager packageManager = getPackageManager();ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);}/*** 设置别名1为启动入口*/public void setAlias1() {PackageManager packageManager = getPackageManager();ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);}
}

具体缺陷

具体缺陷如下:

1. 切换icon会关闭应用进程,不是崩溃所以不会上报bugly。

2. 切换icon需要时间,部分华为机型要10s左右,之后能正常打开。

3. 切换icon过程中,部分机型点击图标无法打开应用,提示应用未安装。

Demo的github地址

https://github.com/FengFeiBiao/SwitchIcon

Android 动态切换应用图标方案相关推荐

  1. uniapp 动态切换应用图标、名称插件(如新年、国庆等) Ba-ChangeIcon

    动态切换应用图标.名称(如新年.国庆等) Ba-ChangeIcon 简介(下载地址) Ba-ChangeIcon 是一款uniapp动态切换应用图标.名称的插件.可实现过年.过节动态切换应用图标的效 ...

  2. android+动态更改app图标,Android开发:如何动态更换APP图标

    在Android开发中,通常会有这样的需求,逢年过节UI的小伙伴们都会做出节日或活动相关的APP图标让我们更换,可是每次更换都要发版实现.那么,如何在不发版的情况下动态地更换我们的图标呢?本篇文章就来 ...

  3. vue动态切换css文件_vue在线动态切换主题色方案

    主要原理是利用webpack插件webpack-theme-color-replacer提取相关颜色css然后根据配置动态生成替换的css 具体实现步骤如下: 1.添加webpack插件,新建文件we ...

  4. Android动态修改桌面图标新坑总结

    一.优秀文章概述 下面的文章描述动态修改桌面图标的基本方法,并且总结了很多坑及解决方案,以下是在此文章基础上的补充 <Android动态修改应用图标和名称> 二.第一个新坑 Android ...

  5. Android 动态使用Iconfont图标

    Android 动态使用Iconfont图标 Android 动态使用Iconfont图标 问题 思路 附 Android 动态使用Iconfont图标 问题 每当进行增删操作后,Iconfont图标 ...

  6. Android 动态改变桌面图标

    其实很简单,利用 AndroidManifest.xml 文件中的 <activity-alias> 标签即可实现. <activity-alias> 介绍 大家知道,对于 A ...

  7. Android动态切换主题

    软件换肤从功能上可以划分三种: 1) 软件内置多个皮肤,不可由用户增加或修改: 最低的自由度,软件实现相对于后两种最容易. 2) 官方提供皮肤供下载,用户可以使用下载的皮肤: 用户可选择下载自己喜欢的 ...

  8. android 动态切换主题,android动态主题切换(RRO 技术)

    android上的主题切换,Android从M开始加入了动态资源overlay机制 runtime resource overlay(RRO),这个是sony贡献的,实现机制如下图,就是在框架中建立一 ...

  9. vue动态发布到线上_vue在线动态切换主题色方案

    主要原理是利用webpack插件webpack-theme-color-replacer提取相关颜色css然后根据配置动态生成替换的css 具体实现步骤如下: 1.添加webpack插件,新建文件we ...

最新文章

  1. 应用交换技术的负载均衡算法
  2. mybatis脱离项目逆向生成映射文件
  3. 使用Pylint进行Python代码规范检查
  4. python下划线怎么输入_python长的下划线怎么打
  5. 飞鸽传书,去看了下WEBBROWSER的资料
  6. 三点拓宽自己的工作思路
  7. 写的网页标题乱码,怎么办?
  8. paip.c#.nett 系统托盘动态图标闪烁图标
  9. 正确的Kado ED「永遠のこたえ」
  10. 计算机采用二进制码的优点
  11. 树莓派54/100 - Pico连接DS1307时钟模块,做个实时数字钟
  12. App Store审核规范
  13. ADI超声解决方案 和系统成像设计
  14. Debian 11 安装,超详细!
  15. VS Code 常用必备插件
  16. 采购招投标系统-高效管控招采流程-降低采购成本
  17. 对话哈佛大学教授Lukin:量子计算将在我们有生之年普及! | AI英雄
  18. 解决无法从公司 Maven 私服下载依赖的问题
  19. 程序员需要了解的硬核知识之操作系统入门
  20. API工具栏教你如何采集淘宝拼多多商品详情数据

热门文章

  1. linux 查找目录或文件,linux 查找目录或文件详解
  2. Day02 HTML制作表格以及练习
  3. 日文电子邮件的格式礼仪
  4. 推荐清晖的一套不错的系列讲解ITIL的教程
  5. 计算机专业班级工作计划,计算机专业班主任的工作计划.doc
  6. 网站被同行恶意攻击了怎么办
  7. 6、文件的路径问题、错误的处理
  8. 学生管理系统软件实现与测试,学生信息管理系统实现
  9. 每日新闻:微软加入Linux相关专利池;英特尔发布Win10核显驱动;谷歌丑闻后推新品;马云重登中国首富;腾讯云举办智慧社区大会...
  10. 用例图类图练习(网上购物平台)