欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android性能圖論在啟動(dòng)優(yōu)化中的應(yīng)用示例詳解

 更新時(shí)間:2022年10月09日 10:21:14   作者:Vector7  
這篇文章主要為大家介紹了Android性能圖論在啟動(dòng)優(yōu)化中的應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

相信伙伴們?cè)趯?shí)際項(xiàng)目中都做過(guò)啟動(dòng)優(yōu)化,而且大部分伙伴們對(duì)于啟動(dòng)優(yōu)化的處理無(wú)非兩種:異步加載 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)
//神策埋點(diǎn)
SensorHelper.init(this)

在Application中,做了很多初始化工作,像sdk的初始化、業(yè)務(wù)模塊的初始化等等,當(dāng)app啟動(dòng)的時(shí)候,這部分會(huì)被首先加載,然后才會(huì)執(zhí)行Activity的onCreate方法加載;如果耗時(shí)嚴(yán)重,那么就會(huì)出現(xiàn)啟動(dòng)白屏的情況。

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)
    //神策埋點(diǎn)
    SensorHelper.init(this)
}

那么異步加載或者延遲加載會(huì)有問(wèn)題嗎?只能說(shuō)不一定,如果是異步加載,可能存在的場(chǎng)景就是Activity啟動(dòng)后立刻回調(diào)用某個(gè)sdk的方法,因?yàn)槭钱惒郊虞d可能sdk還沒(méi)初始化完成,那么就會(huì)報(bào)錯(cuò);

如果使用延遲加載,將任務(wù)放在IdleHandler中處理,想必還是存在同樣的問(wèn)題,那么這些老生常談的處理方式都有自己的不足之處,那么什么方式是最有效的呢?

1 圖論的基礎(chǔ)知識(shí)

我們一直面臨一個(gè)問(wèn)題就是,對(duì)于耗時(shí)嚴(yán)重的sdk,一定要異步加載,但是如果它跟某個(gè)sdk有依賴(lài)關(guān)系呢?

假如sdk4是耗時(shí)最嚴(yán)重,我們把sdk4放在異步線程中,但是sdk5依賴(lài)sdk4和sdk3,所以會(huì)存在一個(gè)問(wèn)題就是:當(dāng)加載sdk5的時(shí)候,sdk4還沒(méi)有初始化完成,會(huì)報(bào)錯(cuò),期望就是等待sdk4初始化完成后,再執(zhí)行sdk5的初始化,那么有什么方式能夠集中管理這些依賴(lài)關(guān)系呢?

1.1 有向無(wú)環(huán)圖

DAG,有向無(wú)環(huán)圖,能夠管理任務(wù)之間的依賴(lài)關(guān)系,并調(diào)度這些任務(wù),似乎能夠滿(mǎn)足本節(jié)開(kāi)始的訴求,那么我們先了解下這種數(shù)據(jù)結(jié)構(gòu)。

頂點(diǎn):在DAG中,每個(gè)節(jié)點(diǎn)(sdk1/sdk2/sdk3......)都是一個(gè)頂點(diǎn);

:連接每個(gè)節(jié)點(diǎn)的連接線;

入度:每個(gè)節(jié)點(diǎn)依賴(lài)的節(jié)點(diǎn)數(shù),形象來(lái)說(shuō)就是有幾根線連接到該節(jié)點(diǎn),例如sdk2的入度是1,sdk5的入度是2。

我們從圖中可以看出,是有方向的,但是沒(méi)有路徑再次回到起點(diǎn),因此就叫做有向無(wú)環(huán)圖

1.2 拓?fù)渑判?/h3>

拓?fù)渑判蛴糜趯?duì)節(jié)點(diǎn)的依賴(lài)關(guān)系進(jìn)行排序,主要分為兩種:DFS(深度優(yōu)先遍歷)、BFS(廣度優(yōu)先遍歷),如果了解二叉樹(shù)的話,對(duì)于這兩種算法應(yīng)該比較熟悉。

我們就拿這張圖來(lái)演示,拓?fù)渑判蛩惴ǖ牧鞒蹋?/p>

(1)首先找到圖中,入度為0的頂點(diǎn),那么這張圖中入度為0的頂點(diǎn)就是sdk1,然后刪除

