背景
開發中會存在多個任務之間互相依賴,運作非常慢的情況,譬如Android在主線程中初始化多個SDK導緻App啟動慢的情況,搜尋一下發現業界的通用做法是構造任務的有向無環圖,拓撲排序生成有序的任務清單,然後用線程池執行任務清單(通俗的說就是先找到沒有依賴的任務執行,執行完了以後再找到剩下的沒有依賴的任務執行,如此反複直到執行完所有任務),但是這個做法無法解決有的任務需要點選對話框授權的情況,基于這個情況打算再造一個輪子出來。
更多開發文檔擷取方法: 公衆号<Android苦做舟> 領取這些
1.Android進階開發工程師必備基礎技能
2.Android性能優化核心知識筆記
3.Android+音視訊進階開發面試題沖刺合集
4.Android 音視訊開發入門到實戰學習手冊
5.Android Framework精編核心解析
6.Flutter實戰進階技術手冊
7.近百個Android錄播視訊+音視訊視訊dome
8.Android Handler機制解析
問題
造輪子之前先梳理了一下對這個輪子的要求,發現除了有向無環圖外還是有很多細節要解決的。
-依賴任務多線程啟動
-支援互動性任務,先攔截任務,互動完成以後再繼續執行
-可視化有向無環圖
-可視化任務執行情況
-支援多線程、主線程、主程序、第一個任務、最後一個任務等配置屬性
方案
開源
TaskGraph: github.com/JonaNorman/…
線程池隻能執行沒有依賴關系的任務,TaskGraph開源庫用有向無環圖實作多線程依賴線程池,用攔截器實作互動式任務
圖中添加了A任務,B任務依賴A任務執行完再執行,其中A任務需要點選對話框才能執行。
TaskGraph taskGraph = new TaskGraph();
taskGraph.addTask(new Task("A",new Runnable() {//添加A任務
@Override
public void run() {
}
}).addTaskInterceptor(new Task.TaskInterceptor() {
@Override
public void onIntercept(Task.TaskInterceptorChain interceptorChain) {//攔截A任務,在A任務之前可以插入對話框
AlertDialog.Builder builder = new AlertDialog.Builder(TaskGraphModule.getTopActivity());
builder.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
interceptorChain.proceed();//繼續
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
interceptorChain.cancel();//取消
}
});
builder.show();
}
}));
taskGraph.addTask(new Task("B",new Runnable() {
@Override
public void run() {//添加B任務,B任務依賴A任務先完成
}
}).dependsOn("A").setMainThread(true));
taskGraph.execute();
可視化有向圖
搜尋TASK_GRAPH_LOG: graphviz:會輸出有向圖日志,複制到 graphviz-visual-editor 可視化檢視
可視化任務執行情況
python systrace.py -o trace.html -a packagename sched
packagename要替換成運作的app的包名 chrome浏覽器打開chrome://tracing/,load 按鈕加載trace.html
原理
依賴任務多線程啟動
正常的線程池隻能執行沒有依賴關系的任務,怎麼才能讓線程池支援運作互相依賴的任務呢? 先找到所有沒有進來箭頭的節點執行,在該圖中也就是A,執行完後删除這個節點和邊, 變成了下圖
繼續以上步驟,找到B運作後删除B,變成下圖這樣
繼續以上步驟,找到C D E同時運作,最終所有任務執行完畢。
把上面的步驟翻譯成術語
- 有箭頭的圖叫有向圖
- 節點有多少個進來的箭頭叫入度
- 沒有進來箭頭的節點叫入度為0的節點
- 箭頭沒有形成環的圖叫有向無環圖
- 依次找到所有入度為0的節點叫拓撲排序
這裡有個問題,多線程怎麼執行拓撲排序的節點,有兩種做法
- 拓撲排序的節點清單作為runnable送出到線程池,依賴的任務線程等待其他任務完成在執行
- 先把入度為0的所有節點送出到線程池,有一個執行完,就觸發尋找剩下入度為0的節點繼續執行 兩種方案我選了方案2,個人感覺方案2找到的節點執行順序是最優的,并且不需要線程等待,代碼簡單而且不需要空占有線程池的線程數量
主要思想:
Grpah圖有多個node節點,每個Node節點有一個Vertex頂點,多個入邊edge,多個出邊edge, 拓撲排序就是找所有node節點入度為0的邊移除然後繼續找直到找完所有節點,核心代碼位址
支援互動性任務
有些任務需要互動輸入,完成以後再繼續執行,為了實作該功能,可以用攔截器的方式來實作。
攔截器的原理就是調用到攔截器時候會用鎖等待,如果執行了proceed方法會喚醒鎖然後執行下個攔截器,如果執行了cancel會喚醒鎖終止所有任務标記cancel狀态,每個攔截器必須調用其中一個方法,要不然會一直等待 核心代碼如下:代碼位址
private void nextIntercept() {
synchronized (sync) {
currentInterceptor = taskInterceptorQueue.poll();//擷取下一個攔截器
if (currentInterceptor == null) {
return;
}
currentInterceptor.onIntercept(this);//處罰攔截器
}
while (!graphController.isFinished()) {
synchronized (sync) {
if (cancel) {//調用cancel方法會把cancel指派為true
throw new TaskCancelException();
} else if (currentInterceptor == proceedInterceptor) {//如果調用了proceed會proceedInterceptor指派為currentInterceptor
nextIntercept();//執行下一個攔截器
break;
} else {
try {
sync.wait();//等待執行proceed或者cancel方法
} catch (InterruptedException e) {
}
}
}
}
}
可視化有向無環圖
多個依賴任務添加進去以後如果不能可視化成圖就會對影響對任務的把控程度,graphviz是一個圖的可視化項目,隻要把圖的情況寫成文本輸入就會生成對應圖。
digraph pic {
A->B;
B->C;
}
可視化任務執行情況
多個任務執行實時運作情況,有助于我們優化任務依賴,主要就是在每個任務執行開始調用Trace.beginSection(name),執行完調用Trace.endSection(),然後用指令
python systrace.py -o trace.html -a packagename sched
生成trace.html,然後用chrome浏覽器打開chrome://tracing/點選load按鈕加載trace.html就可以檢視每個任務的執行情況
支援多線程、主線程、主程序、第一個任務、最後一個任務等配置屬性
任務具有多個屬性,多線程、主線程、主程序等屬性,該實作隻要加對應判斷就行,第一個任務和最後一個任務則需要周遊所有任務,添加對應依賴關系。
收獲
依賴任務多線程排程本身不是很難,在該開源項目中我收獲了很多,包括如何實作有向無環圖,如何在多線程中實作任務攔截繼發,如何使用graphviz實作可視化圖,如何用systemtrace可視化任務執行,希望看完文章的同學也可以從中學到什麼,謝謝大家的浏覽,如果覺得可以,歡迎大家多多star這個開源項目。
作者:信念着了火
連結:https://juejin.cn/post/7168092996133453861
來源:稀土掘金