The Android Kotlin coroutine implements the Application asynchronous startup framework
1. Introduction
What basic functionality is required to implement an asynchronous startup framework
- Specifying the running thread
- Componentization is supported
- Support for multiple processes
- Support for dependencies (name dependencies for modularity)
- Support directed acyclic detection
- Support waiting for the main thread (waiting for everything to complete before continuing application startup)
2. Source address
GitHub
3. Define the Task interface
open interface InitTask {
/** * The task name must be unique */
fun name(a) : String
/** * Whether to execute in background thread (1. Specify the running thread) */
fun background(a) : Boolean = true
/** * Start the activity only when the anchor task is finished (6. Support main thread waiting) */
fun anchor(a) : Boolean = false
/** * task process (3. Support multiple processes) */
fun process(a) : Array<String> = arrayOf(PROCESS_ALL)
/** * Dependent tasks (4. Support dependencies (name dependencies, easy modularization)) */
fun depends(a) : Array<String> = arrayOf()
suspend fun execute(application: Application)
}
Copy the code
The definition of the interface corresponds to all the requirements of the appeal
4. Collect the classes that implement the initTask interface
We use ASM to collect all the classes that implement the initTask interface
ASM is implemented using AutoRegister. Thanks to the AutoRegister authors for sharing (ARouter, who also used this framework when collecting generated classes. But customized, no additional configuration required.)
1. Write a collection class first
internal class FinalTaskRegister {
val taskList : MutableList<InitTask> = mutableListOf()
init {
init()}/** * for code insertion. It is inserted into init as register(TestTask()), and eventually all classes that implement the interface to initTask are collected into taskList */
private fun init(a){}fun register(initTask : InitTask) {
taskList.add(initTask)
}
}
Copy the code
At compile time the editor iterates through all the files, detecting classes that implement the initTask interface and inserting them into the init method as register(TestTask()).
Eventually all classes that implement the initTask interface are collected into the taskList
2. Modify the AutoRegister code (you can also configure it in build.gradle according to the instructions of atuoRegister). Since we made it easy for others to use, and ARouter directly modified the code for customization without configuration.
5. Task classification
Filter inconsistent process data
fun List<InitTask>.toTaskInfo(app: Application, processName : String) : List<TaskInfo>{
val result = mutableListOf<TaskInfo>()
for(initTask in this) {if(ProcessUtils.isMatchProgress(app,processName,initTask)){ result.add(TaskInfo(initTask.name(),initTask.background(),initTask.anchor(),HashSet(listOf(*initTask.process())),HashSet (listOf(*initTask.depends())),initTask)) } }return result
}
Copy the code
Sort out dependencies and filter dependency loops
We iterate over all tasks and sort out their dependencies and whether or not the main thread is waiting.
1. If a dependency relationship exists, it can be run only after the dependency task is complete. The undependent node is the root node.
private fun startTask(a){
runBlocking {// Use runBlocking. RunBlocking blocks the current thread until the coroutine created in runBlocking finishes running
// Get the task and filter the data that does not match the process
val taskList = FinalTaskRegister().taskList.toTaskInfo(startup.app,startup.processName)
// Check if there is a duplicate name, the duplicate name throws an exception
CheckTask.checkDuplicateName(taskList)
taskMap = taskList.map { it.name to it }.toMap()
val singleSyncTasks: MutableSet<TaskInfo> = mutableSetOf()
val singleAsyncTasks: MutableSet<TaskInfo> = mutableSetOf()
val anchorTasks : MutableSet<TaskInfo> = mutableSetOf()
taskList.forEach { task ->
if(task.anchor){
// Anchor task
anchorTasks.add(task)
}
when {
task.depends.isNotEmpty() -> {
// Check whether loop dependencies are thrown
CheckTask.checkCircularDependency(listOf(task.name), task.depends, taskMap)
task.depends.forEach {
val depend = taskMap[it]
checkNotNull(depend) {
"Can't find the task [${task.name}] dependency task [$it]. ""
}
// Put this task into the children of the dependent task. When the dependent task is complete, the children will be traversed to start
depend.children.add(task)
}
}
task.background -> {
singleAsyncTasks.add(task)
}
else -> {
singleSyncTasks.add(task)
}
}
}
// Anchor task dependent tasks are converted to anchor task
for(anchorTask in anchorTasks){
anchor(anchorTask)
}
// Asynchronous tasks without dependencies
singleAsyncTasks.sortedWith(AnchorComparator()).forEach { task ->
launchTask(task)
}
// Synchronization tasks without dependencies
singleSyncTasks.sortedWith(AnchorComparator()).forEach { task ->
launchTask(task)
}
}
}
Copy the code
2. If the main thread needs to wait, its dependent task also needs the main thread to wait. Therefore, iteration from this node to the root node is reset to wait for the main thread.
/** * Iterate over anchor tasks. Anchor task dependent tasks are also marked with anchor */
private fun anchor(anchorTask : TaskInfo){
for(dependName in anchorTask.depends){
val depend = taskMap[dependName]
if(depend ! =null){
depend.anchor = true
anchor(depend)
}
}
}
Copy the code
6. Start the coroutine to run the Task
A blocking feature of runBlocking is that it blocks the current thread until all coroutines started with runBlocking CoroutineContext have finished. So here we have the main thread. This is exactly what the main thread waits for. So the outermost layer we use runBlocking.
There are four types of tasks that we implement
-
Main thread task
// Disconnect runBlocking from CoroutineContext with GlobalScope. RunBlocking does not wait for this task GlobalScope.launch(Dispatchers.Main) { } Copy the code
-
Child thread task
// Disconnect runBlocking from CoroutineContext with GlobalScope. RunBlocking does not wait for this task GlobalScope.launch(Dispatchers.Default) { } Copy the code
-
The main thread task that requires the main thread to wait
// Create launch with runBlocking CoroutineContext. RunBlocking waits for this task to complete launch(Dispatchers.Main) { } Copy the code
-
Child thread tasks that require the main thread to wait
// Create launch with runBlocking CoroutineContext. RunBlocking waits for this task to complete launch(Dispatchers.Default) { } Copy the code
Encapsulate a method based on the above to start the corresponding coroutine based on the Task setup
private suspend fun CoroutineScope.launchTask(task : TaskInfo){
val dispatcher = if (task.background) {
// Subthread Tasks use subthreads
Dispatchers.Default
} else if(! task.background && ThreadUtils.isInMainThread()) {// Main thread task and current thread Unconfined for main thread
Dispatchers.Unconfined
} else {
// The current thread is not the main thread, switch to the main thread
Dispatchers.Main
}
if(task.anchor){
// Anchor tasks follow the main thread lifecycle
launch(dispatcher) { execute(task) }
}else{
// Non-anchor task life cycle is independent
GlobalScope.launch(dispatcher) { execute(task) }
}
}
Copy the code
7. After the task is complete, start the sub-task
After the task is complete, check whether it exists. The task depends on the current task
private suspend fun CoroutineScope.afterExecute(name: String, children: Set<TaskInfo>) {
val allowTasks = mutex.withLock {// Synchronize lock to see if there are executable subtasks
completedTasks.add(name)
children.filter { completedTasks.containsAll(it.depends) }
}
if (ThreadUtils.isInMainThread()) {
// If it is the main thread, the asynchronous task is queued before the synchronous task is executed
allowTasks.filter { it.background }.sortedWith(AnchorComparator()).forEach {
launchTask(it)
}
allowTasks.filter { it.background.not() }.sortedWith(AnchorComparator()).forEach { execute(it) }
} else {
allowTasks.sortedWith(AnchorComparator()).forEach {
launchTask(it)
}
}
}
Copy the code
8. Usage
Add the dependent
module | register | api |
---|---|---|
version |
- Add dependencies to build.gradle in the project root directory:
repositories {
mavenCentral()
}
dependencies {
classpath 'io.github.cnoke.startup:register:?'
}
Copy the code
- Add it in the main project (usually app) build.gradle
plugins {
id 'com.android.application'
id 'startup-register'// Add this plug-in
}
Copy the code
- Add dependencies to the build.gradle project you are using:
dependencies {
implementation "io.github.cnoke.startup:api:?"
// Or the base module build.gradle is introduced with an API so that all modules can use it
//api "io.github.cnoke.startup:api:?"
}
Copy the code
Begin to use
- Call the following method in the onCreate method of the project application
StartUp(this).isDebug(BuildConfig.DEBUG).start()
Copy the code
- Implement com. Cnoke. Startup. Task. InitTask interface
class Task1 : InitTask{
override fun name(a) = "Task1"// The TASK name that needs to be unique must be implemented
override fun background(a) = true// Whether to execute in background thread Default is yes
override fun anchor(a) = true// Whether the activity is an anchor task, default no (start the activity only after the anchor task ends)
override fun process(a) = arrayOf(PROCESS_ALL)// Task process defaults to all processes
override fun depends(a) = arrayOf("Task2")// Dependent task Depends on the name of the task
// Must be implemented
override suspend fun execute(application: Application) {
// Third-party SDK initialization task}}class Task2 : InitTask{
override fun name(a) = "Task2"// The TASK name must be unique
override suspend fun execute(application: Application) {
// Third-party SDK initialization task}}Copy the code
InitTask can be scattered across each module of a componentized project. The startup framework uses ASM to collect all classes that implement the InitTask interface and register them in the startup queue.
By completing these simple steps, you can manage a variety of complex startup logic
9. The framework componentizes Application functions
Application Componentization for Android ASM – Juejin