(2)刪除之后,再次找到入度為0的頂點(diǎn),這個(gè)時(shí)候有兩個(gè)入度為0的頂點(diǎn),sdk2和sdk3,所以拓?fù)渑判虻慕Y(jié)果不是唯一的!

(3)依次遞歸,直到刪除全部入度為0的頂點(diǎn),完成拓?fù)渑判?/strong>

1.3 拓?fù)渑判驅(qū)崿F(xiàn)

interface AndroidStartUp<T> {
    //創(chuàng)建任務(wù)
    fun createTask(context: Context):T
    //依賴(lài)的任務(wù)
    fun dependencies():List<Class<out AndroidStartUp<*>>>?
    //入度數(shù)
    fun getDependencyCount():Int
}

首先,針對(duì)節(jié)點(diǎn)的屬性,每個(gè)節(jié)點(diǎn)都是一個(gè)任務(wù),它有自己依賴(lài)的任務(wù)項(xiàng),并可以設(shè)置入度數(shù)。

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

如果這個(gè)任務(wù)沒(méi)有任何依賴(lài)項(xiàng),那么就說(shuō)明當(dāng)前任務(wù)是一個(gè)頂點(diǎn),入度數(shù)為0.

接下來(lái),我們寫(xiě)5個(gè)任務(wù),先把下圖的邏輯關(guān)系分配好

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個(gè)任務(wù)之后,對(duì)于外界來(lái)說(shuō),其實(shí)不需要關(guān)心這個(gè)依賴(lài)關(guān)系,所有的任務(wù)隨機(jī)組合,但是最終拿到的結(jié)果其實(shí)就是按照依賴(lài)關(guān)系排序出來(lái)的。

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()
    }
    //存儲(chǔ)頂點(diǎn)
    private val queue: ArrayDeque<Class<out AndroidStartUp<*>>> by lazy {
        ArrayDeque()
    }
    fun sort(map: List<AndroidStartUp<*>>) {
        //遍歷全部的節(jié)點(diǎn)
        map.forEach { node ->
            //記錄入度數(shù)
            inDegree[node.javaClass] = node.getDependencyCount()
            if (node.getDependencyCount() == 0) {
                //查找到頂點(diǎn)
                queue.addLast(node.javaClass)
            } else {
                //如果不是頂點(diǎn)需要查找依賴(lài)關(guān)系,找到每個(gè)節(jié)點(diǎn)對(duì)應(yīng)的邊
                //例如node == task2 依賴(lài) task1
                // task1 -- task2就是一條邊,因此需要拿task1作為key,存儲(chǔ)這條邊
                // task1 -- task3也是一條邊,在遍歷到task3的時(shí)候,也需要存進(jìn)來(lái)
                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<*>>>()
        //依次刪除頂點(diǎn)
        while (queue.isNotEmpty()) {
            //取出頂點(diǎn)
            val node = queue.removeFirst()
            Log.e("TAG","取出頂點(diǎn)--$node")
            result.add(node)
            //查找依賴(lài)關(guān)系,凡是依賴(lài)該頂點(diǎn)的,入度數(shù)都 -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)
                    }
                }
            }
        }
    }
}

上面是根據(jù)拓?fù)渑判驈V度優(yōu)先寫(xiě)的算法,思想也很簡(jiǎn)單,就是第一輪遍歷,首先拿到全部的頂點(diǎn),以及每個(gè)節(jié)點(diǎn)與其他節(jié)點(diǎn)的依賴(lài)關(guān)系:(父節(jié)點(diǎn) -- 子節(jié)點(diǎn)),也就是每條邊;

然后會(huì)遞歸頂點(diǎn)存儲(chǔ)集合,從棧中取出每個(gè)頂點(diǎn),每個(gè)頂點(diǎn)對(duì)應(yīng)的邊節(jié)點(diǎn)入度數(shù)全部減1,對(duì)于成為頂點(diǎn)的節(jié)點(diǎn)放入頂點(diǎn)集合繼續(xù)遍歷。

2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task1
2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task2
2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task3
2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task4
2022-10-06 16:04:31.886 25994-25994/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task5

最終得到拓?fù)渑判虻慕Y(jié)果。

2 任務(wù)管理

當(dāng)我們完成了拓?fù)渑判?,只是完成了一小部分,關(guān)鍵在于,我們?nèi)绾巫屵@些任務(wù)運(yùn)行起來(lái),還有就是同步問(wèn)題,當(dāng)在子線程中的任務(wù)依賴(lài)主線程任務(wù)時(shí),如何等到主線程任務(wù)執(zhí)行完成,再執(zhí)行子線程任務(wù),這些都是這個(gè)框架的核心功能。

