当前位置 : 主页 > 手机开发 > android >

Android性能图论在启动优化中的应用示例详解

来源:互联网 收集:自由互联 发布时间:2023-02-01
目录 正文 1 图论的基础知识 1.1 有向无环图 1.2 拓扑排序 1.3 拓扑排序实现 2 任务管理 2.1 任务启动 2.2 线程管理 2.2.1 wait/notify 2.2.2 CountDownLatch 2.2.3 任务分发 2.3 我们的目标 2.4 同步任务阻
目录
  • 正文
  • 1 图论的基础知识
    • 1.1 有向无环图
    • 1.2 拓扑排序
    • 1.3 拓扑排序实现
  • 2 任务管理
    • 2.1 任务启动
    • 2.2 线程管理
      • 2.2.1 wait/notify
      • 2.2.2 CountDownLatch
      • 2.2.3 任务分发
    • 2.3 我们的目标
      • 2.4 同步任务阻塞异步任务处理
      • 3 框架管理 -- ContentProvider
        • 3.1 获取ContentProvider元数据
          • 3.2 注册Task
          • 4 总结

            正文

            相信伙伴们在实际项目中都做过启动优化,而且大部分伙伴们对于启动优化的处理无非两种:异步加载 or 延迟加载,例如:

            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)
            

            在Application中,做了很多初始化工作,像sdk的初始化、业务模块的初始化等等,当app启动的时候,这部分会被首先加载,然后才会执行Activity的onCreate方法加载;如果耗时严重,那么就会出现启动白屏的情况。

            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)
            }
            

            那么异步加载或者延迟加载会有问题吗?只能说不一定,如果是异步加载,可能存在的场景就是Activity启动后立刻回调用某个sdk的方法,因为是异步加载可能sdk还没初始化完成,那么就会报错;

            如果使用延迟加载,将任务放在IdleHandler中处理,想必还是存在同样的问题,那么这些老生常谈的处理方式都有自己的不足之处,那么什么方式是最有效的呢?

            1 图论的基础知识

            我们一直面临一个问题就是,对于耗时严重的sdk,一定要异步加载,但是如果它跟某个sdk有依赖关系呢?

            假如sdk4是耗时最严重,我们把sdk4放在异步线程中,但是sdk5依赖sdk4和sdk3,所以会存在一个问题就是:当加载sdk5的时候,sdk4还没有初始化完成,会报错,期望就是等待sdk4初始化完成后,再执行sdk5的初始化,那么有什么方式能够集中管理这些依赖关系呢?

            1.1 有向无环图

            DAG,有向无环图,能够管理任务之间的依赖关系,并调度这些任务,似乎能够满足本节开始的诉求,那么我们先了解下这种数据结构。

            顶点:在DAG中,每个节点(sdk1/sdk2/sdk3......)都是一个顶点;

            :连接每个节点的连接线;

            入度:每个节点依赖的节点数,形象来说就是有几根线连接到该节点,例如sdk2的入度是1,sdk5的入度是2。

            我们从图中可以看出,是有方向的,但是没有路径再次回到起点,因此就叫做有向无环图

            1.2 拓扑排序

            拓扑排序用于对节点的依赖关系进行排序,主要分为两种:DFS(深度优先遍历)、BFS(广度优先遍历),如果了解二叉树的话,对于这两种算法应该比较熟悉。

            我们就拿这张图来演示,拓扑排序算法的流程:

            (1)首先找到图中,入度为0的顶点,那么这张图中入度为0的顶点就是sdk1,然后删除

            (2)删除之后,再次找到入度为0的顶点,这个时候有两个入度为0的顶点,sdk2和sdk3,所以拓扑排序的结果不是唯一的!

            (3)依次递归,直到删除全部入度为0的顶点,完成拓扑排序

            1.3 拓扑排序实现

            interface AndroidStartUp<T> {
                //创建任务
                fun createTask(context: Context):T
                //依赖的任务
                fun dependencies():List<Class<out AndroidStartUp<*>>>?
                //入度数
                fun getDependencyCount():Int
            }
            

            首先,针对节点的属性,每个节点都是一个任务,它有自己依赖的任务项,并可以设置入度数。

            abstract class AbsAndroidStartUp<T> : AndroidStartUp<T> {
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return null
                }
                override fun getDependencyCount(): Int {
                    return if (dependencies() != null) dependencies()!!.size else 0
                }
            }
            

            如果这个任务没有任何依赖项,那么就说明当前任务是一个顶点,入度数为0.

            接下来,我们写5个任务,先把下图的逻辑关系分配好

            class Task1 : AbsAndroidStartUp<String>() {
                override fun createTask(context: Context): String {
                    Log.e("TAG", "createTask Task1")
                    return ""
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return null
                }
            }
            
            class Task2 : AbsAndroidStartUp<Int>(){
                override fun createTask(context: Context): Int {
                    Log.e("TAG","createTask Task2")
                    return 1
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return mutableListOf(Task1::class.java)
                }
            }
            
            class Task3 : AbsAndroidStartUp<String>() {
                override fun createTask(context: Context): String {
                    Log.e("TAG","createTask Task3")
                    return ""
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return mutableListOf(Task1::class.java)
                }
            }
            
            class Task4 : AbsAndroidStartUp<String>() {
                override fun createTask(context: Context): String {
                    Log.e("TAG","createTask Task4")
                    return ""
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return mutableListOf(Task2::class.java)
                }
            }
            
            class Task5 : AbsAndroidStartUp<String>() {
                override fun createTask(context: Context): String {
                    Log.e("TAG", "createTask Task5")
                    return ""
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return mutableListOf(Task3::class.java, Task4::class.java)
                }
            }
            

            我们拿到了5个任务之后,对于外界来说,其实不需要关心这个依赖关系,所有的任务随机组合,但是最终拿到的结果其实就是按照依赖关系排序出来的。

            class MyTopoSort {
                private val inDegree: MutableMap<Class<out AndroidStartUp<*>>, Int> by lazy {
                    mutableMapOf()
                }
                private val nodeDependency: MutableMap<Class<out AndroidStartUp<*>>, MutableList<Class<out AndroidStartUp<*>>>> by lazy {
                    mutableMapOf()
                }
                //存储顶点
                private val queue: ArrayDeque<Class<out AndroidStartUp<*>>> by lazy {
                    ArrayDeque()
                }
                fun sort(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)
                        //查找依赖关系,凡是依赖该顶点的,入度数都 -1
                        if (nodeDependency.containsKey(node)) {
                            val nodeList = nodeDependency[node]
                            nodeList!!.forEach { node ->
                                val degree = inDegree[node]
                                inDegree[node] = degree!! - 1
                                if (degree - 1 == 0) {
                                    queue.add(node)
                                }
                            }
                        }
                    }
                }
            }
            

            上面是根据拓扑排序广度优先写的算法,思想也很简单,就是第一轮遍历,首先拿到全部的顶点,以及每个节点与其他节点的依赖关系:(父节点 -- 子节点),也就是每条边;

            然后会递归顶点存储集合,从栈中取出每个顶点,每个顶点对应的边节点入度数全部减1,对于成为顶点的节点放入顶点集合继续遍历。

            2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task1
            2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task2
            2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task3
            2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task4
            2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task5

            最终得到拓扑排序的结果。

            2 任务管理

            当我们完成了拓扑排序,只是完成了一小部分,关键在于,我们如何让这些任务运行起来,还有就是同步问题,当在子线程中的任务依赖主线程任务时,如何等到主线程任务执行完成,再执行子线程任务,这些都是这个框架的核心功能。

            2.1 任务启动

            /**
             * 作者:lay
             * 用于存储每个任务执行返回的结果
             */
            class StartupResultManager {
                private val result: ConcurrentHashMap<Class<out AndroidStartUp<*>>, Result<*>> by lazy {
                    ConcurrentHashMap()
                }
                fun saveResult(key: Class<out AndroidStartUp<*>>, value: Result<*>) {
                    result[key] = value
                }
                fun getResult(key: Class<out AndroidStartUp<*>>): Result<*>? {
                    return result[key]
                }
                companion object {
                    val instance: StartupResultManager by lazy {
                        StartupResultManager()
                    }
                }
            }
            

            首先创建一个任务结果管理类,这个类主要就是用来存储每个任务执行的结果,这个存储的主要作用就是,如果某个任务依赖上一个任务的返回结果,这里就用到了。

            class StartupManager {
                private var list: List<AndroidStartUp<*>>? = null
                constructor(list: List<AndroidStartUp<*>>) {
                    this.list = list
                }
                fun start(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))
                    }
                }
                class Builder {
                    private val list: MutableList<AndroidStartUp<*>> by lazy {
                        mutableListOf()
                    }
                    fun setTask(task: AndroidStartUp<*>): Builder {
                        list.add(task)
                        return this
                    }
                    fun setAllTask(tasks: MutableList<AndroidStartUp<*>>): Builder {
                        list.addAll(tasks)
                        return this
                    }
                    fun builder(): StartupManager {
                        return StartupManager(list)
                    }
                }
            }
            

            这里就是将所有的task集中起来管理,采用建造者设计模式,核心方法是start方法,会将拓扑排序之后的结果统一执行

            2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task1
            2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task2
            2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task3
            2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task4
            2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出顶点--class com.lay.toposort.api.Task5
            2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: createTask Task1
            2022-10-06 20:23:02.878 27742-27742/com.lay.mvi E/TAG: createTask Task2
            2022-10-06 20:23:02.878 27742-27742/com.lay.mvi E/TAG: createTask Task3
            2022-10-06 20:23:02.878 27742-27742/com.lay.mvi E/TAG: createTask Task4
            2022-10-06 20:23:02.878 27742-27742/com.lay.mvi E/TAG: createTask Task5

            我们可以看到,每个任务都执行了

            2.2 线程管理

            回到开始的一个问题,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-06 21:03:56.560 28822-28866/com.lay.mvi E/TAG: 开始执行线程1第一步
            2022-10-06 21:03:56.560 28822-28866/com.lay.mvi E/TAG: 开始执行线程1第二步
            2022-10-06 21:03:58.561 28822-28866/com.lay.mvi E/TAG: 开始执行线程1第三步
            2022-10-06 21:03:59.564 28822-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-06 21:27:12.236 29154-29154/com.lay.mvi E/TAG: 开始执行线程2
            2022-10-06 21:27:12.236 29154-29154/com.lay.mvi E/TAG: 开始执行线程3
            2022-10-06 21:27:12.238 29154-29189/com.lay.mvi E/TAG: 开始执行线程1第一步
            2022-10-06 21:27:13.240 29154-29190/com.lay.mvi E/TAG: 线程2执行完成
            2022-10-06 21:27:13.242 29154-29191/com.lay.mvi E/TAG: 线程3执行完成
            2022-10-06 21:27:13.242 29154-29189/com.lay.mvi E/TAG: 开始执行线程1第二步

            2.2.3 任务分发

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

            interface IDispatcher {
                fun callOnMainThread():Boolean //是否在主线程中执行
                fun waitOnMainThread():Boolean //是否需要等待该任务完成
                fun toWait() //等待父任务执行完成
                fun toCountDown() //父任务执行完成
                fun executor():Executor //线程池
                fun threadPriority():Int //线程优先级
            }
            

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

            abstract class AbsAndroidStartUp<T> : AndroidStartUp<T> {
                //依赖的任务数的个数作为状态值
                private val countDownLatch = CountDownLatch(getDependencyCount())
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return null
                }
                override fun getDependencyCount(): Int {
                    return if (dependencies() != null) dependencies()!!.size else 0
                }
                override fun executor(): Executor {
                    return Executors.newFixedThreadPool(5)
                }
                override fun toWait() {
                    countDownLatch.await()
                }
                override fun toCountDown() {
                    countDownLatch.countDown()
                }
                override fun threadPriority(): Int {
                    return Process.THREAD_PRIORITY_DEFAULT
                }
            }
            

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

            class Task1 : AbsAndroidStartUp<String>() {
                override fun createTask(context: Context): String {
                    Log.e("TAG", "createTask Task1")
                    return "Task1执行的结果"
                }
                override fun callOnMainThread(): Boolean {
                    return false
                }
                override fun waitOnMainThread(): Boolean {
                    return false
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return null
                }
            }
            

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

            class Task2 : AbsAndroidStartUp<String>() {
                override fun createTask(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 ""
                    }
                }
                override fun callOnMainThread(): Boolean {
                    return false
                }
                override fun waitOnMainThread(): Boolean {
                    return false
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return mutableListOf(Task1::class.java)
                }
                class myCallable : Callable<String> {
                    override fun call(): String {
                        Thread.sleep(1500)
                        return "任务3线程执行返回结果"
                    }
                }
            }
            
            class Task4 : AbsAndroidStartUp<String>() {
                override fun createTask(context: Context): String {
                    Log.e("TAG", "createTask Task4")
                    //注意,这里取值是取不到的!
                    val result = StartupResultManager.instance.getResult(Task2::class.java)
                    val data = result!!.data
                    //进行task4 初始化任务
                    Log.e("TAG", "$data 开始执行任务4")
                    return ""
                }
                override fun callOnMainThread(): Boolean {
                    return false
                }
                override fun waitOnMainThread(): Boolean {
                    return false
                }
                override fun dependencies(): List<Class<out AndroidStartUp<*>>>? {
                    return mutableListOf(Task2::class.java)
                }
            }
            

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

            fun start(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()
                    }
                }
            }
            
            class StartupRunnable : Runnable {
                private var task: AndroidStartUp<*>? = null
                private var context: Context? = null
                private var manager:StartupManager? = null
                constructor(context: Context, task: AndroidStartUp<*>,manager: StartupManager) {
                    this.task = task
                    this.context = context
                    this.manager = manager
                }
                override fun run() {
                    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!!)
                }
            }
            

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

            fun notify(task: AndroidStartUp<*>) {
                dependency?.get(task.javaClass)?.forEach {
                    //通知全部依赖项
                    startupStore?.get(it)?.toCountDown()
                }
            }
            

            2.3 我们的目标

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

            //状态数由节点的个数决定
            private val mainCountDownLatch by lazy {
                CountDownLatch(list!!.size)
            }
            
            fun notifyChildren(task: AndroidStartUp<*>) {
                sortStore?.notify(task)
                //每次唤醒一个任务,就需要countdown
                mainCountDownLatch.countDown()
            }
            fun await() {
                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-07 10:17:22.807 3867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task1@fc61d74 callOnIOThread
            2022-10-07 10:17:22.808 3867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task3@53df312 callOnIOThread
            2022-10-07 10:17:22.808 3867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task2@d5c2ae3 callOnMainThread
            2022-10-07 10:17:24.316 3867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task4@9842e5e callOnIOThread
            2022-10-07 10:17:24.319 3867-3867/com.lay.mvi E/TAG: com.lay.mvi.topo.Task5@273a70c callOnIOThread

            优化后:

            2022-10-07 10:12:32.954 3662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task1@fc61d74 callOnIOThread
            2022-10-07 10:12:32.954 3662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task3@53df312 callOnIOThread
            2022-10-07 10:12:32.957 3662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task4@3e19ce0 callOnIOThread
            2022-10-07 10:12:32.957 3662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task5@9842e5e callOnIOThread
            2022-10-07 10:12:32.957 3662-3662/com.lay.mvi E/TAG: com.lay.mvi.topo.Task2@e1c923f callOnMainThread

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

            fun getResult(): 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元数据

            <provider
                android:name="com.lay.toposort.provider.StartupContentProvider"
                android:authorities="com.lay.provider">
                <meta-data
                    android:name="com.lay.mvi.topo.Task5"
                    android:value="app_startup" />
            </provider>
            

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

            object ProviderInitialize {
                private const val META_KEY = "app_startup"
                fun initialMetaData(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)
                }
                private fun doInitializeTask(
                    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.746 32620-32620/com.lay.mvi E/TAG: key ===> com.lay.mvi.topo.Task5
            2022-10-07 09:05:10.746 32620-32620/com.lay.mvi E/TAG: dataKey ===> app_startup

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

            3.2 注册Task

            class StartupContentProvider : ContentProvider() {
                override fun onCreate(): Boolean {
                    context?.let {
                        val list = ProviderInitialize.initialMetaData(it)
                        StartupManager.Builder()
                            .setAllTask(list)
                            .builder().start(it).await()
                    }
                    return false
                }
            

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

            2022-10-07 09:43:08.114 1721-1721/com.lay.mvi E/TAG: StartupContentProvider onCreate
            2022-10-07 09:43:08.118 1721-1721/com.lay.mvi E/TAG: key ===> com.lay.mvi.topo.Task5
            2022-10-07 09:43:08.118 1721-1721/com.lay.mvi E/TAG: dataKey ===> app_startup
            2022-10-07 09:43:08.124 1721-1721/com.lay.mvi E/TAG: clazz ===> class com.lay.mvi.topo.Task5
            2022-10-07 09:43:08.124 1721-1721/com.lay.mvi E/TAG: clazz ===> class com.lay.mvi.topo.Task3
            2022-10-07 09:43:08.125 1721-1721/com.lay.mvi E/TAG: clazz ===> class com.lay.mvi.topo.Task1
            2022-10-07 09:43:08.126 1721-1721/com.lay.mvi E/TAG: clazz ===> class com.lay.mvi.topo.Task4
            2022-10-07 09:43:08.126 1721-1721/com.lay.mvi E/TAG: clazz ===> class com.lay.mvi.topo.Task2
            2022-10-07 09:43:08.126 1721-1721/com.lay.mvi E/TAG: clazz ===> class com.lay.mvi.topo.Task1
            2022-10-07 09:43:08.255 1721-1771/com.lay.mvi E/TAG: class com.lay.mvi.topo.Task1
            2022-10-07 09:43:08.255 1721-1771/com.lay.mvi E/TAG: createTask Task1
            2022-10-07 09:43:08.257 1721-1773/com.lay.mvi E/TAG: class com.lay.mvi.topo.Task2
            2022-10-07 09:43:08.261 1721-1772/com.lay.mvi E/TAG: class com.lay.mvi.topo.Task3
            2022-10-07 09:43:08.261 1721-1774/com.lay.mvi E/TAG: class com.lay.mvi.topo.Task4
            2022-10-07 09:43:08.261 1721-1771/com.lay.mvi E/TAG: node class com.lay.mvi.topo.Task1 -- class com.lay.mvi.topo.Task3
            2022-10-07 09:43:08.261 1721-1771/com.lay.mvi E/TAG: node class com.lay.mvi.topo.Task1 -- class com.lay.mvi.topo.Task2
            2022-10-07 09:43:08.261 1721-1772/com.lay.mvi E/TAG: createTask Task2
            2022-10-07 09:43:08.261 1721-1773/com.lay.mvi E/TAG: createTask Task3
            2022-10-07 09:43:08.261 1721-1772/com.lay.mvi E/TAG: node class com.lay.mvi.topo.Task3 -- class com.lay.mvi.topo.Task5
            2022-10-07 09:43:08.263 1721-1775/com.lay.mvi E/TAG: class com.lay.mvi.topo.Task5
            2022-10-07 09:43:09.764 1721-1773/com.lay.mvi E/TAG: node class com.lay.mvi.topo.Task2 -- class com.lay.mvi.topo.Task4
            2022-10-07 09:43:09.764 1721-1774/com.lay.mvi E/TAG: createTask Task4
            2022-10-07 09:43:09.764 1721-1774/com.lay.mvi E/TAG: 任务3线程执行返回结果 开始执行任务4
            2022-10-07 09:43:09.764 1721-1774/com.lay.mvi E/TAG: node class com.lay.mvi.topo.Task4 -- class com.lay.mvi.topo.Task5
            2022-10-07 09:43:09.764 1721-1775/com.lay.mvi E/TAG: createTask Task5
            2022-10-07 09:43:09.768 1721-1721/com.lay.mvi E/TAG: Application onCreate

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

            4 总结

            其实,这个框架就是JetPack组件中提供的一个用于加速App启动速度的库https://www.jb51.net/article/264521.htm ,其中的设计思想就是通过配置依赖关系和并发思想完成的,启动优化的本质就是解决任务的依赖性问题,所以在实际开发中不是所有的场景都适合,但是对于开源框架的思想我们需要了解。

            github地址:github.com/LLLLLaaayyy…

            以上就是Android性能图论在启动优化中的应用示例详解的详细内容,更多关于Android性能图论启动优化的资料请关注自由互联其它相关文章!

            上一篇:Flutter实现编写富文本Text的示例代码
            下一篇:没有了
            网友评论