App启动框架

App启动框架

在这里插入图片描述

如上图所示,有5个启动任务,它们组成了有向无环图,Task2和Task4依赖Task1,需要在Task1执行完成后,Task2和Task4才开始执行,Task3依赖Task2,Task5依赖Task3和Task4。按照入度,可以统计如下表。

任务入度
Task10
Task21
Task31
Task41
Task52

按照依赖关系,可以划分为以下表格:

任务被依赖的任务
Task1[Task2,Task4]
Task2Task3
Task4Task5
Task3Task5

上述表格表示,Task2和Task4依赖Task1,Task3依赖Task2,Task5依赖Task3和Task4。

假设任务都在主线程中完成

如果任务都在主线程中完成,那么只需要对上述有向无环图进行排序,然后执行即可。

首先,我们要给有向无环图进行排序,排序结果有以下几种可能:

12345
12435
14235

排完序后,就可以直接按照拍好的序进行执行任务了。

那么,如何排序呢?

我们首先写一个启动的接口,如下所示:

interface Startup<T> {
    /**
     * 任务逻辑使用
     */
    fun create(context: Context?): T

    /**
     * 本任务依赖哪些任务
     */
    fun dependencies(): List<Class<out Startup<*>>>?

    /**
     * 入度数
     */
    fun getDependenciesCount(): Int
}

因为有的任务入度为0,所以它不依赖其它任务,所以不应该让它执行一些模版代码,所以我们将Startup中的方法做一些默认实现,如下所示:

abstract class AndroidStartup<T> : Startup<T>{
    //当前任务所依赖的任务
    override fun dependencies(): List<Class<out Startup<*>>>? {
        return null
    }

    //当前任务的入度数
    override fun getDependenciesCount(): Int {
        val dependencies: List<Class<out Startup<*>>>? = dependencies()
        return dependencies?.size ?: 0
    }   
}

然后让任务继承上面的抽象类,实现里面的方法

class Task1 : AndroidStartup<Unit>() {

    override fun create(context: Context?) {
        println("Task1:学习Java基础")
        SystemClock.sleep(3000)
        println("Task1:学习Java基础")
    }

}
class Task2 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task2:学习Java基础")
        SystemClock.sleep(3000)
        println("Task2:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task1().javaClass)
        return dependencies
    }
}

class Task3 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task3:学习Java基础")
        SystemClock.sleep(3000)
        println("Task3:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task2().javaClass)
        return dependencies
    }
}

class Task4 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task4:学习Java基础")
        SystemClock.sleep(3000)
        println("Task4:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task1().javaClass)
        return dependencies
    }
}

class Task5 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task5:学习Java基础")
        SystemClock.sleep(3000)
        println("Task5:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task3().javaClass)
        dependencies.add(Task4().javaClass)
        return dependencies
    }
}

然后将上述任务进行排序,比如当有如下顺序添加代码时:

    @Test
    fun testTopologySort() {
        val list = mutableListOf<Startup<*>>()
        list.add(Task4())
        list.add(Task5())
        list.add(Task3())
        list.add(Task2())
        list.add(Task1())

        val startupSortStore = TopologySort.sort(list)
        val result = startupSortStore.result
        val sb = StringBuilder()
        sb.append("\n=============================\n")
        sb.append("Task Graph\n")
        result.forEach {
            sb.append("     ")
                .append(it.javaClass.name).append("\n")
        }
        sb.append("====================")
        println(sb.toString())
    }

排序结果:

=============================
Task Graph
     com.crystallake.appstart.Task1
     com.crystallake.appstart.Task2
     com.crystallake.appstart.Task4
     com.crystallake.appstart.Task3
     com.crystallake.appstart.Task5
====================

其中,最重要的排序算法如下:

