在Android中实现动态应用图标

在Android中实现动态应用图标

你可能已经遇到过那些能够完成一个神奇的技巧的应用程序------在你的生日时改变他们的应用图标,然后无缝切换回常规图标。这是一种引发你好奇心的功能,让你想知道,"他们到底是如何做到的?"。嗯,你对这个好奇并不孤单。很多开发者,包括我自己,也曾思考过这个问题。它似乎是一项看似不可能实现的任务,但猜猜怎么着?它并不是!在本文中,我们将解开在运行时更改Android应用图标的奥秘。我们将逐步介绍并展示给你,这不仅是可行的,而且还相当容易管理。

首先,应用图标是从类似于其他应用组件的清单文件中设置的。Android系统读取清单文件并相应地设置应用图标。目前没有办法在运行时更改应用图标。但有个变通方法。那就是使用activity-alias(如果你对activity-alias不熟悉,可以在官方文档中查看)。

https://developer.android.com/guide/topics/manifest/activity-alias-element

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

    <application
        ...
        android:icon="YOUR_ICON"
        android:roundIcon="YOUR_ICON"
    >
        <activity
            ...
            android:name=".MainActivity"
        >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity-alias
            ...
            android:icon="YOUR_ICON_2"
            android:roundIcon="YOUR_ICON_2"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>
    </application>
</manifest>

如你所见,我们有两个activities。一个是主要activity,另一个是activity alias。activity alias默认处于禁用状态,并且具有与主要活动不同的图标。因此,当应用程序安装完成后,将设置主要活动的图标。而当我们启用活动别名时,将设置活动别名的图标。因此,我们可以通过启用和禁用活动别名来在运行时更改应用程序图标。现在,让我们看看如何在运行时启用和禁用活动别名。我们可以使用PackageManager类来实现这一点。

kt 复制代码
fun Activity.changeIcon() {
        packageManager.setComponentEnabledSetting(
            ComponentName(
                this,
                "$packageName.MainActivityAlias"
            ),
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
        )

        packageManager.setComponentEnabledSetting(
            ComponentName(
                this,
                "$packageName.MainActivity"
            ),
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP
        )
}

正如您所看到的,我们正在使用PackageManager类的setComponentEnabledSetting方法。我们正在传递活动别名和主要活动的组件名称,并将活动别名设置为启用状态,将主活动设置为禁用状态。因此,当我们调用此方法时,活动别名将启用,主要活动将禁用。因此,应用程序图标将被更改。

作为一名软件工程师,我不喜欢这种实现方式。我更希望事情变得简洁和灵活。因此,我认为最好将上述函数更新如下:

kt 复制代码
fun Activity.changeEnabledComponent(
        enabled: String,
        disabled: String,
    ) {
        packageManager.setComponentEnabledSetting(
            ComponentName(
                this,
                enabled
            ),
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
        )

    packageManager.setComponentEnabledSetting(
            ComponentName(
                this,
                disabled
            ),
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP
    )
}

因此,更改应用程序图标将仅需要使用组件名称调用该函数即可。例如:

kt 复制代码
changeEnabledComponent(
    enabled = "$packageName.MainActivityAlias",
    disabled = "$packageName.MainActivity"
)

作为一名软件工程师,我们仍在使用硬编码的事实让我很不满意。我甚至希望事情变得更灵活,更容易改变。所以我想更抽象一些组件名称。但是这里有一个挑战,因为我们必须以某种方式获取与清单文件中使用的相同名称。为了解决这个问题,我们可以使用BuildConfig和manifestPlaceholders,而不是使用硬编码的字符串。在应用程序级别的build.gradle文件中,我们可以添加以下代码:

kt 复制代码
  private val mainActivity = "YOURPATH.MainActivity"
    private val mainActivityAlias = "YOURPATH.MainActivityAlias"
    android {
        defaultConfig {
            ...
            manifestPlaceholders.apply {
                set("main_activity", mainActivity)
                set("main_activity_alias", mainActivityAlias)
            }
        }

        buildTypes {
            release {
                isMinifyEnabled = false
                buildConfigField("String", "main_activity", "\"${mainActivity}\"")
                buildConfigField("String", "main_activity_alias", "\"${mainActivityAlias}\"")
            }

            debug {
                isDebuggable = true
                isMinifyEnabled = false 
                buildConfigField("String", "main_activity", "\"${mainActivity}\"")
                buildConfigField("String", "main_activity_alias", "\"${mainActivityAlias}\"")
            }
        }
    }

在这里,我们将组件名称设置为manifestPlaceholders和buildConfigField。因此,我们可以从BuildConfig类中访问它们。当然,我们需要更新清单文件以便使用这些占位符。

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        ...
    >
        <activity
            android:name="${main_activity}"
            ...
        >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <activity-alias
            android:name="${main_activity_alias}"
            ...
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>
    </application>
</manifest>

您可能会看到一些错误提示,指出找不到main_activity或main_activity_alias。但是您可以忽略它们,因为它们将与build.gradle文件同步生成。现在,我们可以更新我们的代码以使用BuildConfig类。

kt 复制代码
changeEnabledComponent(
    enabled = BuildConfig.main_activity_alias,
    disabled = BuildConfig.main_activity
)

现在我们有了一个干净而灵活的代码。我们可以通过调用changeEnabledComponent函数并传递组件名称来在运行时更改应用程序图标。我们还可以在build.gradle文件中更改组件名称。因此,我们可以在运行时更改应用程序图标而无需更改代码。如果需要更详细的示例,请查看下面的代码片段。

kotlin 复制代码
val mainActivity = BuildConfig.main_activity
val mainActivityAlias = BuildConfig.main_activity_alias

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            DynamicIconTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Screen(
                        on30Click = {
                            changeEnabledComponent(
                                enabled = mainActivityAlias,
                                disabled = mainActivity
                            )
                        },
                        on60Click = {
                            changeEnabledComponent(
                                enabled = mainActivityAlias,
                                disabled = mainActivity
                            )
                        }
                    )
                }
            }
        }
    }
}

结论

从本质上讲,我们解开了在运行时动态更改Android应用程序图标是不可实现的神话。通过利用应用程序清单中activity-alias的功能,并熟练地使用PackageManager类,我们揭示了实现此目标的途径。然而,真正的改变在于我们对更干净、更具适应性的代码的追求,我们利用占位符和BuildConfig实现了极高的灵活性。现在,您可以让用户为您的应用程序图标注入自己独特的风格,而无需迷失在代码的泥沼中。

Github

https://github.com/oguzhanaslann/DynamicIcon

参考

https://www.geeksforgeeks.org/how-to-change-app-icon-of-android-programmatically-in-android/

https://developer.android.com/guide/topics/manifest/activity-alias-element

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui