Android Performance Optimization--Application of Graph Theory in Startup Optimization

I believe that partners have done startup optimization in actual projects, and most partners have nothing more than two types of startup optimization: asynchronous loading or lazy loading, for example:

MyPlayer.init(this)
BaseConfig.initConfig(this)
RetrofitManager.initHttpConfig(HttpConfig())
initBugLy(deviceId, this)
initAliLog(deviceId, this)
initSensor(this, deviceId)
//初始化全局异常
MyCrashHandler.getInstance().init(this)
MyBoxSDK.init(this, !BaseConfig.isDebug)
MyBoxSDK.login(sn)
//神策埋点
SensorHelper.init(this)
复制代码

In the Application, a lot of initialization work has been done, such as sdk initialization, business module initialization, etc. When the app starts, this part will be loaded first, and then the onCreate method of the Activity will be executed to load; if the time-consuming is serious, Then a white screen will appear.

MainScope().launch{
    MyPlayer.init(this)
    BaseConfig.initConfig(this)
    RetrofitManager.initHttpConfig(HttpConfig())
    initBugLy(deviceId, this)
    initAliLog(deviceId, this)
    initSensor(this, deviceId)
    //初始化全局异常
    MyCrashHandler.getInstance().init(this)
    MyBoxSDK.init(this, !BaseConfig.isDebug)
    MyBoxSDK.login(sn)
    //神策埋点
    SensorHelper.init(this)
}
复制代码

So is there a problem with asynchronous loading or lazy loading? It can only be said that it is not necessarily true. If it is asynchronous loading, the possible scenario is that the method of calling a certain sdk is called back immediately after the Activity is started. Because it is asynchronous loading, the sdk may not be initialized yet, then an error will be reported;

If you use lazy loading and put the task in IdleHandler for processing, there must still be the same problem, then these old-fashioned processing methods have their own shortcomings, so what is the most effective method?

1 Basic knowledge of graph theory

We have been facing a problem that the time-consuming sdk must be loaded asynchronously, but what if it has a dependency on a certain sdk?

If sdk4 is the most time-consuming, we put sdk4 in an asynchronous thread, but sdk5 depends on sdk4 and sdk3, so there will be a problem: when sdk5 is loaded, sdk4 has not been initialized yet, and an error will be reported. The expectation is to wait for sdk4 After the initialization is complete, execute the initialization of sdk5, so is there any way to centrally manage these dependencies?

1.1 Directed Acyclic Graph

DAG, a directed acyclic graph, can manage dependencies between tasks and schedule these tasks. It seems to be able to meet the demands of this section, so let's first understand this data structure.

Vertex : In DAG, each node (sdk1/sdk2/sdk3...) is a vertex;

Edge : the connection line connecting each node;

In-degree : The number of nodes that each node depends on. Visually speaking, there are several lines connected to the node. For example, the in-degree of sdk2 is 1, and the in-degree of sdk5 is 2.

We can see from the figure that there is a direction, but there is no path back to the starting point again, so it is called a directed acyclic graph

1.2 Topological sort

Topological sorting is used to sort the dependencies of nodes. There are two main types: DFS (depth-first traversal) and BFS (breadth-first traversal). If you know binary trees, you should be familiar with these two algorithms.

Let's take this picture to demonstrate the flow of the topological sorting algorithm:

(1) First find the vertex with an in-degree of 0 in the graph, then the vertex with an in-degree of 0 in this graph is sdk1, and then delete

(2) After deletion, find the vertex with an in-degree of 0 again. At this time, there are two vertices with an in-degree of 0, sdk2 and sdk3, so the result of topological sorting is not unique!

(3) Recurse in turn until all vertices with an in-degree of 0 are deleted to complete the topological sort

1.3 Topological sort implementation

interfaceAndroidStartUp<T> {

    //创建任务funcreateTask(context: Context):T

    //依赖的任务fundependencies():List<Class<out AndroidStartUp<*>>>?

    //入度数fungetDependencyCount():Int
}
复制代码

First of all, for the properties of nodes, each node is a task, which has its own dependent task items, and can set the in-degree.

abstractclassAbsAndroidStartUp<T> : AndroidStartUp<T> {

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        returnnull
    }

    overridefungetDependencyCount(): Int {
        returnif (dependencies() != null) dependencies()!!.size else0
    }
}
复制代码

If the task does not have any dependencies, it means that the current task is a vertex with an in-degree of 0.

Next, we write 5 tasks, and first assign the logical relationship in the figure below

classTask1 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {

        Log.e("TAG", "createTask Task1")
        return""
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        returnnull
    }
}
复制代码
classTask2 : AbsAndroidStartUp<Int>(){
    overridefuncreateTask(context: Context): Int {
        Log.e("TAG","createTask Task2")
        return1
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        return mutableListOf(Task1::class.java)
    }
}
复制代码
classTask3 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {
        Log.e("TAG","createTask Task3")
        return""
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        return mutableListOf(Task1::class.java)
    }
}
复制代码
classTask4 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {
        Log.e("TAG","createTask Task4")
        return""
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        return mutableListOf(Task2::class.java)
    }
}
复制代码
classTask5 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {
        Log.e("TAG", "createTask Task5")
        return""
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        return mutableListOf(Task3::class.java, Task4::class.java)
    }
}
复制代码

After we got 5 tasks, for the outside world, there is no need to care about this dependency. All the tasks are randomly combined, but the final results are actually sorted according to the dependencies.

classMyTopoSort {

    privateval inDegree: MutableMap<Class<out AndroidStartUp<*>>, Int> by lazy {
        mutableMapOf()
    }
    
    privateval nodeDependency: MutableMap<Class<out AndroidStartUp<*>>, MutableList<Class<out AndroidStartUp<*>>>> by lazy {
        mutableMapOf()
    }

    //存储顶点privateval queue: ArrayDeque<Class<out AndroidStartUp<*>>> by lazy {
        ArrayDeque()
    }


    funsort(map: List<AndroidStartUp<*>>) {
        //遍历全部的节点
        map.forEach { node ->
            //记录入度数
            inDegree[node.javaClass] = node.getDependencyCount()

            if (node.getDependencyCount() == 0) {
                //查找到顶点
                queue.addLast(node.javaClass)
            } else {
                //如果不是顶点需要查找依赖关系,找到每个节点对应的边//例如node == task2 依赖 task1// task1 -- task2就是一条边,因此需要拿task1作为key,存储这条边// task1 -- task3也是一条边,在遍历到task3的时候,也需要存进来
                node.dependencies()?.forEach { parent ->

                    var list = nodeDependency[parent]
                    if (list == null) {
                        list = mutableListOf()
                        nodeDependency[parent] = list
                    }
                    list.add(node.javaClass)
                }
            }
        }

        val result = mutableListOf<Class<out AndroidStartUp<*>>>()
        //依次删除顶点while (queue.isNotEmpty()) {
            //取出顶点val node = queue.removeFirst()
            Log.e("TAG","取出顶点--$node")
            result.add(node)
            //查找依赖关系,凡是依赖该顶点的,入度数都 -1if (nodeDependency.containsKey(node)) {
                val nodeList = nodeDependency[node]
                nodeList!!.forEach { node ->
                    val degree = inDegree[node]
                    inDegree[node] = degree!! - 1if (degree - 1 == 0) {
                        queue.add(node)
                    }
                }
            }
        }
    }
}
复制代码

The above is an algorithm based on breadth-first topological sorting, and the idea is very simple, that is, the first round of traversal, first get all the vertices, and the dependencies between each node and other nodes: (parent node -- child node), also is each edge;

Then it will recursively store the vertex collection, take out each vertex from the stack, and subtract 1 from the in-degrees of the edge nodes corresponding to each vertex, and put the nodes that become vertices into the vertex collection to continue traversing.

