<
Android 动态更换应用图标
>
上一篇

Repo 的 repo forall C 命令
下一篇

Compose 使用 ttf 自定义字体

Android 动态更换应用图标和名称 activity-alias

1. 创建别名(Alias)

AndroidManifest.xml 中为不同的图标创建别名,

使用 activity-alias 标签,并指定 targetActivity,设置 icon 和 label,enable 是否启用这个图标入口(目标 activity 和 这些 activity-alias 都可以设置enable,并且效果相同)

<application
    ...>

        <activity
            android:name=".MainActivity"
            android:enabled="true"
            android:exported="true"
            android:label="切换图标 Default"
            android:theme="@style/Theme.TestAppIcon">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity-alias
            android:name=".MainActivityAlias1"
            android:enabled="false"
            android:exported="true"
            android:icon="@drawable/icon_chrome"
            android:label="切换图标 Icon1"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityAlias2"
            android:enabled="false"
            android:exported="true"
            android:icon="@drawable/icon_conan"
            android:label="切换图标 Icon2"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

</application>

2. 动态切换图标

在代码中,可以通过启用或禁用别名来动态切换图标


    fun enableComponent(componentName: String) {
        val pm = applicationContext.packageManager
        pm.setComponentEnabledSetting(
            ComponentName(applicationContext.packageName, componentName),
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
        )
    }

    fun disableComponent(componentName: String) {
        val pm = applicationContext.packageManager
        pm.setComponentEnabledSetting(
            ComponentName(applicationContext.packageName, componentName),
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP
        )
    }

    // 更新指定入口
    fun updateComponent(componentNameList: List<String>, enableComponentName: String) {
        // 禁用所有
        componentNameList.forEach {
            disableComponent(it)
        }

        // 启用指定的入口
        enableComponent(enableComponentName)
    }

使用上述定义的方法切换图标

val componentNameList = listOf(
    "${packageName}.MainActivity",
    "${packageName}.MainActivityAlias1",
    "${packageName}.MainActivityAlias2",
)

// 切换
updateComponent(componentNameList, componentNameList[0]) // 使用 index 为 0 的 Activity

完整代码

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_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestAppIcon"
        tools:targetApi="31">

        <activity
            android:name=".MainActivity"
            android:enabled="true"
            android:exported="true"
            android:label="切换图标 Default"
            android:theme="@style/Theme.TestAppIcon">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity-alias
            android:name=".MainActivityAlias1"
            android:enabled="false"
            android:exported="true"
            android:icon="@drawable/icon_chrome"
            android:label="切换图标 Icon1"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityAlias2"
            android:enabled="false"
            android:exported="true"
            android:icon="@drawable/icon_conan"
            android:label="切换图标 Icon2"
            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>

MainActivity.kt

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

        val componentNameList = listOf(
            "${packageName}.MainActivity",
            "${packageName}.MainActivityAlias1",
            "${packageName}.MainActivityAlias2",
        )

        setContent {
            TestAppIconTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    UI(
                        modifier = Modifier.padding(innerPadding),
                        componentNameList,
                    ) {
                        updateComponent(componentNameList, componentNameList[it])
                    }
                }
            }
        }
    }

    fun enableComponent(componentName: String) {
        val pm = applicationContext.packageManager
        pm.setComponentEnabledSetting(
            ComponentName(applicationContext.packageName, componentName),
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
        )
    }

    fun disableComponent(componentName: String) {
        val pm = applicationContext.packageManager
        pm.setComponentEnabledSetting(
            ComponentName(applicationContext.packageName, componentName),
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP
        )
    }

    // 更新指定入口
    fun updateComponent(componentNameList: List<String>, enableComponentName: String) {
        // 禁用所有
        componentNameList.forEach {
            disableComponent(it)
        }

        // 启用指定的入口
        enableComponent(enableComponentName)
    }

}

@Composable
fun UI(modifier: Modifier = Modifier, componentNameList: List<String>, click: (Int) -> Unit) {
    val context = LocalContext.current

    var enableActivity by remember { mutableStateOf("") }
    // 检查启用了的组件
    LaunchedEffect(Unit) { // 首次渲染时使用 packageManager 获取
        for (activityName in componentNameList) {
            val enableState = context.packageManager.getComponentEnabledSetting(ComponentName(context, activityName))
            // 判断 3 种状态即可,
            // 0: 默认, 1: 启用, 2: 禁用,
            val isEnabled = enableState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
            if (isEnabled) {
                enableActivity = activityName
                break
            }
        }
    }

    // 弹窗控制变量
    var showDialog by remember { mutableStateOf(false) }
    var selectedIconIndex by remember { mutableStateOf(0) }

    // 确认弹窗
    if (showDialog) {
        AlertDialog(
            onDismissRequest = { showDialog = false },
            title = { Text("确认切换图标") },
            text = { Text("切换图标成功后可能会自动退出App,是否继续?") },
            confirmButton = {
                Button(
                    onClick = {
                        click.invoke(selectedIconIndex)
                        enableActivity = componentNameList[selectedIconIndex]
                        showDialog = false
                    }
                ) {
                    Text("确认")
                }
            },
            dismissButton = {
                Button(onClick = { showDialog = false }) {
                    Text("取消")
                }
            }
        )
    }

    Column(
        modifier = modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Text(
            "当前图标: ${
                when (enableActivity) {
                    componentNameList[0] -> {
                        "默认图标"
                    }

                    componentNameList[1] -> {
                        "Icon1"
                    }

                    componentNameList[2] -> {
                        "Icon2"
                    }

                    else -> {
                        "未知"
                    }
                }
            }"
        )
        Button({
            selectedIconIndex = 0
            showDialog = true
        }) { Text("切换到 默认图标") }
        Button({
            selectedIconIndex = 1
            showDialog = true
        }) { Text("切换到 Icon1") }
        Button({
            selectedIconIndex = 2
            showDialog = true
        }) { Text("切换到 Icon2") }
    }
}
Top
Foot