Comparison of the use of XTask and RxJava

Introduction

RxJava

RxJava is a library for composing asynchronous, event-based programs using observable sequences on the Java VM. RxJava is essentially a library that implements asynchronous operations.

Project address: github.com/ReactiveX/R…

XTask

XTask is a highly extensible Android task execution framework. Through it, you can freely define and combine tasks to achieve the functions you want, especially for dealing with complex business processes, you can flexibly add pre-tasks or adjust the execution order.

Project address: github.com/xuexiangjys…

background

XTask is an open source project created by me based on the design idea of ​​RxJava and combined with the experience used in actual projects. Its purpose is to replace some of the usage scenarios of RxJava in Android and improve the development experience and maintainability.

I believe that people who have used RxJava know that RxJava has many flaws. I will briefly list a few below:

  • RxJava was not first used in Android, so it was designed to be quite complex and cumbersome from the beginning. A library can often reach about 3M, which is still very large in application volume compared to mobile terminals.

  • There are far more than 100 kinds of operators, and users are often confused, and confused use can easily lead to some fatal problems, such as memory leaks.

  • Since RxJava is an event-based library, it lacks log information for some key execution tasks, which makes it difficult to troubleshoot when something goes wrong.

And XTask was open sourced by me in order to solve the above problems.

use contrast

First of all, there is no doubt that RxJava is an excellent open source framework. XTask is not used to replace RxJava. I do not have this ability, and neither does google.

But in some small and common scenarios, we can completely replace the use of RxJava. For example the following two scenarios:

  • Complex serial task processing
  • Complex concurrent task processing

Below I will show you the difference through two small examples.

complex serial tasks

I believe that we will encounter many complex business processes in the usual development process, and many of these processes are linked to each other, and we need to go step by step. If there is any error in the middle, the execution will be stopped.

下面我就以 [高仿网红产品] 的案例流程为例,简单讲解如何通过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的。

最后

Based on the above discussion, XTask and RxJava each have their own advantages. As I said at the beginning of the article: XTask is not intended to replace RxJava. XTask is just a supplement to RxJava in the Android task execution process. Friends who like it can follow the XTask project homepage: github.com/xuexiangjys… .

I am xuexiangjys, a technology up master who loves learning, programming, and is committed to Android architecture research and open source project experience sharing. For more information, welcome to WeChat search public account: [My Android Open Source Journey]

Guess you like

Origin juejin.im/post/7084874838031990797