App启动框架
如上图所示,有5个启动任务,它们组成了有向无环图,Task2和Task4依赖Task1,需要在Task1执行完成后,Task2和Task4才开始执行,Task3依赖Task2,Task5依赖Task3和Task4。按照入度,可以统计如下表。
任务 | 入度 |
---|---|
Task1 | 0 |
Task2 | 1 |
Task3 | 1 |
Task4 | 1 |
Task5 | 2 |
按照依赖关系,可以划分为以下表格:
任务 | 被依赖的任务 |
---|---|
Task1 | [Task2,Task4] |
Task2 | Task3 |
Task4 | Task5 |
Task3 | Task5 |
上述表格表示,Task2和Task4依赖Task1,Task3依赖Task2,Task5依赖Task3和Task4。
假设任务都在主线程中完成
如果任务都在主线程中完成,那么只需要对上述有向无环图进行排序,然后执行即可。
首先,我们要给有向无环图进行排序,排序结果有以下几种可能:
1 | 2 | 3 | 4 | 5 |
1 | 2 | 4 | 3 | 5 |
1 | 4 | 2 | 3 | 5 |
排完序后,就可以直接按照拍好的序进行执行任务了。
那么,如何排序呢?
我们首先写一个启动的接口,如下所示:
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