Kuki93 / KeyboardHelper

完美复刻微信软键盘与表情面板切换
10 stars 2 forks source link

有向无环图 #5

Open Kuki93 opened 1 year ago

Kuki93 commented 1 year ago
object InitTaskService : CoroutineScope by MainScope() {

    private lateinit var taskList: MutableList<TaskInfo>

    private var collectTaskList: MutableList<AutoInitTask>? = null

    private val listenerMap by lazy {
        ArrayMap<String, ITaskStateChangeListener>()
    }

    private var completeListener: ITaskCompleteListener? = null

    private var stateChangeListener: ITaskStateChangeListener? = null

    private var currentProcessName: String? = null

    @Volatile
    private var started = false

    private var taskTotal = 0

    private var counter = 0

    fun callbackOnTaskComplete(listener: ITaskCompleteListener) {
        synchronized(listenerMap) {
            this.completeListener = listener
        }
    }

    fun callbackOnTaskState(listener: ITaskStateChangeListener) {
        synchronized(listenerMap) {
            this.stateChangeListener = listener
        }
    }

    /**
     * 判断任务是否执行完毕
     */
    fun taskIsFinished(name: String): Boolean {
        return getTaskInfoByName(name)?.isDone() == true
    }

    /**
     * 所有任务是否执行完毕
     */
    fun allTaskIsFinished() = counter == taskTotal

    /**
     * 监听某一任务进度
     */
    fun registerTaskStateCallback(name: String, callback: ITaskStateChangeListener) {
        synchronized(listenerMap) {
            getTaskInfoByName(name)?.also {
                callback.onChanged(it)
                if (it.isDone()) {
                    return
                }
            }
            listenerMap[name] = callback
        }
    }

    /**
     * 监听某一任务进度
     */
    fun unregisterTaskFinishCallback(name: String) {
        synchronized(listenerMap) {
            listenerMap.remove(name)
        }
    }

    /**
     * 开始任务
     */
    fun startInitTask(application: Application, vararg registerTasks: ITaskInitExecute<*>) {
        if (started) {
            return
        }
        started = true
        autoCollectTask()
        val collectSize = requireNotNull(collectTaskList).size
        val tempRegisterTask: ArrayMap<String, TaskInfo> = ArrayMap()
        val collectTaskMap: ArrayMap<String, AutoInitTask> = ArrayMap()
        val processName = currentProcessName(application)
        val noDependList = ArrayList<AutoInitTask>(collectSize)
        val dependList = ArrayList<AutoInitTask>(collectSize)
        requireNotNull(collectTaskList).forEach {
            val taskInfo = it.taskInfo
            if (collectTaskMap.contains(taskInfo.name)) {
                throw IllegalArgumentException("task name 不能重复:${taskInfo.name}")
            }
            if (!isMatchProcessByTask(application, processName, taskInfo.process)) {
                return@forEach
            }
            tempRegisterTask[taskInfo.taskIdentity] = taskInfo
            if (it.task != null) {
                collectTaskMap[taskInfo.name] = it
                if (taskInfo.depends.isNullOrEmpty()) {
                    noDependList.add(it)
                    dispatchTaskProgress(taskInfo, TaskState.READY)
                } else {
                    dependList.add(it)
                }
                taskList.add(taskInfo)
            }
        }

        registerTasks.forEach {
            val taskInfo = requireNotNull(tempRegisterTask[it.javaClass.name]) {
                "请使用@TaskProps注解标记Task: $it"
            }
            if (collectTaskMap.contains(taskInfo.name)) {
                throw IllegalArgumentException("task name 不能重复:${taskInfo.name}")
            }
            if (!isMatchProcessByTask(application, processName, taskInfo.process)) {
                return@forEach
            }
            val initTask = AutoInitTask(taskInfo, it)
            collectTaskMap[taskInfo.name] = initTask
            if (taskInfo.depends.isNullOrEmpty()) {
                noDependList.add(initTask)
                dispatchTaskProgress(taskInfo, TaskState.READY)
            } else {
                dependList.add(initTask)
            }
            taskList.add(taskInfo)
        }

        /**
         * 释放引用
         */
        collectTaskList?.clear()
        collectTaskList = null

        taskTotal = taskList.size

        dependList.forEach {
            val taskInfo = it.taskInfo
            taskInfo.depends?.forEach { pName ->
                checkAndBindSubTask(collectTaskMap, pName, taskInfo.name)
                dispatchTaskProgress(taskInfo, TaskState.READY)
            }
        }

        noDependList.sortedDescending().forEach {
            launch(Dispatchers.Main.immediate) {
                executeTask(collectTaskMap, application, it, processName)
            }
        }
    }

    /**
     * AutoRegister 自动注入
     */
    private fun autoCollectTask() {
        collectTaskList = mutableListOf()
        taskList = mutableListOf()
    }

    private fun register(register: ModuleTaskRegister) {
        register.register(requireNotNull(collectTaskList))
    }

