XTask 一個擴充性極強的Android任務執行框架

xuexiangjys發表於2022-02-18

背景

很早之前接觸了RxJava的任務流操作,覺得這種將複雜業務流通過一個個操作符拆解開來,形成一條條條理清晰的function, 讓人寫起來直呼過癮.其實這就是責任鏈模式的一種應用.

但是RxJava的功能實在是太強大了, 如果僅僅是使用它來處理這些業務流我覺得還是有些大材小用了.

之前也做過一段時間的應用效能優化, 其中當然就包括應用冷啟動優化, 中間有涉及過啟動器的概念, 當時也查閱了一些現有的開源框架, 也使用過其中一些, 但是總覺得並不是很好用, 用起來不是很順手.

作為一名資深Android開源框架卷王, 當時我腦海裡就萌發一種想法, 為啥我不自己寫一個任務流執行的框架呢?想到這, 我就抽出了我的一部分業餘時間(女朋友都不陪了), 擼出了這個XTask框架, 自我感覺非常nice, 在這分享給大家.

簡介

XTask是一個擴充性極強的Android任務執行框架。

可自由定義和組合任務來實現你想要的功能,尤其適用於處理複雜的業務流程,可靈活新增前置任務或者調整執行順序。例如:應用的啟動初始化流程。

專案地址


特徵

  • 支援6種執行緒型別方式執行任務。
  • 支援任務鏈中各任務的執行執行緒排程和控制。
  • 支援快捷任務建立,同時支援自定義任務。
  • 支援序列和並行等組任務。
  • 支援任務間資料共享。
  • 支援自由組合任務執行。
  • 支援任務鏈執行取消。
  • 支援取消所有任務鏈和指定名稱的任務鏈。
  • 支援任務鏈呼叫順序記錄和查詢。
  • 支援自定義任務執行的執行緒池。

設計思想

框架主體使用責任鏈的設計模式,輔以建造者模式、工廠模式、介面卡模式、組合模式、外觀模式以及代理模式來實現。

組成結構

  • 任務鏈ITaskChainEngine:任務鏈執行引擎,負責統籌排程各任務步驟。
  • 任務步驟ITaskStep:負責具體任務邏輯處理。
  • 資料儲存倉庫IDataStore:存放資料的倉庫,主要用於儲存任務引數中的資料。
  • 任務引數ITaskParam:負責任務路徑記錄以及任務產生的引數管理。
  • 任務執行結果ITaskResult:存放任務最終執行的結果以及產生的資料。
  • 任務組IGroupTaskStep:負責統籌排程各子任務步驟。

點選檢視框架UML設計圖

日誌一覽

task_log.png

task_log2.png


整合指南

新增Gradle依賴

1.先在專案根目錄的 build.gradlerepositories 新增:

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2.然後在dependencies新增:

dependencies {
  ...
  // XTask
  implementation 'com.github.xuexiangjys:XTask:xtask-core:1.0.0'
}

使用方法

XTask作為對外統一的API入口,所有常用的方法都能從中找到。

開啟除錯模式

當需要定位問題,需要進行除錯時,可開啟除錯模式,這樣便可開啟框架的日誌。

XTask.debug(true);

XTask的API介紹

方法名描述
debug設定是否開啟除錯
setLogger自定義日誌列印
setIsLogTaskRunThreadName設定是否列印任務執行所在的執行緒名,預設false
getTaskChain獲取任務鏈執行引擎
getTask獲取簡化的任務
getTaskBuilder獲取簡化任務的構建者
getConcurrentGroupTask獲取並行任務組
getSerialGroupTask獲取序列任務組
cancelTaskChain取消指定任務鏈執行
postToMain執行任務到主執行緒
submit執行普通非同步任務
emergentSubmit執行緊急非同步任務
backgroundSubmit執行後臺非同步任務
ioSubmit執行io耗時的非同步任務
groupSubmit執行分組非同步任務

如何執行一條任務鏈

下面是一整個完整的例子:

// 1.建立一條任務鏈(必須)
final TaskChainEngine engine = XTask.getTaskChain();
// 2.設定任務鏈的初始化引數(可選)
engine.setTaskParam(TaskParam.get("chainName", engine.getName()));
TaskParam taskParam = TaskParam.get("param1", 100)
        .put("param2", true);
// 3.建立多個任務,並向任務鏈中新增(必須)
XTaskStep taskStep = XTask.getTask(new TaskCommand() {
    @Override
    public void run() {
        ITaskParam param = getTaskParam();
        Log.e(TAG, getName() + "  start, param1:" + param.get("param1") + ", chainName:" + param.get("chainName"));
        param.put("param1", 200);
        param.put("param3", "this is param3!");
    }
}, taskParam);
engine.addTask(taskStep)
        .addTask(XTask.getTask(new TaskCommand() {
            @Override
            public void run() {
                ITaskParam param = getTaskParam();
                Log.e(TAG, getName() + "  start, param1:" + param.get("param1") + ", param3:" + param.get("param3"));
                param.put("param2", false);
            }
        }));