2022-10-0616:04:31.88625994-25994/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task1
2022-10-0616:04:31.88625994-25994/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task2
2022-10-0616:04:31.88625994-25994/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task3
2022-10-0616:04:31.88625994-25994/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task4
2022-10-0616:04:31.88625994-25994/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task5
复制代码

Finally, the result of topological sorting is obtained.

2 Task Management

When we have completed topological sorting, only a small part of it has been completed. The key lies in how we make these tasks run, and there is also the problem of synchronization. When the tasks in the sub-threads depend on the main thread tasks, how to wait until the main thread tasks are executed Complete, and then execute sub-thread tasks, these are the core functions of this framework.

2.1 Task start

/**
 * 作者:lay
 * 用于存储每个任务执行返回的结果
 */classStartupResultManager {

    privateval result: ConcurrentHashMap<Class<out AndroidStartUp<*>>, Result<*>> by lazy {
        ConcurrentHashMap()
    }

    funsaveResult(key: Class<outAndroidStartUp<*>>, value: Result<*>) {
        result[key] = value
    }

    fungetResult(key: Class<outAndroidStartUp<*>>): Result<*>? {
        return result[key]
    }

    companionobject {
        val instance: StartupResultManager by lazy {
            StartupResultManager()
        }
    }
}
复制代码

First, create a task result management class. This class is mainly used to store the results of each task execution. The main function of this storage is that if a task depends on the return result of the previous task, it is used here.

classStartupManager {

    privatevar list: List<AndroidStartUp<*>>? = nullconstructor(list: List<AndroidStartUp<*>>) {
        this.list = list
    }

    funstart(context: Context) {
        //判断是否在主线程中执行if (Looper.myLooper() != Looper.getMainLooper()) {
            throw IllegalArgumentException("请在主线程中使用该框架")
        }
        //排序val sortStore = MyTopoSort().sort(list)
        sortStore.getResult().forEach { task ->
            //执行创建任务val result = task.createTask(context)
            StartupResultManager.instance.saveResult(task.javaClass, Result(result))
        }
    }


    classBuilder {

        privateval list: MutableList<AndroidStartUp<*>> by lazy {
            mutableListOf()
        }

        funsetTask(task: AndroidStartUp<*>): Builder {
            list.add(task)
            returnthis
        }

        funsetAllTask(tasks: MutableList<AndroidStartUp<*>>): Builder {
            list.addAll(tasks)
            returnthis
        }

        funbuilder(): StartupManager {
            return StartupManager(list)
        }

    }
}
复制代码

Here is to centralize and manage all tasks, using the builder design pattern, the core method is the start method, which will uniformly execute the results after topology sorting

2022-10-0620:23:02.87627742-27742/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task1
2022-10-0620:23:02.87627742-27742/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task2
2022-10-0620:23:02.87627742-27742/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task3
2022-10-0620:23:02.87627742-27742/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task4
2022-10-0620:23:02.87627742-27742/com.lay.mvi E/TAG: 取出顶点--classcom.lay.toposort.api.Task5
2022-10-0620:23:02.87627742-27742/com.lay.mvi E/TAG: createTask Task1
2022-10-0620:23:02.87827742-27742/com.lay.mvi E/TAG: createTask Task2
2022-10-0620:23:02.87827742-27742/com.lay.mvi E/TAG: createTask Task3
2022-10-0620:23:02.87827742-27742/com.lay.mvi E/TAG: createTask Task4
2022-10-0620:23:02.87827742-27742/com.lay.mvi E/TAG: createTask Task5
复制代码

We can see that each task executes

2.2 Thread management

回到开始的一个问题,sdk4是耗时任务,可以放在子线程中执行,但是又依赖sdk2的一个返回值,这种情况下,我们其实不能保证每个任务都是在主线程中执行的,需要等待某个线程执行完成之后,再执行下个线程,我们先看一个简单的问题:假如有两个线程AB,A线程需要三步完成,当执行到第二步的时候,开始执行B线程,这种情况下该怎么处理?

2.2.1 wait/notify

wait/notify能够实现吗?