object TopologySort {
    fun sort(startupList: List<Startup<*>>): StartupSortStore {
        //入度表
        val inDegreeMap: MutableMap<Class<out Startup<*>>, Int> = HashMap()
        //0度表
        val zeroDeque: Deque<Class<out Startup<*>>> = ArrayDeque()
        //任务表
        val startupMap: MutableMap<Class<out Startup<*>>, Startup<*>> = HashMap()
        //任务依赖表
        val startupChildrenMap: MutableMap<Class<out Startup<*>>, MutableList<Class<out Startup<*>>>> =
            HashMap()
        //1. 找出入读为0的节点,填表过程
        startupList.forEach {
            startupMap[it.javaClass] = it
            val dCount = it.getDependenciesCount()
            inDegreeMap[it.javaClass] = dCount
            if (dCount == 0) {
                zeroDeque.offer(it.javaClass)
            } else {//建立依赖关系表
                it.dependencies()?.forEach { parent ->
                    var children = startupChildrenMap[parent]

                    if (children == null) {
                        children = mutableListOf()
                        startupChildrenMap[parent] = children!!
                    }
                    children?.add(parent)
                }
            }
        }
        
        val result = mutableListOf<Startup<*>>()
        
        while (!zeroDeque.isEmpty()){
            val clz = zeroDeque.poll()
            clz?.let { 
                val startup = startupMap[it]
                startup?.let { sp ->
                    result.add(sp)
                }
                
                if (startupChildrenMap.containsKey(clz)){
                    startupChildrenMap[clz]?.forEach { 
                        val num = inDegreeMap[it]
                        inDegreeMap[it] = num!!-1
                        if (num-1 == 0){
                            zeroDeque.offer(it)
                        }
                    }
                }
            }
        }
        
    }
}

上述算法描述:

  • 遍历任务列表,找到入度为0的任务,将任务放入0度表中;
  • 如果入度不为0,建立任务依赖表
  • 使用广度优先算法,从0度表中取出任务,然后放入结果表中,同时根据任务去任务依赖表中查找,是否有任务依赖该任务,如果有,就将任务入度减1,判断减1后的任务是否为0,如果为0,则放入0度表中
  • 返回结果表

上述已经对任务进行了排序,如果只在主线程或者单一线程中执行,那么上述方式已经足够,但如果部分任务在子线程执行,部分任务在主线程中执行呢?比如task2在子线程中执行,task3在主线程中执行,那么task3需要等待task2执行完毕后才能继续执行,这种情况就需要进行任务同步。

多线程中执行

这里,我们可以用CountDownLatch类,CountDownLatch的父类有一个成员变量state,如果CountDownLatch调用了await方法,那么如果CountDownLatch的state不为0,则该任务不会执行会继续等待,调用countDown方法可以使state减一。

所以可以将每个Task的CountDownLatch的state设置为Task的入度,当依赖的Task执行完成后,将Task的入度减一,即CountDownLatch的state减一。

如Task5的入度为2,Task5依赖Task3和Task4,Task4完成后,让Task5入度减一,即Task5的CountDownLatch的state减一,此时Task5的CountDownLatch的state为1,所以会继续等待,当Task4也完成后,让Task5的CountDownLatch的state减一,此时Task5的CountDownLatch的state为0,则Task5的CountDownLatch.await会被唤醒从而继续执行。

根据上述分析,可以动手开始写代码了

首先需要一个调度接口

interface Dispatcher {
    /**
     * 返回是否在主线程中执行
     */
    fun callCreateOnMainThread():Boolean
    /**
     * 让每个任务都可以指定自己在哪个线程中执行
     */
    fun executor(): Executor
    /**
     * 指定线程都优先级
     */
    fun getThreadPriority():Int
    /**
     * 等待
     */
    fun toWait()
    /**
     * 有父任务执行完毕
     * 计数器-1
     */
    fun toNotify()
    /**
     * 是否需要主线程等待该任务执行完成
     */
    fun waitOnMainThread():Boolean
}

然后Startup接口基本不变

interface Startup<T> :Dispatcher{...}

然后AndroidStartup

abstract class AndroidStartup<T> : Startup<T>{

    private val mWaitCountDown: CountDownLatch by lazy {
        CountDownLatch(getDependenciesCount())
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        return null
    }


    override fun getDependenciesCount(): Int {
        val dependencies: List<Class<out Startup<*>>>? = dependencies()
        return dependencies?.size ?: 0
    }