// 4.設定任務鏈執行回撥(可選)
ICanceller canceller = engine.setTaskChainCallback(new TaskChainCallbackAdapter() {
    @Override
    public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
        Log.e(TAG, "task chain completed, thread:" + Thread.currentThread().getName());
        Map<String, Object> data = result.getDataStore().getData();
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            Log.e(TAG, "key:" + entry.getKey() + ", value:" + entry.getValue());
        }
    }
// 5.任務鏈執行(必須)
}).start();

1.建立一條任務鏈.(必須)

TaskChainEngine engine = XTask.getTaskChain();

2.設定任務鏈的初始化引數.(可選)

engine.setTaskParam(TaskParam.get("chainName", engine.getName()));

3.建立多個任務,並向任務鏈中新增.(必須)

// 設定任務初始化引數
TaskParam taskParam = TaskParam.get("param1", 100)
        .put("param2", true);
XTaskStep taskStep = XTask.getTask(new TaskCommand() {
    @Override
    public void run() {
        // ...執行任務
    }
}, taskParam);
engine.addTask(taskStep)
        .addTask(XTask.getTask(new TaskCommand() {
            @Override
            public void run() {
                // ...執行任務
            }
        }));

【注意】對於任務執行完成,需要注意以下兩點:

  • 如果任務執行成功,就呼叫notifyTaskSucceed,任務執行失敗,就呼叫notifyTaskFailed。這裡任務無論成功還是失敗,只要執行完成都需要呼叫notifyTaskXXX通知任務鏈該任務完成,否則任務將無法正常執行。
  • TaskCommandSimpleTaskStep預設提供了自動通知執行結果的功能,但是AbstractTaskStep沒有提供,需要手動通知。

4.設定任務鏈執行回撥.(可選)

呼叫setTaskChainCallback設定任務鏈執行回撥。

engine.setTaskChainCallback(new TaskChainCallbackAdapter() {

    @Override
    public boolean isCallBackOnMainThread() {
        // 回撥是否返回主執行緒, 預設是true
        return false;
    }
    @Override
    public void onTaskChainStart(@NonNull ITaskChainEngine engine) {
        Log.e(TAG, "task chain start");
    }
    @Override
    public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
        Log.e(TAG, "task chain completed, thread:" + Thread.currentThread().getName());
    }
    @Override
    public void onTaskChainError(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
        Log.e(TAG, "task chain error");
    }
})

5.任務鏈執行.(必須)

呼叫start執行任務鏈。

ICanceller canceller = engine.start();

任務建立

建立任務有兩種方式:

  • 通過XTask.getTask構建
  • 繼承SimpleTaskStep/AbstractTaskStep實現任務的自定義

通過XTask建立

通過XTask.getTask, 傳入對應的屬性進行構建
屬性名描述
name任務步驟名稱
command任務執行內容
threadType執行緒執行型別
taskParam任務引數
taskHandler任務處理者
isAutoNotify是否自動通知任務執行結果
XTaskStep taskStep = XTask.getTask(new TaskCommand() {
    @Override
    public void run() {
        // todo
    }
}, ThreadType.ASYNC, taskParam);

通過繼承建立

通過繼承SimpleTaskStep或者AbstractTaskStep實現具體功能。
public class StepATask extends SimpleTaskStep {

    @Override
    public void doTask() throws Exception {
        // todo
        // 不需要手動通知任務鏈任務完成
    }
}

public class StepBTask extends AbstractTaskStep {

    @Override
    public void doTask() throws Exception {
        // todo
        // 需手動通知任務鏈任務完成
        notifyTaskSucceed(TaskResult.succeed());
    }

    @Override
    public String getName() {
        return "StepATask";
    }
}

任務執行原則

每一個任務都是依託於任務鏈進行流程控制。任何任務都需要遵循以下原則:

  • 任何任務無論失敗還是成功,都需要呼叫notifyTaskSucceed或者notifyTaskFailed去通知任務鏈任務的完成情況。TaskCommandSimpleTaskStep預設提供了自動通知執行結果的功能。
  • 一旦任務鏈中某個任務執行失敗,整個鏈路都停止工作。
任務型別任務執行說明
TaskCommand自動通知執行結果。如需手動通知,只需設定isAutoNotify為false即可
SimpleTaskStep自動通知執行結果。如需手動通知,只需重寫isAutoNotify方法為false即可
AbstractTaskStep需手動通知執行結果

TaskCommand手動通知執行結果

在通過XTask.getTask傳入TaskCommand構建Task的時候,設定isAutoNotify為false即可手動通知執行結果。

final TaskChainEngine engine = XTask.getTaskChain();
for (int i = 0; i < 5; i++) {
    int finalI = i;
    engine.addTask(XTask.getTask(new TaskCommand() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (finalI == 2) {
                notifyTaskFailed(404, "任務執行失敗!");
            } else {
                notifyTaskSucceed(TaskResult.succeed());
            }
        }
    }, false)); // 設定手動通知執行結果
}
engine.start();