val lock = Object()
val t1 = Thread{
    synchronized(lock){
        Log.e("TAG","开始执行线程1第一步")
        Log.e("TAG","开始执行线程1第二步")
        lock.notify()
        Thread.sleep(2000)
        Log.e("TAG","开始执行线程1第三步")
    }
}

val t2 = Thread{
    synchronized(lock){
        lock.wait()
        Thread.sleep(1000)
        Log.e("TAG","开始执行线程2")
    }
}
t2.start()
t1.start()
t2.join()
t1.join()
复制代码

线程1和线程2共用一把锁,当线程2启动之后,就会释放这把锁,然后线程1开始执行,当执行到第二步的时候,唤醒线程2执行,但是结果并不是我们想的那样。

2022-10-0621:03:56.56028822-28866/com.lay.mvi E/TAG: 开始执行线程1第一步
2022-10-0621:03:56.56028822-28866/com.lay.mvi E/TAG: 开始执行线程1第二步
2022-10-0621:03:58.56128822-28866/com.lay.mvi E/TAG: 开始执行线程1第三步
2022-10-0621:03:59.56428822-28865/com.lay.mvi E/TAG: 开始执行线程2复制代码

这个是为什么呢?是因为两个线程共用一把锁,当线程1执行到第二步时,唤醒线程2,但是线程1还是持有这把锁没有释放,导致线程2无法进入同步代码块,只有等到线程1执行完成之后,才执行了线程2.

2.2.2 CountDownLatch

如果看过系统源码的伙伴,对于闭锁应该是很熟悉了,它的原理就是会等待所有的线程都执行完成之后,再执行下一个任务。

val countDownLatch = CountDownLatch(2)

Thread{
    Log.e("TAG","开始执行线程1第一步")
    countDownLatch.await()
    Log.e("TAG","开始执行线程1第二步")
}.start()

Log.e("TAG","开始执行线程2")
Thread{
    SystemClock.sleep(1000)
    Log.e("TAG","线程2执行完成")
    countDownLatch.countDown()
}.start()

Log.e("TAG","开始执行线程3")
Thread{
    SystemClock.sleep(1000)
    Log.e("TAG","线程3执行完成")
    countDownLatch.countDown()
}.start()
复制代码

声明了CountDownLatch并定义状态值为2,每次执行一次countDown方法,状态值会-1,当等于0时,会回到调用await的地方,继续执行。

2022-10-0621:27:12.23629154-29154/com.lay.mvi E/TAG: 开始执行线程22022-10-0621:27:12.23629154-29154/com.lay.mvi E/TAG: 开始执行线程32022-10-0621:27:12.23829154-29189/com.lay.mvi E/TAG: 开始执行线程1第一步
2022-10-0621:27:13.24029154-29190/com.lay.mvi E/TAG: 线程2执行完成
2022-10-0621:27:13.24229154-29191/com.lay.mvi E/TAG: 线程3执行完成
2022-10-0621:27:13.24229154-29189/com.lay.mvi E/TAG: 开始执行线程1第二步
复制代码

2.2.3 任务分发

既然不能保证每个任务都在主线程中执行,那么就需要对任务做配置

interfaceIDispatcher {

    funcallOnMainThread():Boolean//是否在主线程中执行funwaitOnMainThread():Boolean//是否需要等待该任务完成funtoWait()//等待父任务执行完成funtoCountDown()//父任务执行完成funexecutor():Executor //线程池funthreadPriority():Int//线程优先级
}
复制代码

需要知道当前任务是否需要在子线程中执行,如果需要在子线程中执行,是否需要等待其他任务执行完成。

abstractclassAbsAndroidStartUp<T> : AndroidStartUp<T> {

    //依赖的任务数的个数作为状态值privateval countDownLatch = CountDownLatch(getDependencyCount())

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        returnnull
    }

    overridefungetDependencyCount(): Int {
        returnif (dependencies() != null) dependencies()!!.size else0
    }

    overridefunexecutor(): Executor {
        return Executors.newFixedThreadPool(5)
    }

    overridefuntoWait() {
        countDownLatch.await()
    }

    overridefuntoCountDown() {
        countDownLatch.countDown()
    }

    overridefunthreadPriority(): Int {
        return Process.THREAD_PRIORITY_DEFAULT
    }
}
复制代码

