XTask與RxJava的使用比較

xuexiangjys發表於2022-04-10

簡介

RxJava

RxJava是一個在Java VM上使用可觀測的序列來組成非同步的、基於事件的程式的庫。RxJava本質上是一個實現非同步操作的庫。

專案地址: https://github.com/ReactiveX/RxJava

XTask

XTask是一個擴充性極強的Android任務執行框架。通過它,你可以自由定義和組合任務來實現你想要的功能,尤其適用於處理複雜的業務流程,可靈活新增前置任務或者調整執行順序。

專案的地址: https://github.com/xuexiangjys/XTask

背景

XTask是我基於RxJava的設計思想,並結合實際專案中使用的經驗所創造出來的一個開源專案,其目的就是要代替RxJava在Android中的部分使用場景,提升開發的體驗和可維護性。

相信使用過RxJava的人都知道RxJava有很多硬傷,下面我哦簡單列舉幾個:

  • RxJava最初並不是最先在Android中使用的,所以它一開始就設計的相當的複雜且笨重,一個庫常常能達到3M左右,相對於移動端而已,這還是非常佔應用體積的。
  • 遠遠超過百種的操作符也常常讓使用者摸不著頭腦,稀裡糊塗的使用很容易帶來一些致命性的問題,例如記憶體洩漏等。
  • 由於RxJava是一個基於事件的程式庫,缺少一些關鍵執行任務的日誌資訊,這就導致出了問題後會很難排查出來。

而XTask就是為了能夠解決上述問題而被我開源出來的。

使用對比

首先,RxJava作為一個優秀的開源框架這點是毋庸置疑的,XTask並不是用來代替RxJava的,我沒有這種能力,同樣google也沒有。

但是在某些小且常用的場景下,我們是完全可以替換掉RxJava的使用的。例如如下兩種場景:

  • 複雜序列任務處理
  • 複雜併發任務處理

下面我就通過兩個小例子來給大家呈現它們的不同。

複雜序列任務

相信我們在平時的開發過程中一定會遇到很多複雜的業務流程,而這些流程很多都是一環套著一環,需要一步一步走下去才行,中間有任何錯誤都將停止執行。

下面我就以 [高仿網紅產品] 的案例流程為例,簡單講解如何通過RxJavaXTask去實現這一流程。

案例分析

高仿網紅產品的流程

1.獲取產品資訊 -> 2.查詢可生產的工廠 -> 3.聯絡工廠生產產品 -> 4.送去市場部門評估售價 -> 5.產品上市

實體類設計

這裡主要涉及3個實體類: Product、ProductInfo和ProductFactory。

/**
 * 產品
 */
public class Product {
    /**
     * 產品資訊
     */
    private ProductInfo info;
    /**
     * 產品生產地址
     */
    private String address;
    /**
     * 產品價格
     */
    private String price;
    /**
     * 產品釋出時間
     */
    private String publicTime;
}

/**
 * 產品資訊
 */
public class ProductInfo {
    /**
     * 編號
     */
    private String id;
    /**
     * 品牌
     */
    private String brand;
    /**
     * 質量
     */
    private String quality;
}

/**
 * 產品工廠
 */
public class ProductFactory {
    /**
     * 工廠id
     */
    private String id;
    /**
     * 工廠地址
     */
    private String address;
}

案例實現

業務流程處理

上述共有5個業務流程,我們將其簡化分為以下4個處理器進行處理。

  • 1.獲取產品資訊: GetProductInfoProcessor (productId -> ProductInfo)
  • 2.查詢相關的工廠: SearchFactoryProcessor (ProductInfo -> ProductFactory)
  • 3.評估產品,給出價格: GivePriceProcessor (Product -> Product)
  • 4.產品釋出: PublicProductProcessor (Product -> Product)

業務流程串聯

  • 普通寫法

普通寫法我們直接使用介面回撥的方式, 一層層執行。

