【调研】Android app动态更新launcher_icon

1、背景

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

2、方案

注意⚠️:

  • Android 系统不允许应用程序直接更改已安装应用的图标。每个应用的图标是由该应用自身的代码和资源定义的,只有应用的开发者可以通过【更新应用】来更改图标
  • 动态替换icon,只能替换本地内置的icon,无法从服务器端获取来更新icon;必须把相关资源提前放入到 App 中,无法通过网络下载的方式再去替换应用 icon。

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

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

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

三、关键代码

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

  <!--  
/**
    * @Author    : zhh227
    * date       : 2025/01/05
    */
  -->
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat">

       <activity android:name=".ExchangeIconActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity-alias
            android:name=".MainActivity1"
            android:enabled="false"
            android:icon="@mipmap/icon_1"
            android:label="@string/app_name"
            android:targetActivity=".ExchangeIconActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivity2"
            android:enabled="false"
            android:icon="@mipmap/icon_2"
            android:label="@string/app_name"
            android:targetActivity=".ExchangeIconActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity-alias>

       
    </application>

</manifest>
kotlin 复制代码
class ExchangeIconActivity: AppCompatActivity() {

    var changeTo: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_icon)
        val tvInfo = findViewById(R.id.tv_info) as? TextView
        try {
            val packageInfo = packageManager.getPackageInfo(packageName, 0)
            tvInfo?.text = """
            ${"Name:" + componentName.className}
            Version:${packageInfo.versionName}
            """.trimIndent()
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }

    fun changeToIcon1(v: View?) {
        changeTo = javaClass.name + "1"
        changeLauncher(changeTo!!)
    }

    fun changeToIcon2(v: View?) {
        changeTo = javaClass.name + "2"
        changeLauncher(changeTo!!)
    }

    fun reset(v: View?) {
        changeTo = javaClass.name
        changeLauncher(changeTo!!)
    }

    override fun onDestroy() {
        if (changeTo != null) {
            changeLauncher(changeTo!!)
        }
        super.onDestroy()
    }

    private fun changeLauncher(name: String) {
        val pm = packageManager
        //隐藏之前显示的桌面组件
        pm.setComponentEnabledSetting(
            componentName,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
        )
        //显示新的桌面组件
        pm.setComponentEnabledSetting(
            ComponentName(this@ExchangeIconActivity, name),
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
        )
    }
}

4、适用范围

1、该功能支持Android 8.9.20及以上的版本

2、Android部分机型有兼容性问题,暂不支持使用该功能

3、部分样式有新版本限制,不适配设置为默认样式

5、可能的坑

1、动态替换icon,只能替换本地内置的icon,无法从服务器端获取来更新icon;(网易云音乐应该也是)

2、动态替换icon以后,应用内更新的时候必须要切换到原始icon),否则可能导致更新安装失败(崩溃)(AS上表现为adb运行会失败),或者升级后应用图标出现多个甚至应用图标都不显示的情况(可以通过下面【5】规则解决掉,所以这是一个坑点,不是肯定会发生的问题。);

3、Android系统动态替换app icon会有延迟,在不同的手机系统上刷新icon的时间不一样,大概在10秒左右,在这个时间内点击icon会提示应用未安装(提示可能会有差别,有些厂商或者机型点了没有反应);

4、更换icon的代码运行后一会应用就闪退了,或者导致显示中的Dialog和PopupWindow报错崩溃(这个问题和第二个问题有很大的相关性,按下面【5】给出的规则实行的话是可以解决的。

5、本地编译代码进行覆盖安装或者升级包会:

vbnet 复制代码
Error while executing: am start -n "com.heng.changeiconapp/com.heng.changeiconapp.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxxx.changeiconapp/.MainActivity }
Error type 3
Error: Activity class {com.heng.changeiconapp/com.xxxx.changeiconapp.MainActivity} does not exist.

Error while Launching activity
Failed to launch an application on all devices

Clean Project。接着再选择 Rebuild Project,在设备上卸载旧版本的应用,然后重新安装新版本。

apk覆盖安装或者升级包无影响。

6、防坑专用

  1. 关于 Activity 的 android:enabled 属性

不建议在代码中动态设置 Activity 的 android:enabled 属性。这种做法可能在图标切换过程中导致应用闪退,经过测试发现,小米、华为以及官方模拟器均存在此问题。

  1. 清单文件中的 android:enabled 设置

在清单文件中将 Activity 的 android:enabled 属性设置为 false,并在后续版本中保持此值不变。如果在后续版本中将其设置为 true,可能会导致应用升级后出现多个图标的情况。

  1. 设置默认的 Activity-alias

为应用配置一个默认的 Activity-alias,以确保图标的唯一显示。此设置可替代第一点中提到的将 Activity 的 android:enabled 设置为 false 可能导致桌面上无应用图标的问题。

  1. Activity-alias 的动态管理

对于 android:enabled="true" 的默认显示项,尽量避免在中途进行更改。如果确实需要更新默认值,应通过代码进行动态管理。

  1. 避免多个 Activity-alias

不要为 android:enabled="true" 的 Activity-alias 设置多个实例。若通过代码尝试隐藏其中一个或多个,可能会导致图标消失,此问题在第二点中已有提及。

  1. 新版本中的 Activity-alias 设置

在后续版本中新增的 Activity-alias 必须设置 android:enabled="false",并在清单文件中保持此值,然后通过代码动态更新。

  1. 保留旧版本的 Activity-alias

新版本的 Activity-alias 必须包含所有旧版本的 Activity-alias,以防止在覆盖安装后出现应用图标消失或者崩溃的情况。

  1. 关于 enabled=false 的 Activity 的限制

设置为 enabled=false 的 Activity 无法通过显式 Intent 进行调用,这将导致错误。例如,如果在应用中推送服务发送了打开 SplashActivity 的通知,而该 SplashActivity 设置为 enabled=false,则无法打开,并会出现错误日志。为避免此问题,建议将启动入口的 Activity 单独定义,确保其仅作为启动入口使用,避免被其他地方调用。

相关推荐
myepicure8881 分钟前
Windows下调试Dify相关组件(1)--前端Web
前端·llm
JosieBook14 分钟前
【ASP.NET学习】Web Pages 最简单的网页编程开发模型
前端·asp.net·菜鸟教程
兴趣使然_29 分钟前
[QCustomPlot] 交互示例 Interaction Example
android·交互
雨 子1 小时前
Spring Web MVC
前端·spring boot·spring·mvc·postman
计算机毕设指导61 小时前
基于Springboot美食推荐商城系统【附源码】
java·前端·spring boot·后端·spring·tomcat·美食
!win !1 小时前
外部H5唤起常用小程序链接规则整理
前端·小程序
染指悲剧1 小时前
vue实现虚拟列表滚动
前端·javascript·vue.js
林涧泣2 小时前
【Uniapp-Vue3】navigator路由与页面跳转
前端·vue.js·uni-app
浩浩测试一下2 小时前
Web渗透测试之XSS跨站脚本之JS输出 以及 什么是闭合标签 一篇文章给你说明白
前端·javascript·安全·web安全·网络安全·html·系统安全
Yang-Never3 小时前
Kotlin->Kotlin协程的取消机制
android·java·开发语言·kotlin·android studio·idea