简介
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的使用的。例如如下两种场景:
- 复杂串行任务处理
- 复杂并发任务处理
下面我就通过两个小例子来给大家呈现它们的不同。
复杂串行任务
相信我们在平时的开发过程中一定会遇到很多复杂的业务流程,而这些流程很多都是一环套着一环,需要一步一步走下去才行,中间有任何错误都将停止执行。
下面我就以 [高仿网红产品] 的案例流程为例,简单讲解如何通过RxJava
和XTask
去实现这一流程。
案例分析
高仿网红产品的流程
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() {
@Override
public void onSuccess(final ProductInfo productInfo) {
// 2.查询可生产的工厂
new SearchFactoryProcessor(logger, productInfo).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter() {
@Override
public void onSuccess(final ProductFactory factory) {
// 3.联系工厂生产产品
log("开始生产产品...");
Product product = factory.produce(productInfo);
// 4.送去市场部门评估售价
new GivePriceProcessor(logger, product).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter() {
@Override
public void onSuccess(Product product) {
// 5.产品上市
PublicProductProcessor publicProductProcessor = new PublicProductProcessor(logger, product);
publicProductProcessor.setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter() {
@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执行日志一览
复杂并行任务
除了上面我们讨论到的常见串行任务,我们在平时的开发过程中也会遇到一些复杂的并行流程。这些流程往往是单独可执行的,虽说前后关联不大,但是又是同时为了某个目标去执行的流程。
下面我就以常见的 [展示商品详细信息] 的案例流程为例,简单讲解如何通过RxJava
和XTask
去实现这一流程。
案例分析
展示商品详细信息的流程
- 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() {
@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() {
@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() {
@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() {
@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() {
@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开源之旅】