在Android中实现动态应用图标

在Android中实现动态应用图标_第1张图片

在Android中实现动态应用图标

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

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

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


<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类来实现这一点。

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方法。我们正在传递活动别名和主要活动的组件名称,并将活动别名设置为启用状态,将主活动设置为禁用状态。因此,当我们调用此方法时,活动别名将启用,主要活动将禁用。因此,应用程序图标将被更改。
在Android中实现动态应用图标_第2张图片
作为一名软件工程师,我不喜欢这种实现方式。我更希望事情变得简洁和灵活。因此,我认为最好将上述函数更新如下:

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
    )
}

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

changeEnabledComponent(
    enabled = "$packageName.MainActivityAlias",
    disabled = "$packageName.MainActivity"
)

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

  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}\"")
            }
        }
    }

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

<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_activitymain_activity_alias。但是您可以忽略它们,因为它们将与build.gradle文件同步生成。现在,我们可以更新我们的代码以使用BuildConfig类。

changeEnabledComponent(
    enabled = BuildConfig.main_activity_alias,
    disabled = BuildConfig.main_activity
)

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

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

你可能感兴趣的:(Android,framework,Android新特性,android)