Skip to content

Android 冷门知识

启动图标替换

主要是利用 ActivityAlias 进行设置,在 manifest 文件提前配置好,需要用到的时候动态设置为 true。

  1. 动态替换会导致杀死 App,所以需要 App 在后台时进行切换
  2. 在 manifest 配置的 icon 需要预先放在 apk 中。 如果非要动态加载,可以考虑动态加载资源。参考链接:https://www.jianshu.com/p/e6125ddfaea7,备用链接:https://app.yinxiang.com/shard/s69/nl/17150910/fb6c13b0-0ec9-4918-a5ea-1a8d61c374bc/
<activity-alias
    android:name=".SplashAliasActivity"
    android:enabled="false"
    android:icon="@mipmap/ic_launcher_1111"
    android:targetActivity=".pages.SplashActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity-alias>
object ActivityAliasUtils {
    private val taskMap = mutableMapOf<String, AliasTask>()

    fun register(app: Application) {
        app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
            var startActivityCount = 0
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            }

            override fun onActivityStarted(activity: Activity) {
                if (startActivityCount == 0) {
                    appStart()
                }
                startActivityCount += 1
            }

            override fun onActivityResumed(activity: Activity) {
            }

            override fun onActivityPaused(activity: Activity) {
            }

            override fun onActivityStopped(activity: Activity) {
                startActivityCount -= 1
                if (startActivityCount == 0) {
                    appBackground(activity.application)
                }
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            }

            override fun onActivityDestroyed(activity: Activity) {
            }

        })
    }

    private fun appBackground(application: Application) {
        processTask(application)
    }

    private fun appStart() {

    }

    const val tag = "ActivityAliasUtils"

    private fun processTask(application: Application) {
        for (value in taskMap.values) {
            if (isTimeValid(value)) {
                disableComponent(application, getLauncherActivityName(application))
                enableComponent(application, value.launcherComponentClassName)
            } else {
                disableComponent(application, getLauncherActivityName(application))
                enableComponent(application, value.aliasComponentClassName)
                break
            }
        }
    }

    /**
     * 超出范围
     */
    private fun isTimeValid(value: AliasTask): Boolean {
        return System.currentTimeMillis() > value.endTime || System.currentTimeMillis() < value.startTime
    }

    private fun isTimeValid(value: AliasTask, application: Application): Boolean {
        return when {
            // 超过时间
            isPassedOutDateTime(value) -> {
                Log.d(tag, "OutDateTime ")
                disableComponent(application, getLauncherActivityName(application))
                enableComponent(application, value.launcherComponentClassName)
                false
            }
            // 在活动时间范围内
            isPassedPresetTime(value) -> {
                Log.d(tag, "PresetTime ")
                disableComponent(application, getLauncherActivityName(application))
                enableComponent(application, value.aliasComponentClassName)
                true
            }
            else -> false
        }
    }

    /**
     * 是否已超过预设时间
     * @param task 任务
     */
    private fun isPassedPresetTime(task: AliasTask) =
        System.currentTimeMillis() > task.startTime

    /**
     * 是否已超过过期时间
     * @param task 任务
     *
     */
    private fun isPassedOutDateTime(task: AliasTask) =
        System.currentTimeMillis() > task.endTime

    /**
     * 添加图标切换任务
     * @param newTasks 新任务,可以传多个
     */
    fun addNewTask(vararg newTasks: AliasTask) {
        for (newTask in newTasks) {
            // 防止重复添加任务
            if (taskMap.containsKey(newTask.aliasComponentClassName)) return

            // 校验任务的预设时间和过期时间
            for (queuedTask in taskMap.values) {
                if (newTask.startTime > newTask.endTime) throw IllegalArgumentException("非法的任务预设时间${newTask.startTime}, 不能晚于过期时间")
                if (newTask.startTime <= queuedTask.endTime) throw IllegalArgumentException("非法的任务预设时间${newTask.startTime}, 不能早于已添加任务的过期时间")
            }

            taskMap[newTask.aliasComponentClassName] = newTask
        }
    }

    /**
     * 启用组件
     * @param context 上下文
     * @param className 组件类名
     */
    private fun enableComponent(context: Context, className: String) {
        Log.d(tag, "enableComponent $className")

        val componentName = ComponentName(context, className)

        if (isComponentEnabled(context, componentName)) return //已经启用

        context.packageManager.setComponentEnabledSetting(
            componentName,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
        )
    }

    /**
     * 禁用组件
     * @param context 上下文
     * @param className 组件类名
     */
    private fun disableComponent(context: Context, className: String) {
        Log.d(tag, "disableComponent $className")
        if (className.isNullOrEmpty()) {
            return
        }
        val componentName = ComponentName(context, className)

        if (isComponentDisabled(context, componentName)) return  // 已经禁用

        context.packageManager.setComponentEnabledSetting(
            componentName,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP
        )
    }

    /**
     * 组件是否处于可用状态
     * @param context 上下文
     * @param componentName 组件名
     */
    private fun isComponentEnabled(context: Context, componentName: ComponentName): Boolean {
        val state: Int = context.packageManager.getComponentEnabledSetting(componentName)
        return PackageManager.COMPONENT_ENABLED_STATE_ENABLED == state
    }

    /**
     * 组件是否处于禁用状态
     * @param context 上下文
     * @param componentName 组件名
     */
    private fun isComponentDisabled(context: Context, componentName: ComponentName): Boolean {
        val state: Int = context.packageManager.getComponentEnabledSetting(componentName)
        return PackageManager.COMPONENT_ENABLED_STATE_DISABLED == state
    }

    private fun getLauncherActivityName(context: Context): String {
        val intent = Intent(Intent.ACTION_MAIN, null).apply {
            addCategory(Intent.CATEGORY_LAUNCHER)
            setPackage(context.packageName)
        }
        val resolveInfoList = context.packageManager.queryIntentActivities(intent, 0)
        return if (resolveInfoList.isNotEmpty() && resolveInfoList.isNotEmpty()) resolveInfoList[0].activityInfo.name else ""
    }

}