    override fun toWait() {
        try {
            mWaitCountDown.await()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun toNotify() {
        mWaitCountDown.countDown()
    }

    override fun executor(): Executor {
        return ExecutorManager.ioExecutor
    }

    /**
     * 默认IO线程中执行
     */
    override fun getThreadPriority(): Int {
        return Process.THREAD_PRIORITY_DEFAULT
    }

    /**
     * 默认在子线程中执行
     */
    override fun callCreateOnMainThread(): Boolean {
        return false
    }

    /**
     * 默认不需要主线程等待自己执行完毕
     */
    override fun waitOnMainThread(): Boolean {
        return false
    }

}

任务类:

class Task1 : AndroidStartup<Unit>() {

    override fun create(context: Context?) {
        println("Task1:学习Java基础")
        SystemClock.sleep(3000)
        println("Task1:学习Java基础")
    }
}

class Task2 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task2:学习Java基础")
        SystemClock.sleep(3000)
        println("Task2:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task1().javaClass)
        return dependencies
    }

    override fun executor(): Executor {
        return ExecutorManager.mainExecutor
    }

    override fun callCreateOnMainThread(): Boolean {
        return true
    }

    override fun waitOnMainThread(): Boolean {
        return true
    }
}

class Task3 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task3:学习Java基础")
        SystemClock.sleep(3000)
        println("Task3:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task2().javaClass)
        return dependencies
    }
}

class Task4 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task4:学习Java基础")
        SystemClock.sleep(3000)
        println("Task4:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task1().javaClass)
        return dependencies
    }

    override fun executor(): Executor {
        return ExecutorManager.mainExecutor
    }

    override fun callCreateOnMainThread(): Boolean {
        return true
    }

    override fun waitOnMainThread(): Boolean {
        return true
    }
}

class Task5 : AndroidStartup<Unit>() {
    override fun create(context: Context?) {
        println("Task5:学习Java基础")
        SystemClock.sleep(3000)
        println("Task5:学习Java基础")
    }

    override fun dependencies(): List<Class<out Startup<*>>>? {
        val dependencies = mutableListOf<Class<AndroidStartup<*>>>()
        dependencies.add(Task3().javaClass)
        dependencies.add(Task4().javaClass)
        return dependencies
    }
}

由上所示,task2和task4都是在子线程中执行,task1,task3,task5都是在主线程中执行。

然后写一个StartupManager类

public class StartupManager {
    private CountDownLatch awaitCountDownLatch;
    private Context mContext;
    private List<Startup<?>> mStartupList;
    private StartupSortStore mStartupSortStore;

    public StartupManager(Context context, List<Startup<?>> startupList, CountDownLatch awaitCountDownLatch) {
        this.mContext = context;
        this.mStartupList = startupList;
        this.awaitCountDownLatch = awaitCountDownLatch;
    }

    public StartupManager start() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new RuntimeException("请在主线程调用!");
        }

        mStartupSortStore = TopologySort.INSTANCE.sort(mStartupList);

        for (Startup<?> startup : mStartupSortStore.getResult()) {
            StartupRunnable startupRunnable = new StartupRunnable(mContext, startup, this);
            if (startup.callCreateOnMainThread()) {
                startupRunnable.run();
            } else {
                startup.executor().execute(startupRunnable);
            }
        }
        return this;
    }

    public void notifyChildren(Startup<?> startup) {
        if (!startup.callCreateOnMainThread() &&
                startup.waitOnMainThread()) {
            awaitCountDownLatch.countDown();
        }

        if (mStartupSortStore.getStartupChildrenMap().containsKey(startup.getClass())) {
            List<Class<? extends Startup>> childStartupCls = mStartupSortStore.getStartupChildrenMap().get(startup.getClass());
            for (Class<? extends Startup> cls:childStartupCls){
                Startup<?> childStartup = mStartupSortStore.getStartupMap().get(cls);
                childStartup.toNotify();
            }
        }
    }

    public void await(){
        try{
            awaitCountDownLatch.await();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static class Builder{
        private List<Startup<?>> startupList = new ArrayList<>();
        private CountDownLatch awaitCountDownLatch;

        public Builder addStartup(Startup<?> startup){
            startupList.add(startup);
            return this;
        }

        public Builder addAllStartup(List<Startup<?>> startupList){
            this.startupList.addAll(startupList);
            return this;
        }

        public StartupManager build(Context context){
            awaitCountDownLatch = new CountDownLatch(startupList.size());
            return new StartupManager(context,startupList,awaitCountDownLatch);
        }
    }


}

调用

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        StartupManager.Builder()
            .addStartup(Task4())
            .addStartup(Task5())
            .addStartup(Task2())
            .addStartup(Task3())
            .addStartup(Task1())
            .build(this)
            .start().await()
    }
}

