Android 动态切换应用图标方案
经常听到大家讨论类似的需求,怀疑大厂是不是用了此方案,据我个人了解,多数头部 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 动态切换应用图标方案
经常听到大家讨论类似的需求,怀疑大厂是不是用了此方案,据我个人了解,多数头部 app 其实都是发版来更新节假日的 icon。当然本方案也是一种可选的方案,以前我也调研过,存在问题和作者所述差不多,此外原文链…...
SMART PLC斜坡函数功能块(梯形图代码)
斜坡函数Ramp的具体应用可以参看下面的文章链接: PID优化系列之给定值斜坡函数(PLC代码+Simulink仿真测试)_RXXW_Dor的博客-CSDN博客很多变频器里的工艺PID,都有"PID给定值变化时间"这个参数,这里的给定值变化时间我们可以利用斜坡函数实现,当然也可以利用PT1…...
不那么认真的linux复习
这是个不那么认真的linux总结,可能有一些错误 1、linuxkernel(内核)shell(外壳)fs(文件系统)pro/uti/tol(应用程序) 2、ls(列出文件) -a…...
Redis系列文章总纲
跟着老万学Redis 前言 从事开发工作这么久,很多核心技术其实都还只是局限在满足日常开发工作中的基础使用,并没有完整的总结研究。今年的目标之一是完成几个技术栈的系列博客,系统的总结一下知识体系,目前计划是从Redis开始。 Re…...

更新丨三大模块升级,助力高效交付商业项目!
功能更新!本文将介绍最新升级的步进漫游、行业方案、VR漫游三个模块,让您更快更好的了解系统能力,为您带来更加便捷、高效的使用体验。步进漫游 离线导出步进式漫游系统,是基于全景图自动生成三维建模的解决方案,实现大…...

C++回顾(二)——const和引用
2.1 C中的const 2.1.1 C与C中const的比较 (1)C语言中的const C语言中 const修饰的变量是一个 常变量,本质还是变量,有自己的地址空间。 (2)C中的const 1、C中 const 变量声明的是一个真正的常量ÿ…...
MXNet中使用双向循环神经网络BiRNN对文本进行情感分类<改进版>
在上一节的情感分类当中,有些评论是负面的,但预测的结果是正面的,比如,"this movie was shit"这部电影是狗屎,很明显就是对这部电影极不友好的评价,属于负类评价,给出的却是positive。…...

DNS 域名解析
介绍域名 网域名称(英语:Domain Name,简称:Domain),简称域名、网域。 域名是互联网上某一台计算机或计算机组的名称。 域名可以说是一个 IP 地址的代称,目的是为了便于记忆。例如,…...

Spring MVC 源码- ViewResolver 组件
ViewResolver 组件ViewResolver 组件,视图解析器,根据视图名和国际化,获得最终的视图 View 对象回顾先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 ViewResolver 组件,可以回到《一个请求响应的旅行过程》中的 …...

【Hello Linux】初识冯诺伊曼体系
作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:简单介绍冯诺伊曼体系 冯诺伊曼体系 冯诺伊曼体系结构的合理性 我们在Linux的第一篇博客中讲解了第一台计算机的发明是为了解决导弹的…...
mysql索引,主从多个核心主题去探索问题。
网上收集不错的优化方案 事务 mvcc 详讲 详讲 索引 索引概念 MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据 库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数 据…...

前端一面必会面试题(边面边更)
哪些情况会导致内存泄漏 以下四种情况会造成内存的泄漏: 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。被遗忘的计时器或回调函数: 设置了 setInterval…...

【Hello Linux】初识操作系统
作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:简单介绍下操作系统的概念 操作系统 操作系统是什么? 操作系统是管理软硬件资源的软件 为什么要设计操作系统 为什么要设…...
完美的vue3动态渲染菜单路由全程
前言: 首先,我们需要知道,动态路由菜单并非一开始就写好的,而是用户登录之后获取的路由菜单再进行渲染,从而可以起到资源节约何最大程度的保护系统的安全性。 需要配合后端,如果后端的值不匹配࿰…...
2023年CDGA考试模拟题库(301-400)
2023年CDGA考试模拟题库(301-400) 300.无附加价值的信息通常也不会被删除,因为:[1分] A.它不应该被移除,所有数据都是有价值的 B.我们可能在以后的某个阶段需更这些信息 C.规程中不明确是否应该保留 D.数据是一种资产它很可能在未来被认为是有价值的 E.规程中不明确哪些是…...

Linux-常见命令
🚜关注博主:翻斗花园代码手牛爷爷 🚙Gitee仓库:牛爷爷爱写代码 目录🚒xshell热键🚗Linux基本命令🚗ls指令🚕pwd指令🚖cd指令🚌touch指令🚍mkdir指…...

2.25测试对象分类
一.按照测试对象划分1.界面测试又称UI测试,按照界面的需求(一般是ui设计稿)和界面的设计规则,对我们软件界面所展示的全部内容进行测试和检查.对于非软件来说:颜色,大小,材质,整体是否美观对于软件来说:输入框,按钮,文字,图片...的尺寸,颜色,形状,整体适配,清晰度等等,2.可靠性…...
【Zabbix实战之部署篇】Zabbix客户端的安装部署方法
【Zabbix实战之部署篇】Zabbix客户端的安装部署方法 一、Zabbix-agent2介绍1.Zabbix-agent2简介2.Zabbix-agent2优点3.主动模式和被动模式二、环境规划1.Zabbix服务器部署链接2.IP规划三、配置客户端系统环境1.关闭selinux2.放行端口或关闭防火墙四、安装zabbix-agent21.下载za…...

【CSS】CSS 层叠样式表 ② ( CSS 引入方式 - 内嵌样式 )
文章目录一、CSS 引入方式 - 内嵌样式1、内嵌样式语法2、内嵌样式示例3、内嵌样式完整代码示例4、内嵌样式运行效果一、CSS 引入方式 - 内嵌样式 1、内嵌样式语法 CSS 内嵌样式 , 一般将 CSS 样式写在 HTML 的 head 标签中 ; CSS 内嵌样式 语法如下 : <head><style …...

MySQL事务与索引
MySQL事务与索引 一、事务 1、事务简介 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。事务用来管理 insert,update,delete 语句 事务特性…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...