因此AbsAndroidStartUp也需要做一次改造,需要实现这个接口,在这个抽象类中,需要创建一个CountDownLatch,当前任务的依赖任务数作为状态值,当这个任务执行之前先await,等待依赖的任务执行完成之后,再执行自己的任务。

classTask1 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {
        Log.e("TAG", "createTask Task1")
        return"Task1执行的结果"
    }

    overridefuncallOnMainThread(): Boolean {
        returnfalse
    }

    overridefunwaitOnMainThread(): Boolean {
        returnfalse
    }
    
    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        returnnull
    }
}
复制代码

那么假设所有的任务都在子线程中执行,如果没有闭锁,那么就不存在任务执行的先后顺序,再次回到前面的问题,因为Task2是在子线程中执行的,而Task4则是依赖Task2的返回值,如果没有同步队列的作用,那么就会导致Task4无法获取Task3的返回值,直接崩溃!

classTask2 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {
        Log.e("TAG", "createTask Task3")
        val executor = Executors.newSingleThreadExecutor()
        val future = executor.submit(myCallable())
        try {
            return future.get()
        } catch (e: Exception) {
            return""
        }
    }

    overridefuncallOnMainThread(): Boolean {
        returnfalse
    }

    overridefunwaitOnMainThread(): Boolean {
        returnfalse
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        return mutableListOf(Task1::class.java)
    }

    classmyCallable : Callable<String> {
        overridefuncall(): String {
            Thread.sleep(1500)
            return"任务3线程执行返回结果"
        }
    }
}
复制代码
classTask4 : AbsAndroidStartUp<String>() {
    overridefuncreateTask(context: Context): String {
        Log.e("TAG", "createTask Task4")
        //注意,这里取值是取不到的!val result = StartupResultManager.instance.getResult(Task2::class.java)
        valdata = result!!.data//进行task4 初始化任务
        Log.e("TAG", "$data 开始执行任务4")
        return""
    }

    overridefuncallOnMainThread(): Boolean {
        returnfalse
    }

    overridefunwaitOnMainThread(): Boolean {
        returnfalse
    }

    overridefundependencies(): List<Class<out AndroidStartUp<*>>>? {
        return mutableListOf(Task2::class.java)
    }
}
复制代码

但是当我们使用闭锁之后,每个任务在执行之前,首先会调用toWait方法,等待依赖的全部项执行完成之后,再执行自己的task代码块;

funstart(context: Context) {
    //判断是否在主线程中执行if (Looper.myLooper() != Looper.getMainLooper()) {
        throw IllegalArgumentException("请在主线程中使用该框架")
    }
    //排序
    sortStore = MyTopoSort().sort(list)
    sortStore?.getResult()?.forEach { task ->

        //判断当前任务执行的线程if (task.callOnMainThread()) {
            //如果在主线程,那么就直接执行

        } else {
            //如果在子线程
            StartupRunnable(context, task, this).run()
        }
    }
}
复制代码
classStartupRunnable : Runnable {

    privatevar task: AndroidStartUp<*>? = nullprivatevar context: Context? = nullprivatevar manager:StartupManager? = nullconstructor(context: Context, task: AndroidStartUp<*>,manager: StartupManager) {
        this.task = task
        this.context = context
        this.manager = manager
    }

    overridefunrun() {
        Process.setThreadPriority(task?.threadPriority() ?: Process.THREAD_PRIORITY_DEFAULT)
        //当前任务暂停执行
        task?.toWait() //注意,如果是顶点,状态值为0,那么就不会阻塞//等到依赖的任务全部执行完成,再执行val result = task?.createTask(context!!)
        StartupResultManager.instance.saveResult(task?.javaClass!!, Result(result))
        //当前任务执行完成,通知子任务
        manager?.notifyChildren(task!!)
    }
}
复制代码