可以看到,入口是StartupManager的start方法,我们先看看start方法:

    public StartupManager start() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new RuntimeException("请在主线程调用!");
        }

        mStartupSortStore = TopologySort.INSTANCE.sort(mStartupList);

        for (Startup<?> startup : mStartupSortStore.getResult()) {
            StartupRunnable startupRunnable = new StartupRunnable(mContext, startup, this);
            if (startup.callCreateOnMainThread()) {//#1
                startupRunnable.run();
            } else {
                startup.executor().execute(startupRunnable);
            }
        }
        return this;
    }
    

首先必须在主线程中调用,然后对任务列表进行排序,对排好序的结果进行调度,如果任务在主线程中执行,则直接调用startupRunnable的run方法,startupRunnable继承Runnable接口。如果不是主线程中执行,则调用线程池执行startupRunnable。

这里有个问题,如下图所示
在这里插入图片描述

按照代码中#1处逻辑,当执行task1时,与之并列的其它任务都会被阻塞,等待task1执行完成后,再次进行循环调用。那么如何进行优化呢?这里的优化在排序当中,如下所示:

        for (startup in startupList) {
            startupMap[startup.javaClass] = startup
            val dependenciesCount = startup.getDependenciesCount()
            inDegreeMap[startup.javaClass] = dependenciesCount
            //记录入度数为0的任务
            if (dependenciesCount == 0) {
                zeroDeque.offer(startup.javaClass)
            } else {
                //遍历本任务的依赖任务,生成任务依赖表
                for (parent in startup.dependencies()!!) {
                    var children = startupChildrenMap[parent]
                    if (children == null) {
                        children = ArrayList()
                        //记录这个父任务的所有子任务
                        startupChildrenMap[parent] = children
                    }
                    children.add(startup.javaClass)
                }
            }
        }
        //2. 删除图中入度为0的这些顶点,并更新全图,最后完成排序
        val result: MutableList<Startup<*>> = ArrayList()
        val main = mutableListOf<Startup<*>>()
        val threads = mutableListOf<Startup<*>>()

        while (!zeroDeque.isEmpty()) {
            val cls = zeroDeque.poll()
            val startup = startupMap[cls]!!

            //主要是这里优化
            if (startup.callCreateOnMainThread()) {
                main.add(startup)
            } else {
                threads.add(startup)
            }

            if (startupChildrenMap.containsKey(cls)) {
                val childStartup: List<Class<out Startup<*>>> = startupChildrenMap[cls]!!
                for (childCls in childStartup) {
                    val num = inDegreeMap[childCls]
                    inDegreeMap[childCls] = num!! - 1
                    if (num - 1 == 0) {
                        zeroDeque.offer(childCls)
                    }
                }
            }
        }
        result.addAll(threads)
        result.addAll(main)
        return StartupSortStore(result, startupMap, startupChildrenMap)

排序时,将需要在子线程中执行的任务放在前面,需要在主线程中执行的任务放在后面,因为有CountDownLatch和任务依赖表,所以不会影响任务的最终执行效果。

看下StartupRunnable里面的代码:

public class StartupRunnable implements Runnable {
    
    private StartupManager mStartupManager;
    private Startup<?> mStartup;
    private Context mContext;
    
    public StartupRunnable(Context context, Startup<?> startup, StartupManager startupManager) {
        mContext = context;
        mStartup = startup;
        mStartupManager = startupManager;
    }
    
    @Override
    public void run() {
        Process.setThreadPriority(mStartup.getThreadPriority());
        mStartup.toWait();
        Object result = mStartup.create(mContext);
        StartupCacheManager.INSTANCE.saveInitializedComponent((Class<? extends Startup<Object>>) mStartup.getClass(), new Result<>(result));
        mStartupManager.notifyChildren(mStartup);
    }
}

那么,既然有任务依赖表和CountDownLatch,还有必要对有向无环图进行排序么?应该是没有必要的,因为任务入度不为0,则等待着的任务会依旧等待。所以还可以进行优化。

未完待续。。。

源码地址:https://github.com/ydslib/AppStart

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值