Android性能圖論在啟動優(yōu)化中的應用示例詳解
正文
相信伙伴們在實際項目中都做過啟動優(yōu)化,而且大部分伙伴們對于啟動優(yōu)化的處理無非兩種:異步加載 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的初始化、業(yè)務模塊的初始化等等,當app啟動的時候,這部分會被首先加載,然后才會執(zhí)行Activity的onCreate方法加載;如果耗時嚴重,那么就會出現(xiàn)啟動白屏的情況。
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啟動后立刻回調(diào)用某個sdk的方法,因為是異步加載可能sdk還沒初始化完成,那么就會報錯;
如果使用延遲加載,將任務放在IdleHandler中處理,想必還是存在同樣的問題,那么這些老生常談的處理方式都有自己的不足之處,那么什么方式是最有效的呢?
1 圖論的基礎知識
我們一直面臨一個問題就是,對于耗時嚴重的sdk,一定要異步加載,但是如果它跟某個sdk有依賴關系呢?

假如sdk4是耗時最嚴重,我們把sdk4放在異步線程中,但是sdk5依賴sdk4和sdk3,所以會存在一個問題就是:當加載sdk5的時候,sdk4還沒有初始化完成,會報錯,期望就是等待sdk4初始化完成后,再執(zhí)行sdk5的初始化,那么有什么方式能夠集中管理這些依賴關系呢?
1.1 有向無環(huán)圖
DAG,有向無環(huán)圖,能夠管理任務之間的依賴關系,并調(diào)度這些任務,似乎能夠滿足本節(jié)開始的訴求,那么我們先了解下這種數(shù)據(jù)結(jié)構(gòu)。
頂點:在DAG中,每個節(jié)點(sdk1/sdk2/sdk3......)都是一個頂點;
邊:連接每個節(jié)點的連接線;
入度:每個節(jié)點依賴的節(jié)點數(shù),形象來說就是有幾根線連接到該節(jié)點,例如sdk2的入度是1,sdk5的入度是2。
我們從圖中可以看出,是有方向的,但是沒有路徑再次回到起點,因此就叫做有向無環(huán)圖
1.2 拓撲排序
拓撲排序用于對節(jié)點的依賴關系進行排序,主要分為兩種:DFS(深度優(yōu)先遍歷)、BFS(廣度優(yōu)先遍歷),如果了解二叉樹的話,對于這兩種算法應該比較熟悉。

我們就拿這張圖來演示,拓撲排序算法的流程:
(1)首先找到圖中,入度為0的頂點,那么這張圖中入度為0的頂點就是sdk1,然后刪除

(2)刪除之后,再次找到入度為0的頂點,這個時候有兩個入度為0的頂點,sdk2和sdk3,所以拓撲排序的結(jié)果不是唯一的!

(3)依次遞歸,直到刪除全部入度為0的頂點,完成拓撲排序