AppExecutors.get().singleIO().execute(() -> {
    // 1.獲取產品資訊
    new GetProductInfoProcessor(logger, productId).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<ProductInfo>() {
        @Override
        public void onSuccess(final ProductInfo productInfo) {
            // 2.查詢可生產的工廠
            new SearchFactoryProcessor(logger, productInfo).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<ProductFactory>() {
                @Override
                public void onSuccess(final ProductFactory factory) {
                    // 3.聯絡工廠生產產品
                    log("開始生產產品...");
                    Product product = factory.produce(productInfo);
                    // 4.送去市場部門評估售價
                    new GivePriceProcessor(logger, product).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<Product>() {
                        @Override
                        public void onSuccess(Product product) {
                            // 5.產品上市
                            PublicProductProcessor publicProductProcessor = new PublicProductProcessor(logger, product);
                            publicProductProcessor.setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<Product>() {
                                @Override
                                public void onSuccess(Product product) {
                                    log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
                                    log("仿冒生產網紅產品完成, " + product);
                                }
                            }).process();
                        }
                    }).process();
                }
            }).process();
        }
    }).process();
});
  • RxJava寫法

RxJava中執行序列任務,一般使用map或者flatMap,這裡由於是一對一,所以使用map執行即可。

disposable = Observable.just(productId)
        // 1.獲取產品資訊
        .map(id -> new GetProductInfoProcessor(logger, id).process())
        // 2.查詢可生產的工廠
        .map(productInfo -> new Pair<>(new SearchFactoryProcessor(logger, productInfo).process(), productInfo))
        .map(productPair -> {
            // 3.聯絡工廠生產產品
            log("開始生產產品...");
            Product product = productPair.first.produce(productPair.second);
            // 4.送去市場部門評估售價
            return new GivePriceProcessor(logger, product).process();
        })
        // 5.產品上市
        .map(product -> new PublicProductProcessor(logger, product).process())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(product -> {
            log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
            log("仿冒生產網紅產品完成, " + product);
        });
  • XTask寫法

與普通寫法和RxJava寫法不同的是,XTask是把所有的業務處理器都封裝在了一個一個的Task中,然後按任務的執行順序依次新增對應的Task即可完成。

XTask.getTaskChain()
        .setTaskParam(TaskParam.get(ProductTaskConstants.KEY_PRODUCT_ID, productId))
        // 1.獲取產品資訊
        .addTask(new GetProductInfoTask(logger))
        // 2.查詢可生產的工廠, 3.聯絡工廠生產產品
        .addTask(new SearchFactoryTask(logger))
        // 4.送去市場部門評估售價
        .addTask(new GivePriceTask(logger))
        // 5.產品上市
        .addTask(new PublicProductTask(logger))
        .setTaskChainCallback(new TaskChainCallbackAdapter() {
            @Override
            public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
                log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
                Product product = result.getDataStore().getObject(ProductTaskConstants.KEY_PRODUCT, Product.class);
                log("仿冒生產網紅產品完成, " + product);
            }
        }).start();

案例執行結果

  • 程式執行結果

  • XTask執行日誌一覽


複雜並行任務

除了上面我們討論到的常見序列任務,我們在平時的開發過程中也會遇到一些複雜的並行流程。這些流程往往是單獨可執行的,雖說前後關聯不大,但是又是同時為了某個目標去執行的流程。

下面我就以常見的 [展示商品詳細資訊] 的案例流程為例,簡單講解如何通過RxJavaXTask去實現這一流程。

案例分析

展示商品詳細資訊的流程

  • 1.根據商品的唯一號ID獲取商品簡要資訊
  • 2.獲取商品的詳細資訊:

    • 2.1 獲取商品的生產資訊
    • 2.2 獲取商品的價格資訊
    • 2.3 獲取商品的促銷資訊
    • 2.4 獲取商品的富文字資訊
  • 3.進行商品資訊的展示