2.1 任務(wù)啟動(dòng)

/**
 * 作者:lay
 * 用于存儲(chǔ)每個(gè)任務(wù)執(zhí)行返回的結(jié)果
 */
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()
        }
    }
}

首先創(chuàng)建一個(gè)任務(wù)結(jié)果管理類(lèi),這個(gè)類(lèi)主要就是用來(lái)存儲(chǔ)每個(gè)任務(wù)執(zhí)行的結(jié)果,這個(gè)存儲(chǔ)的主要作用就是,如果某個(gè)任務(wù)依賴(lài)上一個(gè)任務(wù)的返回結(jié)果,這里就用到了。

class StartupManager {
    private var list: List<AndroidStartUp<*>>? = null
    constructor(list: List<AndroidStartUp<*>>) {
        this.list = list
    }
    fun start(context: Context) {
        //判斷是否在主線程中執(zhí)行
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw IllegalArgumentException("請(qǐng)?jiān)谥骶€程中使用該框架")
        }
        //排序
        val sortStore = MyTopoSort().sort(list)
        sortStore.getResult().forEach { task ->
            //執(zhí)行創(chuàng)建任務(wù)
            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集中起來(lái)管理,采用建造者設(shè)計(jì)模式,核心方法是start方法,會(huì)將拓?fù)渑判蛑蟮慕Y(jié)果統(tǒng)一執(zhí)行

2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task1
2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task2
2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task3
2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出頂點(diǎn)--class com.lay.toposort.api.Task4
2022-10-06 20:23:02.876 27742-27742/com.lay.mvi E/TAG: 取出頂點(diǎn)--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

我們可以看到,每個(gè)任務(wù)都執(zhí)行了

2.2 線程管理

回到開(kāi)始的一個(gè)問(wèn)題,sdk4是耗時(shí)任務(wù),可以放在子線程中執(zhí)行,但是又依賴(lài)sdk2的一個(gè)返回值,這種情況下,我們其實(shí)不能保證每個(gè)任務(wù)都是在主線程中執(zhí)行的,需要等待某個(gè)線程執(zhí)行完成之后,再執(zhí)行下個(gè)線程,我們先看一個(gè)簡(jiǎn)單的問(wèn)題:假如有兩個(gè)線程AB,A線程需要三步完成,當(dāng)執(zhí)行到第二步的時(shí)候,開(kāi)始執(zhí)行B線程,這種情況下該怎么處理?

2.2.1 wait/notify

wait/notify能夠?qū)崿F(xiàn)嗎?

val lock = Object()
val t1 = Thread{
    synchronized(lock){
        Log.e("TAG","開(kāi)始執(zhí)行線程1第一步")
        Log.e("TAG","開(kāi)始執(zhí)行線程1第二步")
        lock.notify()
        Thread.sleep(2000)
        Log.e("TAG","開(kāi)始執(zhí)行線程1第三步")
    }
}
val t2 = Thread{
    synchronized(lock){
        lock.wait()
        Thread.sleep(1000)
        Log.e("TAG","開(kāi)始執(zhí)行線程2")
    }
}
t2.start()
t1.start()
t2.join()
t1.join()

線程1和線程2共用一把鎖,當(dāng)線程2啟動(dòng)之后,就會(huì)釋放這把鎖,然后線程1開(kāi)始執(zhí)行,當(dāng)執(zhí)行到第二步的時(shí)候,喚醒線程2執(zhí)行,但是結(jié)果并不是我們想的那樣。

2022-10-06 21:03:56.560 28822-28866/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程1第一步
2022-10-06 21:03:56.560 28822-28866/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程1第二步
2022-10-06 21:03:58.561 28822-28866/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程1第三步
2022-10-06 21:03:59.564 28822-28865/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程2

這個(gè)是為什么呢?

是因?yàn)閮蓚€(gè)線程共用一把鎖,當(dāng)線程1執(zhí)行到第二步時(shí),喚醒線程2,但是線程1還是持有這把鎖沒(méi)有釋放,導(dǎo)致線程2無(wú)法進(jìn)入同步代碼塊,只有等到線程1執(zhí)行完成之后,才執(zhí)行了線程2.