1.3 拓撲排序?qū)崿F(xiàn)
interface AndroidStartUp<T> {
//創(chuàng)建任務
fun createTask(context: Context):T
//依賴的任務
fun dependencies():List<Class<out AndroidStartUp<*>>>?
//入度數(shù)
fun getDependencyCount():Int
}
首先,針對節(jié)點的屬性,每個節(jié)點都是一個任務,它有自己依賴的任務項,并可以設置入度數(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
}
}
如果這個任務沒有任何依賴項,那么就說明當前任務是一個頂點,入度數(shù)為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個任務之后,對于外界來說,其實不需要關心這個依賴關系,所有的任務隨機組合,但是最終拿到的結(jié)果其實就是按照依賴關系排序出來的。
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<*>>) {
//遍歷全部的節(jié)點
map.forEach { node ->
//記錄入度數(shù)
inDegree[node.javaClass] = node.getDependencyCount()
if (node.getDependencyCount() == 0) {
//查找到頂點
queue.addLast(node.javaClass)
} else {
//如果不是頂點需要查找依賴關系,找到每個節(jié)點對應的邊
//例如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)
//查找依賴關系,凡是依賴該頂點的,入度數(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ù)拓撲排序廣度優(yōu)先寫的算法,思想也很簡單,就是第一輪遍歷,首先拿到全部的頂點,以及每個節(jié)點與其他節(jié)點的依賴關系:(父節(jié)點 -- 子節(jié)點),也就是每條邊;
然后會遞歸頂點存儲集合,從棧中取出每個頂點,每個頂點對應的邊節(jié)點入度數(shù)全部減1,對于成為頂點的節(jié)點放入頂點集合繼續(xù)遍歷。
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
最終得到拓撲排序的結(jié)果。
2 任務管理
當我們完成了拓撲排序,只是完成了一小部分,關鍵在于,我們?nèi)绾巫屵@些任務運行起來,還有就是同步問題,當在子線程中的任務依賴主線程任務時,如何等到主線程任務執(zhí)行完成,再執(zhí)行子線程任務,這些都是這個框架的核心功能。
2.1 任務啟動
/**
* 作者:lay
* 用于存儲每個任務執(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)建一個任務結(jié)果管理類,這個類主要就是用來存儲每個任務執(zhí)行的結(jié)果,這個存儲的主要作用就是,如果某個任務依賴上一個任務的返回結(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("請在主線程中使用該框架")
}
//排序
val sortStore = MyTopoSort().sort(list)
sortStore.getResult().forEach { task ->
//執(zhí)行創(chuàng)建任務
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方法,會將拓撲排序之后的結(jié)果統(tǒng)一執(zhí)行
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
我們可以看到,每個任務都執(zhí)行了
2.2 線程管理
回到開始的一個問題,sdk4是耗時任務,可以放在子線程中執(zhí)行,但是又依賴sdk2的一個返回值,這種情況下,我們其實不能保證每個任務都是在主線程中執(zhí)行的,需要等待某個線程執(zhí)行完成之后,再執(zhí)行下個線程,我們先看一個簡單的問題:假如有兩個線程AB,A線程需要三步完成,當執(zhí)行到第二步的時候,開始執(zhí)行B線程,這種情況下該怎么處理?
2.2.1 wait/notify
wait/notify能夠?qū)崿F(xiàn)嗎?
val lock = Object()
val t1 = Thread{
synchronized(lock){
Log.e("TAG","開始執(zhí)行線程1第一步")
Log.e("TAG","開始執(zhí)行線程1第二步")
lock.notify()
Thread.sleep(2000)
Log.e("TAG","開始執(zhí)行線程1第三步")
}
}
val t2 = Thread{
synchronized(lock){
lock.wait()
Thread.sleep(1000)
Log.e("TAG","開始執(zhí)行線程2")
}
}
t2.start()
t1.start()
t2.join()
t1.join()
線程1和線程2共用一把鎖,當線程2啟動之后,就會釋放這把鎖,然后線程1開始執(zhí)行,當執(zhí)行到第二步的時候,喚醒線程2執(zhí)行,但是結(jié)果并不是我們想的那樣。
2022-10-06 21:03:56.560 28822-28866/com.lay.mvi E/TAG: 開始執(zhí)行線程1第一步
2022-10-06 21:03:56.560 28822-28866/com.lay.mvi E/TAG: 開始執(zhí)行線程1第二步
2022-10-06 21:03:58.561 28822-28866/com.lay.mvi E/TAG: 開始執(zhí)行線程1第三步
2022-10-06 21:03:59.564 28822-28865/com.lay.mvi E/TAG: 開始執(zhí)行線程2
這個是為什么呢?
是因為兩個線程共用一把鎖,當線程1執(zhí)行到第二步時,喚醒線程2,但是線程1還是持有這把鎖沒有釋放,導致線程2無法進入同步代碼塊,只有等到線程1執(zhí)行完成之后,才執(zhí)行了線程2.
2.2.2 CountDownLatch
如果看過系統(tǒng)源碼的伙伴,對于閉鎖應該是很熟悉了,它的原理就是會等待所有的線程都執(zhí)行完成之后,再執(zhí)行下一個任務。
val countDownLatch = CountDownLatch(2)
Thread{
Log.e("TAG","開始執(zhí)行線程1第一步")
countDownLatch.await()
Log.e("TAG","開始執(zhí)行線程1第二步")
}.start()
Log.e("TAG","開始執(zhí)行線程2")
Thread{
SystemClock.sleep(1000)
Log.e("TAG","線程2執(zhí)行完成")
countDownLatch.countDown()
}.start()
Log.e("TAG","開始執(zhí)行線程3")
Thread{
SystemClock.sleep(1000)
Log.e("TAG","線程3執(zhí)行完成")
countDownLatch.countDown()
}.start()
聲明了CountDownLatch并定義狀態(tài)值為2,每次執(zhí)行一次countDown方法,狀態(tài)值會-1,當?shù)扔?時,會回到調(diào)用await的地方,繼續(xù)執(zhí)行。
2022-10-06 21:27:12.236 29154-29154/com.lay.mvi E/TAG: 開始執(zhí)行線程2
2022-10-06 21:27:12.236 29154-29154/com.lay.mvi E/TAG: 開始執(zhí)行線程3
2022-10-06 21:27:12.238 29154-29189/com.lay.mvi E/TAG: 開始執(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: 開始執(zhí)行線程1第二步
2.2.3 任務分發(fā)
既然不能保證每個任務都在主線程中執(zhí)行,那么就需要對任務做配置
interface IDispatcher {
fun callOnMainThread():Boolean //是否在主線程中執(zhí)行
fun waitOnMainThread():Boolean //是否需要等待該任務完成
fun toWait() //等待父任務執(zhí)行完成
fun toCountDown() //父任務執(zhí)行完成
fun executor():Executor //線程池
fun threadPriority():Int //線程優(yōu)先級
}
需要知道當前任務是否需要在子線程中執(zhí)行,如果需要在子線程中執(zhí)行,是否需要等待其他任務執(zhí)行完成。
abstract class AbsAndroidStartUp<T> : AndroidStartUp<T> {
//依賴的任務數(shù)的個數(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也需要做一次改造,需要實現(xiàn)這個接口,在這個抽象類中,需要創(chuàng)建一個CountDownLatch,當前任務的依賴任務數(shù)作為狀態(tài)值,當這個任務執(zhí)行之前先await,等待依賴的任務執(zhí)行完成之后,再執(zhí)行自己的任務。
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
}
}
那么假設所有的任務都在子線程中執(zhí)行,如果沒有閉鎖,那么就不存在任務執(zhí)行的先后順序,再次回到前面的問題,因為Task2是在子線程中執(zhí)行的,而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線程執(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
//進行task4 初始化任務
Log.e("TAG", "$data 開始執(zhí)行任務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)
}
}
但是當我們使用閉鎖之后,每個任務在執(zhí)行之前,首先會調(diào)用toWait方法,等待依賴的全部項執(zhí)行完成之后,再執(zhí)行自己的task代碼塊;
fun start(context: Context) {
//判斷是否在主線程中執(zhí)行
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalArgumentException("請在主線程中使用該框架")
}
//排序
sortStore = MyTopoSort().sort(list)
sortStore?.getResult()?.forEach { task ->
//判斷當前任務執(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)
//當前任務暫停執(zhí)行
task?.toWait() //注意,如果是頂點,狀態(tài)值為0,那么就不會阻塞
//等到依賴的任務全部執(zhí)行完成,再執(zhí)行
val result = task?.createTask(context!!)
StartupResultManager.instance.saveResult(task?.javaClass!!, Result(result))
//當前任務執(zhí)行完成,通知子任務
manager?.notifyChildren(task!!)
}
}
而且每個任務執(zhí)行完成之后,都需要去通知子任務,如下圖

fun notify(task: AndroidStartUp<*>) {
dependency?.get(task.javaClass)?.forEach {
//通知全部依賴項
startupStore?.get(it)?.toCountDown()
}
}
2.3 我們的目標
我們的目標僅僅是為了排序嗎?不是;既然每個任務之間相互依賴,我們?nèi)糠旁谧泳€程中,這樣主線程幾乎不耗時,直接進入主界面,可以嗎?顯然不可以,這樣初始化sdk的意義不大了,所以我們的目標就是,如果需要5個sdk全部初始化完成才能進入主界面,如何處理?還是CountDownLatch
//狀態(tài)數(shù)由節(jié)點的個數(shù)決定
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,完全可以放在子線程中執(zhí)行,而且因為存在依賴管理而不需要關系依賴關系,而對于進入app就會使用到的sdk,可以通過閉鎖進行任務管理。
2.4 同步任務阻塞異步任務處理
前面我們在使用這個框架的時候,要么是全部在主線程,要么是全部在子線程,前面我們就提到說任務不一定能全部在主線程或者子線程,看下面的場景:
如果我們拿到的拓撲排序的順序是:1-2-3-4-5,其中任務2在主線程執(zhí)行,任務1、3、4、5在子線程,當任務分發(fā)的時候,任務1在子線程,任務2在主線程,這個時候,需要等到主線程執(zhí)行完任務2才能分發(fā)任務3執(zhí)行,所以這個問題該怎么處理。
我們可以這么想,如果分發(fā)的時候,先把子線程的任務分發(fā)下去,再執(zhí)行主線程的任務,順序就變成了1-3-4-5-2,咋一看這個順序是有問題,任務2依賴任務1,怎么跑到最后邊去了?
如果是同步執(zhí)行,這個順序當然有問題,但是我們前面已經(jīng)做了很全面的異步任務處理!
分發(fā)任務1 --- 子線程 不阻塞 執(zhí)行
分發(fā)任務3 --- 子線程 阻塞 等待任務1
分發(fā)任務4 --- 子線程 阻塞 等待任務2
分發(fā)任務5 --- 子線程 阻塞 等待任務3 4
分發(fā)任務2 --- 主線程
所以即便是任務2最后分發(fā),依然不會影響每個任務的依賴關系,而且不會阻塞主線程!
優(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
我們能很清楚的看到,主線程任務不會阻塞異步任務!
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)化點就在于,當獲取拓撲排序的結(jié)果之后,根據(jù)任務是否在主線程中分類,將在主線程中的任務放在后面分發(fā)。
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中間執(zhí)行的,也就是說在ContentProvider的onCreate方法中完成注冊任務就可以。
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>
因為所有任務終點為Task5,因此我們理想的目標就是只需要在清單文件中配置一個Task,然后所有的Task都能被注冊進來,所以我們需要一個方式能夠獲取這個元數(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)
//存儲節(jié)點
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中的元數(shù)據(jù),通過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
通過遞歸的方式,倒推獲取全部的依賴節(jié)點。
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獲取元數(shù)據(jù)之后,拿到了所有的頂點的集合,然后在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線程執(zhí)行返回結(jié)果 開始執(zhí)行任務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
通過日志可以發(fā)現(xiàn),當任務注冊完成之后,才執(zhí)行了Application的onCreate方法。
4 總結(jié)
其實,這個框架就是JetPack組件中提供的一個用于加速App啟動速度的庫http://www.fzitv.net/article/264521.htm ,其中的設計思想就是通過配置依賴關系和并發(fā)思想完成的,啟動優(yōu)化的本質(zhì)就是解決任務的依賴性問題,所以在實際開發(fā)中不是所有的場景都適合,但是對于開源框架的思想我們需要了解。
github地址:github.com/LLLLLaaayyy…
以上就是Android性能圖論在啟動優(yōu)化中的應用示例詳解的詳細內(nèi)容,更多關于Android性能圖論啟動優(yōu)化的資料請關注腳本之家其它相關文章!
相關文章
Android中ProgressDialog的dismiss()與cancel()方法的區(qū)別
本文主要介紹了Android中ProgressDialog的dismiss()與cancel()方法的區(qū)別,具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04
Android嵌套RecyclerView左右滑動替代自定義view
這篇文章主要介紹了Android嵌套RecyclerView左右滑動替代自定義view,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-06-06
最好用的Android省市區(qū)三級聯(lián)動選擇效果
這篇文章主要為大家詳細介紹了最好用的Android省市區(qū)三級聯(lián)動選擇效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02
android自定義gradle插件并且發(fā)布到本地倉庫詳細教程
這篇文章主要介紹了android自定義gradle插件并且發(fā)布到本地倉庫詳細教程的相關資料,需要的朋友可以參考下2023-07-07
Android實現(xiàn)自定義圓角對話框Dialog的示例代碼
項目中多處用到對話框,本篇文章主要介紹了Android實現(xiàn)圓角對話框Dialog的示例代碼,有興趣的可以了解一下。2017-03-03