SimpleTaskStep手動通知執行結果

重寫SimpleTaskStepisAutoNotify方法為false即可手動通知執行結果。

public class StepATask extends SimpleTaskStep {

    @Override
    public void doTask() throws Exception {
        // todo
        // 手動通知任務鏈任務完成
        notifyTaskSucceed();
    }

    @Override
    protected boolean isAutoNotify() {
        return false;
    }
}

引數傳遞

  • 任何TaskStep我們都可以通過getTaskParam獲取任務引數和任務執行結果ITaskParam
  • 上一個TaskStep儲存處理過的任務引數會自動帶入到下一個TaskStep中去,因此最後一個TaskStep擁有之前所有任務的引數資料。
XTask.getTask(new TaskCommand() {
    @Override
    public void run() {
        ITaskParam param = getTaskParam();
        Log.e(TAG, getName() + "  start, param1:" + param.get("param1") + ", param3:" + param.get("param3"));
        param.put("param2", false);
    }
})

執行緒控制

設定任務的threadType型別,即可完成對任務執行執行緒的控制。目前支援6種執行緒處理方式。

型別描述執行緒池構成
MAIN主執行緒(UI執行緒)/
ASYNC非同步執行緒(開子執行緒,普通執行緒池)核心執行緒數和最大執行緒為CPU數,0s keepTime,LinkedBlockingQueue(128),執行緒優先順序5
ASYNC_IO非同步執行緒(開子執行緒,io執行緒池)核心執行緒數和最大執行緒為(2*CPU數+1),30s keepTime,LinkedBlockingQueue(128),執行緒優先順序5
ASYNC_EMERGENT非同步執行緒(開子執行緒,緊急執行緒池)核心執行緒數為2,最大執行緒為∞,60s keepTime,SynchronousQueue(不阻塞),執行緒優先順序10
ASYNC_BACKGROUND非同步執行緒(開子執行緒,優先順序較低執行緒池)核心執行緒數和最大執行緒為2,0s keepTime,LinkedBlockingQueue(128),執行緒優先順序1
SYNC同步執行緒(直接執行)/
// 1.構造時傳入執行緒
XTaskStep taskStep = XTask.getTask(new SimpleTaskCommand(1000), ThreadType.ASYNC_EMERGENT);
// 2.設定執行緒的方法
taskStep.setThreadType(ThreadType.ASYNC_IO);

任務組

目前共有序列任務組(SerialGroupTaskStep)和並行任務組(ConcurrentGroupTaskStep)

序列任務組

序列任務組是按順序依次執行,和任務鏈的處理方式類似。使用XTask.getSerialGroupTask獲取。

final TaskChainEngine engine = XTask.getTaskChain();
SerialGroupTaskStep group1 = XTask.getSerialGroupTask("group1");
for (int i = 0; i < 5; i++) {
    group1.addTask(XTask.getTask(new SimpleTaskCommand(500)));
}
SerialGroupTaskStep group2 = XTask.getSerialGroupTask("group2");
for (int i = 0; i < 5; i++) {
    group2.addTask(XTask.getTask(new SimpleTaskCommand(1000)));
}
ICanceller canceller = engine.addTask(group1)
        .addTask(group2)
        .setTaskChainCallback(new TaskChainCallbackAdapter() {
            @Override
            public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
                Log.e(TAG, "task chain completed, path:" + result.getPath());
            }
        })
        .start();
addCanceller(canceller);

並行任務組

並行任務組是組內所有任務同時執行,待所有任務都完成後才視為任務組完成。使用XTask.getConcurrentGroupTask獲取。

final TaskChainEngine engine = XTask.getTaskChain();
ConcurrentGroupTaskStep group1 = XTask.getConcurrentGroupTask("group1");
for (int i = 0; i < 5; i++) {
    group1.addTask(XTask.getTask(new SimpleTaskCommand(100 * (i + 1))));
}
ConcurrentGroupTaskStep group2 = XTask.getConcurrentGroupTask("group2");
for (int i = 0; i < 5; i++) {
    group2.addTask(XTask.getTask(new SimpleTaskCommand(200 * (i + 1))));
}
ICanceller canceller = engine.addTask(group1)
        .addTask(group2)
        .setTaskChainCallback(new TaskChainCallbackAdapter() {
            @Override
            public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
                Log.e(TAG, "task chain completed, path:" + result.getPath());
            }
        })
        .start();
addCanceller(canceller);

最後

如果你覺得這個專案對你有所幫助, 你可以點選star進行收藏或者將其分享出去, 讓更多的人知道這個專案!

我是xuexiangjys,一枚熱愛學習,愛好程式設計,致力於Android架構研究以及開源專案經驗分享的技術up主。獲取更多資訊,歡迎微信搜尋公眾號:【我的Android開源之旅】

相關文章