2.2.2 CountDownLatch

如果看過(guò)系統(tǒng)源碼的伙伴,對(duì)于閉鎖應(yīng)該是很熟悉了,它的原理就是會(huì)等待所有的線程都執(zhí)行完成之后,再執(zhí)行下一個(gè)任務(wù)。

val countDownLatch = CountDownLatch(2)
Thread{
    Log.e("TAG","開(kāi)始執(zhí)行線程1第一步")
    countDownLatch.await()
    Log.e("TAG","開(kāi)始執(zhí)行線程1第二步")
}.start()
Log.e("TAG","開(kāi)始執(zhí)行線程2")
Thread{
    SystemClock.sleep(1000)
    Log.e("TAG","線程2執(zhí)行完成")
    countDownLatch.countDown()
}.start()
Log.e("TAG","開(kāi)始執(zhí)行線程3")
Thread{
    SystemClock.sleep(1000)
    Log.e("TAG","線程3執(zhí)行完成")
    countDownLatch.countDown()
}.start()

聲明了CountDownLatch并定義狀態(tài)值為2,每次執(zhí)行一次countDown方法,狀態(tài)值會(huì)-1,當(dāng)?shù)扔?時(shí),會(huì)回到調(diào)用await的地方,繼續(xù)執(zhí)行。

2022-10-06 21:27:12.236 29154-29154/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程2
2022-10-06 21:27:12.236 29154-29154/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程3
2022-10-06 21:27:12.238 29154-29189/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程1第一步
2022-10-06 21:27:13.240 29154-29190/com.lay.mvi E/TAG: 線程2執(zhí)行完成
2022-10-06 21:27:13.242 29154-29191/com.lay.mvi E/TAG: 線程3執(zhí)行完成
2022-10-06 21:27:13.242 29154-29189/com.lay.mvi E/TAG: 開(kāi)始執(zhí)行線程1第二步

2.2.3 任務(wù)分發(fā)

既然不能保證每個(gè)任務(wù)都在主線程中執(zhí)行,那么就需要對(duì)任務(wù)做配置

interface IDispatcher {
    fun callOnMainThread():Boolean //是否在主線程中執(zhí)行
    fun waitOnMainThread():Boolean //是否需要等待該任務(wù)完成
    fun toWait() //等待父任務(wù)執(zhí)行完成
    fun toCountDown() //父任務(wù)執(zhí)行完成
    fun executor():Executor //線程池
    fun threadPriority():Int //線程優(yōu)先級(jí)
}

需要知道當(dāng)前任務(wù)是否需要在子線程中執(zhí)行,如果需要在子線程中執(zhí)行,是否需要等待其他任務(wù)執(zhí)行完成。

abstract class AbsAndroidStartUp<T> : AndroidStartUp<T> {
    //依賴(lài)的任務(wù)數(shù)的個(gè)數(shù)作為狀態(tài)值
    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也需要做一次改造,需要實(shí)現(xiàn)這個(gè)接口,在這個(gè)抽象類(lèi)中,需要?jiǎng)?chuàng)建一個(gè)CountDownLatch,當(dāng)前任務(wù)的依賴(lài)任務(wù)數(shù)作為狀態(tài)值,當(dāng)這個(gè)任務(wù)執(zhí)行之前先await,等待依賴(lài)的任務(wù)執(zhí)行完成之后,再執(zhí)行自己的任務(wù)。

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

那么假設(shè)所有的任務(wù)都在子線程中執(zhí)行,如果沒(méi)有閉鎖,那么就不存在任務(wù)執(zhí)行的先后順序,再次回到前面的問(wèn)題,因?yàn)門(mén)ask2是在子線程中執(zhí)行的,而Task4則是依賴(lài)Task2的返回值,如果沒(méi)有同步隊(duì)列的作用,那么就會(huì)導(dǎo)致Task4無(wú)法獲取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 "任務(wù)3線程執(zhí)行返回結(jié)果"
        }
    }
}
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
        //進(jìn)行task4 初始化任務(wù)
        Log.e("TAG", "$data 開(kāi)始執(zhí)行任務(wù)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)
    }
}

但是當(dāng)我們使用閉鎖之后,每個(gè)任務(wù)在執(zhí)行之前,首先會(huì)調(diào)用toWait方法,等待依賴(lài)的全部項(xiàng)執(zhí)行完成之后,再執(zhí)行自己的task代碼塊;