其中步驟2中的4個子步驟是可以同時進行,互不影響的併發流程。

實體類設計

這裡主要涉及6個實體類: BriefInfo、Product、FactoryInfo、PriceInfo、PromotionInfo 和 RichInfo。

/**
 * 產品簡要資訊
 */
public class BriefInfo {
    private String id;

    protected String name;

    private String factoryId;

    private String priceId;

    private String promotionId;

    private String richId;
}

/**
 * 產品
 */
public class Product extends BriefInfo {
    /**
     * 生產資訊
     */
    private FactoryInfo factory;
    /**
     * 價格資訊
     */
    private PriceInfo price;
    /**
     * 促銷資訊
     */
    private PromotionInfo promotion;
    /**
     * 富文字資訊
     */
    private RichInfo rich;
}

/**
 * 工廠生產資訊
 */
public class FactoryInfo {
    private String id;
    /**
     * 生產地址
     */
    private String address;
    /**
     * 生產日期
     */
    private String productDate;
    /**
     * 過期日期
     */
    private String expirationDate;
}

/**
 * 價格資訊
 */
public class PriceInfo {
    private String id;
    /**
     * 出廠價
     */
    private float factoryPrice;
    /**
     * 批發價
     */
    private float wholesalePrice;
    /**
     * 零售價
     */
    private float retailPrice;
}

/**
 * 產品促銷資訊
 */
public class PromotionInfo {
    private String id;
    /**
     * 促銷型別
     */
    private int type;
    /**
     * 促銷內容
     */
    private String content;
    /**
     * 生效日期
     */
    private String effectiveDate;
    /**
     * 失效日期
     */
    private String expirationDate;
}

/**
 * 富文字資訊
 */
public class RichInfo {
    private String id;
    /**
     * 描述資訊
     */
    private String description;
    /**
     * 圖片連結
     */
    private String imgUrl;
    /**
     * 視訊連結
     */
    private String videoUrl;
}

案例實現

業務流程處理

上述共有3個大業務流程,4個子業務流程,我們將其簡化分為以下5個處理器進行處理。

  • 1.獲取商品簡要資訊: GetBriefInfoProcessor (productId -> BriefInfo)
  • 2.獲取商品的生產資訊: GetFactoryInfoProcessor (factoryId -> FactoryInfo)
  • 3.獲取商品的價格資訊: GetPriceInfoProcessor (priceId -> PriceInfo)
  • 4.獲取商品的促銷資訊: GetPromotionInfoProcessor (promotionId -> PromotionInfo)
  • 5.獲取商品的富文字資訊: GetRichInfoProcessor (richId -> RichInfo)

業務流程串聯

  • 普通寫法

普通寫法我們需要通過介面回撥+同步鎖的方式, 實現任務的併發和協同。