而且每个任务执行完成之后,都需要去通知子任务,如下图

funnotify(task: AndroidStartUp<*>) {
    dependency?.get(task.javaClass)?.forEach {
        //通知全部依赖项
        startupStore?.get(it)?.toCountDown()
    }
}
复制代码

2.3 我们的目标

我们的目标仅仅是为了排序吗?不是;既然每个任务之间相互依赖,我们全部放在子线程中,这样主线程几乎不耗时,直接进入主界面,可以吗?显然不可以,这样初始化sdk的意义不大了,所以我们的目标就是,如果需要5个sdk全部初始化完成才能进入主界面,如何处理?还是CountDownLatch

//状态数由节点的个数决定privateval mainCountDownLatch by lazy {
    CountDownLatch(list!!.size)
}
复制代码
funnotifyChildren(task: AndroidStartUp<*>) {
    sortStore?.notify(task)
    //每次唤醒一个任务,就需要countdown
    mainCountDownLatch.countDown()
}

funawait() {
    mainCountDownLatch.await()
}
复制代码
StartupManager.Builder()
    .setTask(Task1())
    .setAllTask(mutableListOf(Task5(), Task4(), Task3(), Task2()))
    .builder().start(this).await()
复制代码

所以对于app一开始就用不到的sdk,完全可以放在子线程中执行,而且因为存在依赖管理而不需要关系依赖关系,而对于进入app就会使用到的sdk,可以通过闭锁进行任务管理。

2.4 同步任务阻塞异步任务处理

前面我们在使用这个框架的时候,要么是全部在主线程,要么是全部在子线程,前面我们就提到说任务不一定能全部在主线程或者子线程,看下面的场景:

如果我们拿到的拓扑排序的顺序是:1-2-3-4-5,其中任务2在主线程执行,任务1、3、4、5在子线程,当任务分发的时候,任务1在子线程,任务2在主线程,这个时候,需要等到主线程执行完任务2才能分发任务3执行,所以这个问题该怎么处理。

我们可以这么想,如果分发的时候,先把子线程的任务分发下去,再执行主线程的任务,顺序就变成了1-3-4-5-2,咋一看这个顺序是有问题,任务2依赖任务1,怎么跑到最后边去了?

如果是同步执行,这个顺序当然有问题,但是我们前面已经做了很全面的异步任务处理!

分发任务1 --- 子线程 不阻塞 执行

分发任务3 --- 子线程 阻塞 等待任务1

分发任务4 --- 子线程 阻塞 等待任务2

分发任务5 --- 子线程 阻塞 等待任务3 4

分发任务2 --- 主线程

所以即便是任务2最后分发,依然不会影响每个任务的依赖关系,而且不会阻塞主线程!

优化前:

2022-10-0710:17:22.8073867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task1@fc61d74 callOnIOThread
2022-10-0710:17:22.8083867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task3@53df312 callOnIOThread
2022-10-0710:17:22.8083867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task2@d5c2ae3 callOnMainThread
2022-10-0710:17:24.3163867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task4@9842e5e callOnIOThread
2022-10-0710:17:24.3193867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task5@273a70c callOnIOThread
复制代码

优化后:

2022-10-0710:12:32.9543662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task1@fc61d74 callOnIOThread
2022-10-0710:12:32.9543662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task3@53df312 callOnIOThread
2022-10-0710:12:32.9573662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task4@3e19ce0 callOnIOThread
2022-10-0710:12:32.9573662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task5@9842e5e callOnIOThread
2022-10-0710:12:32.9573662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task2@e1c923f callOnMainThread
复制代码

我们能很清楚的看到,主线程任务不会阻塞异步任务!

fungetResult(): List<AndroidStartUp<*>> {

    if (result == null) return emptyList()

    val list = mutableListOf<AndroidStartUp<*>>()
    result?.forEach { key ->

        if (startupStore?.get(key)?.callOnMainThread()!!) {
            mainThread.add(startupStore?.get(key)!!)
        } else {
            ioThread.add(startupStore?.get(key)!!)
        }
    }
    list.addAll(ioThread)
    list.addAll(mainThread)
    return list
}
复制代码

