XTaskとRxJavaの使用の比較

序章

RxJava

RxJavaは、JavaVMで監視可能なシーケンスを使用して非同期のイベントベースのプログラムを作成するためのライブラリです。RxJavaは基本的に、非同期操作を実装するライブラリです。

プロジェクトアドレス:github.com/ReactiveX/R…

XTask

XTaskは、拡張性の高いAndroidタスク実行フレームワークです。これにより、タスクを自由に定義および組み合わせて、必要な機能を実現できます。特に、複雑なビジネスプロセスを処理する場合は、事前タスクを柔軟に追加したり、実行順序を調整したりできます。

プロジェクトアドレス:github.com/xuexiangjys…

バックグラウンド

XTaskは、RxJavaの設計アイデアに基づいて私が作成し、実際のプロジェクトで使用された経験と組み合わせたオープンソースプロジェクトです。その目的は、AndroidでのRxJavaの使用シナリオの一部を置き換え、開発経験と保守性を向上させることです。

RxJavaを使用したことのある人は、RxJavaには多くの欠陥があることを知っていると思います。以下にいくつか簡単にリストします。

  • RxJavaはAndroidで最初に使用されたわけではないため、最初から非常に複雑で面倒な設計になっています。ライブラリは約3Mに達することがよくありますが、これはモバイル端末に比べてアプリケーションの量が非常に多いです。

  • 100種類をはるかに超える演算子があり、ユーザーを混乱させることがよくあります。混乱した使用は、メモリリークなどの致命的な問題を簡単に引き起こす可能性があります。

  • RxJavaはイベントベースのライブラリであるため、一部の主要な実行タスクのログ情報が不足しているため、問題が発生した場合のトラブルシューティングが困難になります。

そして、XTaskは、上記の問題を解決するために私によってオープンソース化されました。

コントラストを使用する

まず第一に、RxJavaが優れたオープンソースフレームワークであることは間違いありません。XTaskはRxJavaの代わりには使用されません。私にはこの機能がなく、グーグルもありません。

ただし、一部の小規模で一般的なシナリオでは、RxJavaの使用を完全に置き換えることができます。たとえば、次の2つのシナリオ:

  • 複雑なシリアルタスク処理
  • 複雑な並行タスク処理

以下に、2つの小さな例を通して違いを示します。

複雑なシリアルタスク

通常の開発プロセスでは複雑なビジネスプロセスが多く発生し、それらのプロセスの多くは相互に関連しているため、段階的に進める必要があると思います。途中でエラーが発生した場合は、実行を停止します。 。

下面我就以 [高仿网红产品] 的案例流程为例,简单讲解如何通过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は、Androidタスク実行プロセスにおけるRxJavaの補足にすぎません。XTaskが好きな友達は、XTaskのプロジェクトホームページ(github.com/xuexiangjys…)をフォローできます

私はxuexiangjysです。これは、学習とプログラミングが大好きで、Androidアーキテクチャの研究とオープンソースプロジェクトの経験の共有に取り組んでいるテクノロジーアップマスターです。詳細については、WeChat検索パブリックアカウントへようこそ:[My Android Open Source Journey]

おすすめ

転載: juejin.im/post/7084874838031990797