fun start(context: Context) {
    //判斷是否在主線程中執(zhí)行
    if (Looper.myLooper() != Looper.getMainLooper()) {
        throw IllegalArgumentException("請(qǐng)?jiān)谥骶€程中使用該框架")
    }
    //排序
    sortStore = MyTopoSort().sort(list)
    sortStore?.getResult()?.forEach { task ->
        //判斷當(dāng)前任務(wù)執(zhí)行的線程
        if (task.callOnMainThread()) {
            //如果在主線程,那么就直接執(zhí)行
        } 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)
        //當(dāng)前任務(wù)暫停執(zhí)行
        task?.toWait() //注意,如果是頂點(diǎn),狀態(tài)值為0,那么就不會(huì)阻塞
        //等到依賴(lài)的任務(wù)全部執(zhí)行完成,再執(zhí)行
        val result = task?.createTask(context!!)
        StartupResultManager.instance.saveResult(task?.javaClass!!, Result(result))
        //當(dāng)前任務(wù)執(zhí)行完成,通知子任務(wù)
        manager?.notifyChildren(task!!)
    }
}

而且每個(gè)任務(wù)執(zhí)行完成之后,都需要去通知子任務(wù),如下圖

fun notify(task: AndroidStartUp<*>) {
    dependency?.get(task.javaClass)?.forEach {
        //通知全部依賴(lài)項(xiàng)
        startupStore?.get(it)?.toCountDown()
    }
}

2.3 我們的目標(biāo)

我們的目標(biāo)僅僅是為了排序嗎?不是;既然每個(gè)任務(wù)之間相互依賴(lài),我們?nèi)糠旁谧泳€程中,這樣主線程幾乎不耗時(shí),直接進(jìn)入主界面,可以嗎?顯然不可以,這樣初始化sdk的意義不大了,所以我們的目標(biāo)就是,如果需要5個(gè)sdk全部初始化完成才能進(jìn)入主界面,如何處理?還是CountDownLatch

//狀態(tài)數(shù)由節(jié)點(diǎn)的個(gè)數(shù)決定
private val mainCountDownLatch by lazy {
    CountDownLatch(list!!.size)
}
fun notifyChildren(task: AndroidStartUp<*>) {
    sortStore?.notify(task)
    //每次喚醒一個(gè)任務(wù),就需要countdown
    mainCountDownLatch.countDown()
}
fun await() {
    mainCountDownLatch.await()
}
StartupManager.Builder()
    .setTask(Task1())
    .setAllTask(mutableListOf(Task5(), Task4(), Task3(), Task2()))
    .builder().start(this).await()

所以對(duì)于app一開(kāi)始就用不到的sdk,完全可以放在子線程中執(zhí)行,而且因?yàn)榇嬖谝蕾?lài)管理而不需要關(guān)系依賴(lài)關(guān)系,而對(duì)于進(jìn)入app就會(huì)使用到的sdk,可以通過(guò)閉鎖進(jìn)行任務(wù)管理。

2.4 同步任務(wù)阻塞異步任務(wù)處理

前面我們?cè)谑褂眠@個(gè)框架的時(shí)候,要么是全部在主線程,要么是全部在子線程,前面我們就提到說(shuō)任務(wù)不一定能全部在主線程或者子線程,看下面的場(chǎng)景:

如果我們拿到的拓?fù)渑判虻捻樞蚴牵?-2-3-4-5,其中任務(wù)2在主線程執(zhí)行,任務(wù)1、3、4、5在子線程,當(dāng)任務(wù)分發(fā)的時(shí)候,任務(wù)1在子線程,任務(wù)2在主線程,這個(gè)時(shí)候,需要等到主線程執(zhí)行完任務(wù)2才能分發(fā)任務(wù)3執(zhí)行,所以這個(gè)問(wèn)題該怎么處理。

我們可以這么想,如果分發(fā)的時(shí)候,先把子線程的任務(wù)分發(fā)下去,再執(zhí)行主線程的任務(wù),順序就變成了1-3-4-5-2,咋一看這個(gè)順序是有問(wèn)題,任務(wù)2依賴(lài)任務(wù)1,怎么跑到最后邊去了?

如果是同步執(zhí)行,這個(gè)順序當(dāng)然有問(wèn)題,但是我們前面已經(jīng)做了很全面的異步任務(wù)處理!