    private fun currentProcessName(application: Application): String {
        return currentProcessName ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            Application.getProcessName()
        } else {
            val pid = Process.myPid()
            (application.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager)?.runningAppProcesses?.find {
                it.pid == pid
            }?.processName
        }?.also {
            currentProcessName = it
        }.orEmpty()
    }

    private fun isMatchProcessByTask(application: Application, processName: String, process: Array<String>?): Boolean {
        if (process.isNullOrEmpty()) {
            return true
        }
        if (process.size == 1) {
            val singleElement = process[0]
            if (singleElement == TaskProps.PROCESS_ALL) {
                return true
            }
            if (singleElement == TaskProps.PROCESS_MAIN && application.packageName == processName) {
                return true
            }
            if (singleElement == TaskProps.PROCESS_WITHOUT_MAIN && application.packageName != processName) {
                return true
            }
        }
        return process.contains(processName)
    }

    private fun checkAndBindSubTask(collectTaskMap: ArrayMap<String, AutoInitTask>, pName: String, cName: String) {
        val parentTask = collectTaskMap[pName]
        requireNotNull(parentTask) {
            "$cName-依赖的父Task-$pName-不存在"
        }
        if (parentTask.taskInfo.depends?.contains(cName) == true) {
            throw IllegalArgumentException("任务存在循环引用:$pName and $cName")
        }
        parentTask.taskInfo.childTask = parentTask.taskInfo.childTask?.plusElement(cName) ?: arrayOf(cName)
    }

    @MainThread
    private suspend fun executeTask(collectTaskMap: ArrayMap<String, AutoInitTask>, application: Application, autoTask: AutoInitTask, processName: String) {
        val taskInfo = autoTask.taskInfo
        check(!allTaskIsFinished()) {
            "系统异常,校验错误"
        }
        check(!taskInfo.isDone()) {
            "系统异常,任务已经执行过了"
        }

        val coroutineContext = if (taskInfo.isMainThread) {
            EmptyCoroutineContext
        } else {
            Dispatchers.Default
        }

        dispatchTaskProgress(taskInfo, TaskState.RUNNING)

        val result = withContext(coroutineContext) {
            (autoTask.task as ITaskInitExecute<*>).runCatching {
                executeInit(application, processName)
            }
        }

        synchronized(listenerMap) {
            taskInfo.result = if (result.isSuccess) {
                result.getOrNull()
            } else {
                result.exceptionOrNull()
            }
            counter++
            dispatchTaskProgress(taskInfo, TaskState.DONE)

            if (counter == taskTotal) {
                // 所有任务已完成
                completeListener?.onCompleted()
                completeListener = null
                stateChangeListener = null
                listenerMap.clear()
                return
            }
        }

        taskInfo.childTask?.mapNotNull { cName ->
            requireNotNull(collectTaskMap[cName]).takeIf {
                requireNotNull(it.taskInfo.depends).all { pName ->
                    requireNotNull(collectTaskMap[pName]).taskInfo.isDone()
                }
            }
        }?.sortedDescending()?.forEach {
            launch(Dispatchers.Main.immediate) {
                executeTask(collectTaskMap, application, it, processName)
            }
        }

    }

    private fun getTaskInfoByName(name: String): TaskInfo? {
        if (!::taskList.isInitialized) {
            return null
        }
        return taskList.find { it.name == name }
    }

    private fun dispatchTaskProgress(task: TaskInfo, state: TaskState) {
        task.state = state
        synchronized(listenerMap) {
            stateChangeListener?.onChanged(task)
            listenerMap[task.name]?.onChanged(task)
        }
    }
}
Kuki93 commented 1 year ago

interface ITaskInitExecute<Result> : ITask {

    suspend fun executeInit(application: Application, processName: String): Result
}

fun interface ITaskCompleteListener {
    fun onCompleted()
}

fun interface ITaskStateChangeListener {
    fun onChanged(task: TaskInfo)
}

class AutoInitTask constructor(val taskInfo: TaskInfo, val task: ITask?) : Comparable<AutoInitTask> {

    override fun compareTo(other: AutoInitTask): Int {
        return taskInfo.compareTo(other.taskInfo)
    }
}

internal object UnResult : Any()

enum class TaskState {
    NONE,
    READY,
    RUNNING,
    DONE
}

data class TaskInfo(
    val name: String,
    val priority: Int,
    val isMainThread: Boolean,
    val process: Array<String>?,
    val depends: Array<String>?,
    val taskIdentity: String
) : Comparable<TaskInfo> {

    var childTask: Array<String>? = null

    var result: Any? = UnResult

    var state: TaskState = TaskState.NONE

    fun isDone() = result !== UnResult

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as TaskInfo

        if (name != other.name) return false

        return true
    }

    override fun hashCode(): Int {
        return name.hashCode()
    }

    override fun compareTo(other: TaskInfo): Int {
        return priority.compareTo(other.priority)
    }

    override fun toString(): String {
        return "TaskInfo(name='$name', priority=$priority, isMainThread=$isMainThread, process=${process?.contentToString()}, depends=${depends?.contentToString()}, taskIdentity='$taskIdentity', childTask=${childTask?.contentToString()}, result=$result)"
    }

    fun toStateString(): String {
        return "TaskInfo(name='$name', state=$state, result=$result)"
    }

}