设想一下,当我们正在开发一款应用。随着某个节日的临近,我们可能希望通过更改应用图标来增强用户的节日氛围,例如在图标上添“新年特惠”或者“龙年大吉”等标签。
这种小小的改变看似不经意,却能够吸引用户的注意。
运行时更改应用程序图标
首先,应用程序图标是从清单文件设置的,就像任何其他应用程序组件一样。 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"><applicationandroid: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"><activityandroid: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-aliasandroid: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的硬编码,比如下面的代码:
<activityandroid:name=".MainActivity"...>...
</activity><activity-aliasandroid: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"><applicationandroid: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"><activityandroid: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-aliasandroid: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!