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 语句 事务特性…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...