主要优化点就在于,当获取拓扑排序的结果之后,根据任务是否在主线程中分类,将在主线程中的任务放在后面分发。

3 框架管理 -- ContentProvider

通过前面对于框架的梳理,我们完成了对于任务的集中管理,要知道项目中,可能存在多个初始化的sdk,如果每次新增一个依赖任务,就需要手动添加一个task,显然并没有那么灵活

StartupManager.Builder()
    .setTask(Task1())
    .setAllTask(mutableListOf(Task5(), Task4(), Task3(), Task2()))
    .builder().start(this).await()
复制代码

那么有什么方式能够在app启动之前,或者在Application的onCreate之前能够完成注册能力呢?如果有熟悉ContentProvider的伙伴应该知道,ContentProvider的onCreate方法是在Application的onAttach和pnCreate中间执行的,也就是说在ContentProvider的onCreate方法中完成注册任务就可以。

3.1 获取ContentProvider元数据

<providerandroid:name="com.lay.toposort.provider.StartupContentProvider"android:authorities="com.lay.provider"><meta-dataandroid:name="com.lay.mvi.topo.Task5"android:value="app_startup" /></provider>复制代码

因为所有任务终点为Task5,因此我们理想的目标就是只需要在清单文件中配置一个Task,然后所有的Task都能被注册进来,所以我们需要一个方式能够获取这个元数据。

object ProviderInitialize {

    privateconstval META_KEY = "app_startup"funinitialMetaData(context: Context): List<AndroidStartUp<*>> {

        val provider = ComponentName(context, "com.lay.toposort.StartupContentProvider")
        val providerInfo =
            context.packageManager.getProviderInfo(provider, PackageManager.GET_META_DATA)

        //存储节点val nodeMap: MutableMap<Class<*>, AndroidStartUp<*>> = mutableMapOf()
        providerInfo.metaData.keySet().forEach { key ->
            Log.e("TAG", "key ===> $key")
            val dataKey = providerInfo.metaData.get(key)
            Log.e("TAG", "dataKey ===> $dataKey")
            if (dataKey == META_KEY) {
                //处理task
                doInitializeTask(context, Class.forName(key), nodeMap)
            }
        }

        return ArrayList(nodeMap.values)
    }

    privatefundoInitializeTask(
        context: Context,
        clazz: Class<*>,
        nodeMap: MutableMap<Class<*>, AndroidStartUp<*>>
    ) {
        val startUp = clazz.newInstance() as AndroidStartUp<*>
        Log.e("TAG", "clazz ===> $clazz")
        if (!nodeMap.containsKey(clazz)) {
            nodeMap[clazz] = startUp
        }
        //查找依赖项if (startUp.dependencies() != null) {
            //获取全部的依赖项val dependencyList = startUp.dependencies()
            dependencyList?.forEach {
                doInitializeTask(context, it, nodeMap)
            }
        }
    }

}
复制代码

通过PackageManager就可以获取到Contentprovider中的元数据,通过ProviderInfo的metaData参数可以获取所有的key和value

2022-10-07 09:05:10.74632620-32620/com.lay.mvi E/TAG: key ===> com.lay.mvi.topo.Task5
2022-10-07 09:05:10.74632620-32620/com.lay.mvi E/TAG: dataKey ===> app_startup
复制代码

通过递归的方式,倒推获取全部的依赖节点。

3.2 注册Task

