当前位置: 首页 > news >正文

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 动态切换应用图标方案

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

SMART PLC斜坡函数功能块(梯形图代码)

斜坡函数Ramp的具体应用可以参看下面的文章链接: PID优化系列之给定值斜坡函数(PLC代码+Simulink仿真测试)_RXXW_Dor的博客-CSDN博客很多变频器里的工艺PID,都有"PID给定值变化时间"这个参数,这里的给定值变化时间我们可以利用斜坡函数实现,当然也可以利用PT1…...

不那么认真的linux复习

这是个不那么认真的linux总结&#xff0c;可能有一些错误 1、linuxkernel&#xff08;内核&#xff09;shell&#xff08;外壳&#xff09;fs&#xff08;文件系统&#xff09;pro/uti/tol&#xff08;应用程序&#xff09; 2、ls&#xff08;列出文件&#xff09; -a&#xf…...

Redis系列文章总纲

跟着老万学Redis 前言 从事开发工作这么久&#xff0c;很多核心技术其实都还只是局限在满足日常开发工作中的基础使用&#xff0c;并没有完整的总结研究。今年的目标之一是完成几个技术栈的系列博客&#xff0c;系统的总结一下知识体系&#xff0c;目前计划是从Redis开始。 Re…...

更新丨三大模块升级,助力高效交付商业项目!

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

C++回顾(二)——const和引用

2.1 C中的const 2.1.1 C与C中const的比较 &#xff08;1&#xff09;C语言中的const C语言中 const修饰的变量是一个 常变量&#xff0c;本质还是变量&#xff0c;有自己的地址空间。 &#xff08;2&#xff09;C中的const 1、C中 const 变量声明的是一个真正的常量&#xff…...

MXNet中使用双向循环神经网络BiRNN对文本进行情感分类<改进版>

在上一节的情感分类当中&#xff0c;有些评论是负面的&#xff0c;但预测的结果是正面的&#xff0c;比如&#xff0c;"this movie was shit"这部电影是狗屎&#xff0c;很明显就是对这部电影极不友好的评价&#xff0c;属于负类评价&#xff0c;给出的却是positive。…...

DNS 域名解析

介绍域名 网域名称&#xff08;英语&#xff1a;Domain Name&#xff0c;简称&#xff1a;Domain&#xff09;&#xff0c;简称域名、网域。 域名是互联网上某一台计算机或计算机组的名称。 域名可以说是一个 IP 地址的代称&#xff0c;目的是为了便于记忆。例如&#xff0c…...

Spring MVC 源码- ViewResolver 组件

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

【Hello Linux】初识冯诺伊曼体系

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍冯诺伊曼体系 冯诺伊曼体系 冯诺伊曼体系结构的合理性 我们在Linux的第一篇博客中讲解了第一台计算机的发明是为了解决导弹的…...

mysql索引,主从多个核心主题去探索问题。

网上收集不错的优化方案 事务 mvcc 详讲 详讲 索引 索引概念 MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据 库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数 据…...

前端一面必会面试题(边面边更)

哪些情况会导致内存泄漏 以下四种情况会造成内存的泄漏&#xff1a; 意外的全局变量&#xff1a; 由于使用未声明的变量&#xff0c;而意外的创建了一个全局变量&#xff0c;而使这个变量一直留在内存中无法被回收。被遗忘的计时器或回调函数&#xff1a; 设置了 setInterval…...

【Hello Linux】初识操作系统

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍下操作系统的概念 操作系统 操作系统是什么&#xff1f; 操作系统是管理软硬件资源的软件 为什么要设计操作系统 为什么要设…...

完美的vue3动态渲染菜单路由全程

前言&#xff1a; 首先&#xff0c;我们需要知道&#xff0c;动态路由菜单并非一开始就写好的&#xff0c;而是用户登录之后获取的路由菜单再进行渲染&#xff0c;从而可以起到资源节约何最大程度的保护系统的安全性。 需要配合后端&#xff0c;如果后端的值不匹配&#xff0…...

2023年CDGA考试模拟题库(301-400)

2023年CDGA考试模拟题库(301-400) 300.无附加价值的信息通常也不会被删除,因为:[1分] A.它不应该被移除,所有数据都是有价值的 B.我们可能在以后的某个阶段需更这些信息 C.规程中不明确是否应该保留 D.数据是一种资产它很可能在未来被认为是有价值的 E.规程中不明确哪些是…...

Linux-常见命令

&#x1f69c;关注博主&#xff1a;翻斗花园代码手牛爷爷 &#x1f699;Gitee仓库&#xff1a;牛爷爷爱写代码 目录&#x1f692;xshell热键&#x1f697;Linux基本命令&#x1f697;ls指令&#x1f695;pwd指令&#x1f696;cd指令&#x1f68c;touch指令&#x1f68d;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 数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性&#xff0c;保证成批的 SQL 语句要么全部执行&#xff0c;要么全部不执行。事务用来管理 insert,update,delete 语句 事务特性…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

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

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...