设想一下,当我们正在开发一款应用。随着某个节日的临近,我们可能希望通过更改应用图标来增强用户的节日氛围,例如在图标上添“新年特惠”或者“龙年大吉”等标签。
这种小小的改变看似不经意,却能够吸引用户的注意。
运行时更改应用程序图标
首先,应用程序图标是从清单文件设置的,就像任何其他应用程序组件一样。 Android系统读取manifest文件并相应地设置应用程序图标。
目前无法直接使用相关的代码在运行时更改应用程序图标,但有一个解决方案,就是使用activity-alias。
步骤1:准备图标资源🖼️
步骤2:修改AndroidManifest.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:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher1"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TestAndroid"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.TestAndroid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias
android:name=".MainActivityAlias"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher2"
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 和一个activity-alias, 并在activity-alias中设置了新的icon。默认情况下disable activity-alias,也就是设置android:enabled=“false”。
应用安装时,启动的是MainActivity,然后我们可以调用代码将主Activity设置成MainActivityAlias,就可以实现运行时更改Android 应用程序图标的内容了。(即disable MainActivity,enable MainActivityAlias。)
如何设置呢,就要使用到PackageManager 这个类了。
步骤3:编写更改图标的代码🔀 (假设我们使用Compose)
// 编写Activity的扩展方法
fun Activity.changeEnabledComponent(
enabledPkgName: String,
disabledPkgName: String,
) {
packageManager.setComponentEnabledSetting(
ComponentName(this, enabledPkgName),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
packageManager.setComponentEnabledSetting(
ComponentName(this, disabledPkgName),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
@Composable
fun ChangeIconViews(activity: Activity, enabledPkgName: String, disabledPkgName: String) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
val btnModifier = Modifier.padding(vertical = 2.5.dp)
Button(modifier = btnModifier, onClick = {
activity.changeEnabledComponent(
enabledPkgName = enabledPkgName,
disabledPkgName = disabledPkgName
)
}) {
Text(text = "切换成Test 1")
}
Button(modifier = btnModifier, onClick = {
activity.changeEnabledComponent(
enabledPkgName = disabledPkgName,
disabledPkgName = enabledPkgName
)
}) {
Text(text = "切换成Test 2")
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TestAndroidTheme {
Surface(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 5.dp),
color = MaterialTheme.colorScheme.background
) {
Box(
contentAlignment = Alignment.Center
) {
ChangeIconViews(
activity = this@MainActivity,
enabledPkgName = "com.example.testandroid.MainActivity",
disabledPkgName = "com.example.testandroid.MainActivityAlias"
)
}
}
}
}
}
}
- 函数 changeEnabledComponent用于启用或禁用指定的组件,使用 packageManager 对象的 setComponentEnabledSetting 方法来设置组件的启用状态。
- 函数 ChangeIconViews 是一个Composable函数,用于显示一个列(Column)布局,并包含两个按钮。
- 最后在MainActivity的 onCreate 方法中放置ChangeIconViews。
然后就能够实现在运行时更改Android应用程序图标这个需求了。
步骤4:优化我们的代码📚
但是作为一名开发人员,我们想要我们的代码更整洁更灵活的话,我们就应该考虑优化我们的代码,比如硬编码就不是很合适:
ChangeIconViews(
activity = this@MainActivity,
enabledPkgName = "com.example.testandroid.MainActivity",
disabledPkgName = "com.example.testandroid.MainActivityAlias"
)
假设我们使用最新版的Android Studio工具开发,使用build.gradle.kts编写我们的编译脚本(使用build.gradle的话其实也差不多,就是语法不大一样),就可以像下面这样:
...
private val mainActivity = "com.example.testandroid.MainActivity"
private val mainActivityAlias = "com.example.testandroid.MainActivityAlias"
android {
...
defaultConfig {
...
buildConfigField("String", "main_activity", "\"${mainActivity}\"")
buildConfigField("String", "main_activity_alias", "\"${mainActivityAlias}\"")
}
...
buildFeatures {
...
// 使用自定义的buildConfig需要开启这个功能
buildConfig = true
}
...
}
所以我们的MainActivity的onCreate就可以换成下面的代码:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TestAndroidTheme {
Surface(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 5.dp),
color = MaterialTheme.colorScheme.background
) {
Box(
contentAlignment = Alignment.Center
) {
ChangeIconViews(
activity = this@MainActivity,
enabledPkgName = BuildConfig.main_activity,
disabledPkgName = BuildConfig.main_activity_alias
)
}
}
}
}
}
}
除此之外我们发现AndroidManifest.xml中也有关于mainActivity和mainActivityAlias的硬编码,比如下面的代码:
<activity
android:name=".MainActivity"
...>
...
</activity>
<activity-alias
android:name=".MainActivityAlias"
...
android:targetActivity=".MainActivity">
...
</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:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher1"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TestAndroid"
tools:targetApi="31">
<activity
android:name="${main_activity}"
android:exported="true"
android:theme="@style/Theme.TestAndroid">
<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:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher2"
android:targetActivity="${main_activity}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>
android {
...
defaultConfig {
...
manifestPlaceholders.apply {
set("main_activity", mainActivity)
set("main_activity_alias", mainActivityAlias)
}
buildConfigField("String", "main_activity", "\"${mainActivity}\"")
buildConfigField("String", "main_activity_alias", "\"${mainActivityAlias}\"")
}
...
}
完整代码地址
感谢阅读,Best Regards!