classStartupContentProvider : ContentProvider() {
    overridefunonCreate(): Boolean {
        context?.let {
            val list = ProviderInitialize.initialMetaData(it)
            StartupManager.Builder()
                .setAllTask(list)
                .builder().start(it).await()
        }
        returnfalse
    }
复制代码

通过PackageManager获取元数据之后,拿到了所有的顶点的集合,然后在StartupContentProvider的onCreate方法中注册全部Task。

2022-10-07 09:43:08.1141721-1721/com.lay.mvi E/TAG: StartupContentProvider onCreate
2022-10-07 09:43:08.1181721-1721/com.lay.mvi E/TAG: key ===> com.lay.mvi.topo.Task5
2022-10-07 09:43:08.1181721-1721/com.lay.mvi E/TAG: dataKey ===> app_startup
2022-10-07 09:43:08.1241721-1721/com.lay.mvi E/TAG: clazz ===> classcom.lay.mvi.topo.Task5
2022-10-07 09:43:08.1241721-1721/com.lay.mvi E/TAG: clazz ===> classcom.lay.mvi.topo.Task3
2022-10-07 09:43:08.1251721-1721/com.lay.mvi E/TAG: clazz ===> classcom.lay.mvi.topo.Task1
2022-10-07 09:43:08.1261721-1721/com.lay.mvi E/TAG: clazz ===> classcom.lay.mvi.topo.Task4
2022-10-07 09:43:08.1261721-1721/com.lay.mvi E/TAG: clazz ===> classcom.lay.mvi.topo.Task2
2022-10-07 09:43:08.1261721-1721/com.lay.mvi E/TAG: clazz ===> classcom.lay.mvi.topo.Task1
2022-10-07 09:43:08.2551721-1771/com.lay.mvi E/TAG: classcom.lay.mvi.topo.Task1
2022-10-07 09:43:08.2551721-1771/com.lay.mvi E/TAG: createTask Task1
2022-10-07 09:43:08.2571721-1773/com.lay.mvi E/TAG: classcom.lay.mvi.topo.Task2
2022-10-07 09:43:08.2611721-1772/com.lay.mvi E/TAG: classcom.lay.mvi.topo.Task3
2022-10-07 09:43:08.2611721-1774/com.lay.mvi E/TAG: classcom.lay.mvi.topo.Task4
2022-10-07 09:43:08.2611721-1771/com.lay.mvi E/TAG: node classcom.lay.mvi.topo.Task1 -- classcom.lay.mvi.topo.Task3
2022-10-07 09:43:08.2611721-1771/com.lay.mvi E/TAG: node classcom.lay.mvi.topo.Task1 -- classcom.lay.mvi.topo.Task2
2022-10-07 09:43:08.2611721-1772/com.lay.mvi E/TAG: createTask Task2
2022-10-07 09:43:08.2611721-1773/com.lay.mvi E/TAG: createTask Task3
2022-10-07 09:43:08.2611721-1772/com.lay.mvi E/TAG: node classcom.lay.mvi.topo.Task3 -- classcom.lay.mvi.topo.Task5
2022-10-07 09:43:08.2631721-1775/com.lay.mvi E/TAG: classcom.lay.mvi.topo.Task5
2022-10-07 09:43:09.7641721-1773/com.lay.mvi E/TAG: node classcom.lay.mvi.topo.Task2 -- classcom.lay.mvi.topo.Task4
2022-10-07 09:43:09.7641721-1774/com.lay.mvi E/TAG: createTask Task4
2022-10-07 09:43:09.7641721-1774/com.lay.mvi E/TAG: 任务3线程执行返回结果 开始执行任务42022-10-07 09:43:09.7641721-1774/com.lay.mvi E/TAG: node classcom.lay.mvi.topo.Task4 -- classcom.lay.mvi.topo.Task5
2022-10-07 09:43:09.7641721-1775/com.lay.mvi E/TAG: createTask Task5
2022-10-07 09:43:09.7681721-1721/com.lay.mvi E/TAG: Application onCreate
复制代码

通过日志可以发现,当任务注册完成之后,才执行了Application的onCreate方法。

Android知识点笔记:

Android开发核心知识点笔记

Android Framework核心知识点笔记

Android Flutter核心知识点笔记与实战详解

音视频开发笔记,入门到高级进阶

性能优化核心知识点笔记

Android开发高频面试题,25个知识点整合

Android开发核心架构知识点笔记

Guess you like

Origin blog.csdn.net/m0_70748845/article/details/129479950