AppExecutors.get().singleIO().execute(() -> {
    new GetBriefInfoProcessor(logger, productId).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<BriefInfo>() {
        @Override
        public void onSuccess(BriefInfo briefInfo) {
            final Product product = new Product(briefInfo);
            CountDownLatch latch = new CountDownLatch(4);

            // 2.1 獲取商品的生產資訊
            AppExecutors.get().networkIO().execute(() -> {
                new GetFactoryInfoProcessor(logger, product.getFactoryId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<FactoryInfo>() {
                    @Override
                    public void onSuccess(FactoryInfo result) {
                        product.setFactory(result);
                        latch.countDown();
                    }
                }).process();
            });
            // 2.2 獲取商品的價格資訊
            AppExecutors.get().networkIO().execute(() -> {
                new GetPriceInfoProcessor(logger, product.getPriceId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<PriceInfo>() {
                    @Override
                    public void onSuccess(PriceInfo result) {
                        product.setPrice(result);
                        latch.countDown();
                    }
                }).process();
            });
            // 2.3 獲取商品的促銷資訊
            AppExecutors.get().networkIO().execute(() -> {
                new GetPromotionInfoProcessor(logger, product.getPromotionId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<PromotionInfo>() {
                    @Override
                    public void onSuccess(PromotionInfo result) {
                        product.setPromotion(result);
                        latch.countDown();
                    }
                }).process();
            });
            // 2.4 獲取商品的富文字資訊
            AppExecutors.get().networkIO().execute(() -> {
                new GetRichInfoProcessor(logger, product.getRichId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<RichInfo>() {
                    @Override
                    public void onSuccess(RichInfo result) {
                        product.setRich(result);
                        latch.countDown();
                    }
                }).process();
            });
            try {
                latch.await();
                log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
                log("查詢商品資訊完成, " + product);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).process();
});
  • RxJava寫法

RxJava中執行並行任務,一般使用merge或者zip,這裡由於需要協同,所以使用zip對任務流進行合併。

disposable = Observable.just(productId)
        // 1.獲取商品簡要資訊
        .map(id -> new GetBriefInfoProcessor(logger, id).process())
        .map(Product::new)
        .flatMap(product ->
                Observable.zip(
                        // 2.1 獲取商品的生產資訊
                        Observable.fromCallable(() -> new GetFactoryInfoProcessor(logger, product.getFactoryId()).process()).subscribeOn(Schedulers.io()),
                        // 2.2 獲取商品的價格資訊
                        Observable.fromCallable(() -> new GetPriceInfoProcessor(logger, product.getPriceId()).process()).subscribeOn(Schedulers.io()),
                        // 2.3 獲取商品的促銷資訊
                        Observable.fromCallable(() -> new GetPromotionInfoProcessor(logger, product.getPromotionId()).process()).subscribeOn(Schedulers.io()),
                        // 2.4 獲取商品的富文字資訊
                        Observable.fromCallable(() -> new GetRichInfoProcessor(logger, product.getRichId()).process()).subscribeOn(Schedulers.io()), (factoryInfo, priceInfo, promotionInfo, richInfo) -> product.setFactory(factoryInfo)
                                .setPrice(priceInfo)
                                .setPromotion(promotionInfo)
                                .setRich(richInfo)
                )
        )
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(product -> {
            log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
            log("查詢商品資訊完成, " + product);
        });
  • XTask寫法

XTask是把所有的業務處理器都封裝在了一個一個的Task中,然後並行的任務需要通過一個ConcurrentGroupTask(同步組任務)進行包裹,其他按正常執行順序新增Task即可。

XTask.getTaskChain()
        .setTaskParam(TaskParam.get(ProductTaskConstants.KEY_PRODUCT_ID, productId))
        // 1.獲取商品簡要資訊
        .addTask(new GetBriefInfoTask(logger))
        .addTask(XTask.getConcurrentGroupTask(ThreadType.SYNC)
                // 2.1 獲取商品的生產資訊
                .addTask(new GetFactoryInfoTask(logger))
                // 2.2 獲取商品的價格資訊
                .addTask(new GetPriceInfoTask(logger))
                // 2.3 獲取商品的促銷資訊
                .addTask(new GetPromotionInfoTask(logger))
                // 2.4 獲取商品的富文字資訊
                .addTask(new GetRichInfoTask(logger)))
        .setTaskChainCallback(new TaskChainCallbackAdapter() {
            @Override
            public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
                log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
                Product product = result.getDataStore().getObject(ProductTaskConstants.KEY_PRODUCT, Product.class);
                log("查詢商品資訊完成, " + product);
            }
        }).start();

案例執行結果

  • 程式執行結果

  • XTask執行日誌一覽


使用對比總結

從上面的使用對比來看,我們可以簡單歸納總結以下幾點:

程式設計方式

1.RxJava遵循的是函式響應式程式設計的原則,處理過程都是基於資料流的處理。這樣的好處就是,我們可以最直觀有效的感受到資料的變化過程,當然缺點就是太過於細化和具體,不符合物件導向的設計模式原則,增加了日後的程式碼維護成本。當然如果資料的結構相對穩定的話,這樣的程式設計方式還可以接受,但如果資料或者業務頻繁發生變動的話,這樣的程式設計方式簡直就是地獄。

2.XTask遵循的是物件導向的程式設計原則,每個處理過程都對應了一個具體或者抽象的Task。這樣的好處就是,減少了業務和資料結構之間的耦合,同時也減少了各個業務之間的耦合。這樣即使你的資料結構或者業務流程出現大的變動,功能實現的主體也不會產生大的改動,更多的只是每個子業務Task內部的改動和調整,真正實現了高複用低耦合。

總結: 兩種不同的程式設計方式,遵循兩種不同的程式設計原則,無法進行對比。

上手難度

如果你是一名RxJava的開發老鳥的話,這樣就沒什麼可比性了,這裡我只是從初學者的角度來說。

1.RxJava擁有龐大複雜的操作符,上百種操作符一定會讓初學者摸不著頭腦,如果在不熟悉的情況下強行使用,很容易導致誤用而產生很多意想不到的問題(比如記憶體洩漏或者OOM等)。

2.XTask作為專為Android設計的任務執行框架,功能相對單一。沒有複雜的操作符,有的只是“任務鏈、任務、組任務、任務引數和執行結果”這五個組成要素,使用起來相對簡單容易上手。

總結: 整體比較下來,XTask要優於RxJava。

開發效率

1.RxJava的開發效率主要取決於開發者對RxJava操作符使用的熟練程度。越是能夠熟練使用操作符,開發效率就越高,出問題的概率也越小。

2.XTask相對而言就平滑了許多,開發效率和使用的熟練程度關係不大(主要還是上手難度不高)。但是由於每個業務子步驟都需要寫一個Task類,對於那些使用RxJava比較熟練的人而言,效率是明顯會低一些。

總結: 整體比較下來,從長期而言,RxJava要優於XTask。

可維護性

1.RxJava遵循的是函式響應式程式設計的原則,本質上還是程式導向式的程式設計。所有的業務流程都和資料有著比較強的耦合,當資料結構或者業務流程發生變動的時候,必然會影響到主幹程式碼的變動。而且對於初入專案的開發人員接手專案的時候,能看到的往往是區域性業務資料流的變動,無法從全域性的視角去理解專案主體業務,很容易產生區域性修改影響全域性的結果。

2.XTask遵循的是物件導向的程式設計原則,設計之初就嚴格遵循物件導向的設計模式原則。充分減少業務與業務、業務與資料流之間的耦合,這樣即使你的資料結構或者業務流程出現重大的變化,主幹程式碼也不會有很大的變動。而且XTask擁有較強的日誌記錄系統,能夠非常清晰的記錄你當前任務鏈的執行過程和所線上程的資訊(自動的),當任務執行出現問題的時候,便能很快地定位出問題產生的位置。而對於初入專案的開發人員來說,也能快速從任務執行過程的日誌中去理解專案的主體業務。待主體業務流程有了清楚的認知後再去仔細看子業務,這樣才能全方位理解專案的業務,也更利於專案的維護。

總結: 整體比較下來,XTask完勝RxJava。

效能

在效能上,XTask為了實現業務與資料之間的隔離,設計了共享資料的結構,相比較RxJava而言,多了資料拷貝以及資料儲存的過程,所以無論是在時間還是空間上而言,RxJava都是較優於XTask的。

最後

綜合以上的論述,XTask和RxJava各有各的優勢。正如我文章開頭所說: XTask並不是用來代替RxJava的。XTask只是作為RxJava在Android任務執行流程上的一種補充,喜歡的朋友可以關注XTask的專案主頁: https://github.com/xuexiangjys/XTask

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

相關文章