Android 中的动态应用程序图标
一、需求
您可能遇到过那些可以实现巧妙技巧的应用程序 - 更改应用程序图标(也许是在您的生日那天),然后无缝切换回常规图标。这种功能会激起你的好奇心,让你想知道,他们到底是怎么做到的?
好吧,你并不是唯一一个有好奇心的人。许多开发人员,包括我自己,都思考过这个问题。这似乎是看似不可能的任务之一,但你猜怎么着?它不是!在本文中,我们将揭开运行时更改 Android 应用程序图标背后的谜团。我们将一步步为您分解,并向您展示它不仅可行,而且非常易于管理。
二、解决方案
首先,应用程序图标是从清单文件设置的,就像任何其他应用程序组件一样。Android系统读取manifest文件并相应地设置应用程序图标。目前无法在运行时更改应用程序图标。但有一个解决方法。也就是使用一个activity-alias
(如果你对activity-alias不熟悉,可以查看这里的官方文档)。
三、方案实现
<?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>
如您所见,我们有两项活动。一个是主要活动,另一个是活动别名。默认情况下禁用活动别名。它的图标与主要活动不同。因此,当安装应用程序时,将设置主要活动的图标。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方法。我们正在传递活动别名和主要活动的组件名称。我们将活动别名设置为启用,将主要活动设置为禁用。因此,当我们调用此方法时,活动别名将被启用,而主活动将被禁用。所以应用程序图标将会改变。
另外,作为一名软件工程师,我不喜欢这种实现方式。我希望事情干净且灵活。因此,我认为最好将上面的函数更新如下:
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"
)
另外,作为一名软件工程师,我们仍然使用硬代码这一事实让我很困扰。我什至希望事情变得更加灵活,更加愿意改变。所以我想对组件名称进行更多抽象。但这是一个挑战,因为我们需要以某种方式获得与清单文件中使用的相同的名称。为了解决这个问题,我们可以使用BuildConfig和manifestPlaceholders. 在应用程序级 build.gradle 文件中,我们可以添加以下代码:(我假设您正在为 build.gradle 文件使用Kotlin DSL 。)
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类访问它们。当然,我们需要更新清单文件,以便它使用占位符。
<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
类。
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 来实现最终的灵活性。现在,您可以让用户将自己独特的风格注入到您的应用程序图标中,而不会迷失在代码杂草中。@[toc](Android 中的动态应用程序图标)