分發(fā)任務(wù)1 --- 子線程 不阻塞 執(zhí)行

分發(fā)任務(wù)3 --- 子線程 阻塞 等待任務(wù)1

分發(fā)任務(wù)4 --- 子線程 阻塞 等待任務(wù)2

分發(fā)任務(wù)5 --- 子線程 阻塞 等待任務(wù)3 4

分發(fā)任務(wù)2 --- 主線程

所以即便是任務(wù)2最后分發(fā),依然不會(huì)影響每個(gè)任務(wù)的依賴(lài)關(guān)系,而且不會(huì)阻塞主線程!

優(yōu)化前:

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

優(yōu)化后:

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

我們能很清楚的看到,主線程任務(wù)不會(huì)阻塞異步任務(wù)!

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
}

主要優(yōu)化點(diǎn)就在于,當(dāng)獲取拓?fù)渑判虻慕Y(jié)果之后,根據(jù)任務(wù)是否在主線程中分類(lèi),將在主線程中的任務(wù)放在后面分發(fā)。

3 框架管理 -- ContentProvider

通過(guò)前面對(duì)于框架的梳理,我們完成了對(duì)于任務(wù)的集中管理,要知道項(xiàng)目中,可能存在多個(gè)初始化的sdk,如果每次新增一個(gè)依賴(lài)任務(wù),就需要手動(dòng)添加一個(gè)task,顯然并沒(méi)有那么靈活

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

那么有什么方式能夠在app啟動(dòng)之前,或者在Application的onCreate之前能夠完成注冊(cè)能力呢?如果有熟悉ContentProvider的伙伴應(yīng)該知道,ContentProvider的onCreate方法是在Application的onAttach和pnCreate中間執(zhí)行的,也就是說(shuō)在ContentProvider的onCreate方法中完成注冊(cè)任務(wù)就可以。

3.1 獲取ContentProvider元數(shù)據(jù)

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

因?yàn)樗腥蝿?wù)終點(diǎn)為T(mén)ask5,因此我們理想的目標(biāo)就是只需要在清單文件中配置一個(gè)Task,然后所有的Task都能被注冊(cè)進(jìn)來(lái),所以我們需要一個(gè)方式能夠獲取這個(gè)元數(shù)據(jù)。

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)
        //存儲(chǔ)節(jié)點(diǎn)
        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
        }
        //查找依賴(lài)項(xiàng)
        if (startUp.dependencies() != null) {
            //獲取全部的依賴(lài)項(xiàng)
            val dependencyList = startUp.dependencies()
            dependencyList?.forEach {
                doInitializeTask(context, it, nodeMap)
            }
        }
    }
}

通過(guò)PackageManager就可以獲取到Contentprovider中的元數(shù)據(jù),通過(guò)ProviderInfo的metaData參數(shù)可以獲取所有的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

通過(guò)遞歸的方式,倒推獲取全部的依賴(lài)節(jié)點(diǎn)。

3.2 注冊(cè)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
    }

通過(guò)PackageManager獲取元數(shù)據(jù)之后,拿到了所有的頂點(diǎn)的集合,然后在StartupContentProvider的onCreate方法中注冊(cè)全部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: 任務(wù)3線程執(zhí)行返回結(jié)果 開(kāi)始執(zhí)行任務(wù)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

通過(guò)日志可以發(fā)現(xiàn),當(dāng)任務(wù)注冊(cè)完成之后,才執(zhí)行了Application的onCreate方法。

4 總結(jié)

其實(shí),這個(gè)框架就是JetPack組件中提供的一個(gè)用于加速App啟動(dòng)速度的庫(kù)http://www.dbjr.com.cn/article/264521.htm ,其中的設(shè)計(jì)思想就是通過(guò)配置依賴(lài)關(guān)系和并發(fā)思想完成的,啟動(dòng)優(yōu)化的本質(zhì)就是解決任務(wù)的依賴(lài)性問(wèn)題,所以在實(shí)際開(kāi)發(fā)中不是所有的場(chǎng)景都適合,但是對(duì)于開(kāi)源框架的思想我們需要了解。

github地址:github.com/LLLLLaaayyy…

以上就是Android性能圖論在啟動(dòng)優(yōu)化中的應(yīng)用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android性能圖論啟動(dòng)優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論