背景
XTask是我基于RxJava的设计思想,并结合实际项目中使用的经验所创造出来的一个开源项目,其目的就是要代替RxJava在Android中的部分使用场景,提升开发的体验和可维护性。
前段时间写过一篇《XTask与RxJava的使用对比》文章,本来只是从对比这两者使用的不同,来让大家更直观全面地了解XTask,然而有些杠精们就开始在下面评论或者私信说“用Kotlin的协程它不香嘛”、“和kotlin的协程相比如何”等。
首先我想说的是,协程并没某些人吹得那么神乎其神,说到底它就是个应用框架而已,主要解决的就是在开发过程中的异步执行问题,这点它和RxJava是类似的;其次,协程并不是kotlin最先提出的,协程概念的提出最早可追溯至20世纪50年代,目前主流的语言如python、C++和go语言对于协程都有支持和实现;最后,这世上从来就没有一本万利的框架,任何不谈使用场景的技术吹捧,都是在耍流氓。
不过既然你们想要对比,那我这就安排上!
不过在对比之前,我还是先来简单介绍这两个框架。
简介
XTask
XTask是一个拓展性极强的Android任务执行框架。通过它,你可以自由定义和组合任务来实现你想要的功能,尤其适用于处理复杂的业务流程,可灵活添加前置任务或者调整执行顺序。
项目的地址:github.com/xuexiangjys…
Kotlin Coroutine
kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启用高级协程的原语,包括 launch、async 等等。
协程不是系统级线程,很多时候协程被称为“轻量级线程”、“微线程”。在Java中就类似Runnable。
中文文档:www.kotlincn.net/docs/refere…
使用对比
还是和上次一样,这次我还是从下面两个小且常用的场景来给大家呈现它们的不同。
- 复杂串行任务处理
- 复杂并发任务处理
复杂串行任务
相信我们在平时的开发过程中一定会遇到很多复杂的业务流程,而这些流程很多都是一环套着一环,需要一步一步走下去才行,中间有任何错误都将停止执行。
下面我就以 [高仿网红产品] 的案例流程为例,简单讲解如何通过Kotlin Coroutine
和XTask
去实现这一流程。
案例分析
高仿网红产品的流程
1.获取产品信息 -> 2.查询可生产的工厂 -> 3.联系工厂生产产品 -> 4.送去市场部门评估售价 -> 5.产品上市
实体类设计
这里主要涉及3个实体类: Product、ProductInfo和ProductFactory。
/** * 产品 */ class Product { /** * 产品信息 */ var info: ProductInfo /** * 产品生产地址 */ var address: String /** * 产品价格 */ var price: String? = null /** * 产品发布时间 */ var publicTime: String? = null } /** * 产品信息 */ class ProductInfo { /** * 编号 */ var id: String /** * 品牌 */ var brand: String? = null /** * 质量 */ var quality: String? = null } /** * 产品工厂 */ class ProductFactory { /** * 工厂id */ var id: String /** * 工厂地址 */ var address: String }
案例实现
业务流程处理
上述共有5个业务流程,我们将其简化分为以下4个处理器进行处理。
- 1.获取产品信息: GetProductInfoProcessor (productId -> ProductInfo)
- 2.查找相关的工厂: SearchFactoryProcessor (ProductInfo -> ProductFactory)
- 3.评估产品,给出价格: GivePriceProcessor (Product -> Product)
- 4.产品发布: PublicProductProcessor (Product -> Product)
业务流程串联
- 普通写法
普通写法我们直接使用接口回调的方式,一层层执行。
AppExecutors.get().singleIO().execute { // 1.获取产品信息 GetProductInfoProcessor(binding?.logger, productId).setProcessorCallback(object : ProcessorCallbackAdapter<ProductInfo?>() { override fun onSuccess(productInfo: ProductInfo?) { // 2.查询可生产的工厂 SearchFactoryProcessor(binding?.logger, productInfo!!).setProcessorCallback( object : ProcessorCallbackAdapter<ProductFactory?>() { override fun onSuccess(factory: ProductFactory?) { // 3.联系工厂生产产品 log("开始生产产品...") val product = factory?.produce(productInfo) // 4.送去市场部门评估售价 GivePriceProcessor(binding?.logger, product!!).setProcessorCallback( object : ProcessorCallbackAdapter<Product?>() { override fun onSuccess(product: Product?) { // 5.产品上市 PublicProductProcessor( binding?.logger, product ).setProcessorCallback(object : ProcessorCallbackAdapter<Product?>() { override fun onSuccess(product: Product?) { log("总共耗时:" + (System.currentTimeMillis() - startTime) + "ms") log("仿冒生产网红产品完成, $product") } }).process() } }).process() } }).process() } }).process()
- Kotlin Coroutine写法
Kotlin Coroutine最大的优势就是可以让异步代码同步化,只需要使用withContext
即可完成。其实这也不是什么新鲜玩意,这就和js、dart语言里的await类似。
mainScope.launch { val productInfo = withContext(Dispatchers.IO) { // 1.获取产品信息 GetProductInfoProcessor(binding?.logger, productId).process() } val factory = withContext(Dispatchers.IO) { // 2.查询可生产的工厂 SearchFactoryProcessor(binding?.logger, productInfo).process() } // 3.联系工厂生产产品 log("开始生产产品...") var product = factory.produce(productInfo) product = withContext(Dispatchers.IO) { // 4.送去市场部门评估售价 GivePriceProcessor(binding?.logger, product).process() // 5.产品上市 PublicProductProcessor(binding?.logger, product).process() } log("总共耗时:" + (System.currentTimeMillis() - startTime) + "ms") log("仿冒生产网红产品完成, $product") }
- Kotlin Flow写法
Kotlin Flow是Kotlin Coroutine生态的一部分,必须依托其才能使用。它是对标RxJava设计出来的,所有的API和RxJava基本相同,在绝大多数场景下可以做到等价替换。
如下代码所示,flowOf就类比just,map更是连名字都一样的,flowIn类比subscribeOn,collect类比subscribe。
mainScope.launch { flowOf(productId) .map { id -> // 1.获取产品信息 GetProductInfoProcessor(binding?.logger, id).process() } .map { productInfo -> // 2.查询可生产的工厂 SearchFactoryProcessor(binding?.logger, productInfo).process() to productInfo } .map { pair -> // 3.联系工厂生产产品 log("开始生产产品...") val product = pair.first.produce(pair.second) // 4.送去市场部门评估售价 GivePriceProcessor(binding?.logger, product).process() }.map { product -> // 5.产品上市 PublicProductProcessor(binding?.logger, product).process() }.flowOn(Dispatchers.IO) .collect { 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(GetProductInfoTask(binding?.logger)) // 2.查询可生产的工厂, 3.联系工厂生产产品 .addTask(SearchFactoryTask(binding?.logger)) // 4.送去市场部门评估售价 .addTask(GivePriceTask(binding?.logger)) // 5.产品上市 .addTask(PublicProductTask(binding?.logger)) .setTaskChainCallback(object : TaskChainCallbackAdapter() { override fun onTaskChainCompleted(engine: ITaskChainEngine, result: ITaskResult) { log("总共耗时:" + (System.currentTimeMillis() - startTime) + "ms") val product = result.dataStore.getObject( ProductTaskConstants.KEY_PRODUCT, Product::class.java ) log("仿冒生产网红产品完成, $product") } }).start()
案例执行结果
- 程序执行结果
- XTask执行日志一览
复杂并行任务
除了上面我们讨论到的常见串行任务,我们在平时的开发过程中也会遇到一些复杂的并行流程。这些流程往往是单独可执行的,虽说前后关联不大,但是又是同时为了某个目标去执行的流程。
下面我就以常见的 [展示商品详细信息] 的案例流程为例,简单讲解如何通过Kotlin Coroutine
和XTask
去实现这一流程。
案例分析
展示商品详细信息的流程
- 1.根据商品的唯一号ID获取商品简要信息
- 2.获取商品的详细信息:
- 2.1 获取商品的生产信息
- 2.2 获取商品的价格信息
- 2.3 获取商品的促销信息
- 2.4 获取商品的富文本信息
- 3.进行商品信息的展示
其中步骤2中的4个子步骤是可以同时进行,互不影响的并发流程。
实体类设计
这里主要涉及6个实体类: BriefInfo、Product、FactoryInfo、PriceInfo、PromotionInfo 和 RichInfo。
/** * 产品简要信息 */ open class BriefInfo { var id: String var name: String? = null var factoryId: String? = null var priceId: String? = null var promotionId: String? = null var richId: String? = null } /** * 产品 */ class Product(briefInfo: BriefInfo) : BriefInfo(briefInfo) { /** * 生产信息 */ var factory: FactoryInfo? = null /** * 价格信息 */ var price: PriceInfo? = null /** * 促销信息 */ var promotion: PromotionInfo? = null /** * 富文本信息 */ var rich: RichInfo? = null } /** * 工厂生产信息 */ class FactoryInfo(var id: String) { /** * 生产地址 */ var address: String? = null /** * 生产日期 */ var productDate: String? = null /** * 过期日期 */ var expirationDate: String? = null } /** * 价格信息 */ class PriceInfo(var id: String) { /** * 出厂价 */ var factoryPrice = 0f /** * 批发价 */ var wholesalePrice = 0f /** * 零售价 */ var retailPrice = 0f } /** * 产品促销信息 */ class PromotionInfo(var id: String) { /** * 促销类型 */ var type = 0 /** * 促销内容 */ var content: String? = null /** * 生效日期 */ var effectiveDate: String? = null /** * 失效日期 */ var expirationDate: String? = null } /** * 富文本信息 */ class RichInfo(var id: String) { /** * 描述信息 */ var description: String? = null /** * 图片链接 */ var imgUrl: String? = null /** * 视频链接 */ var videoUrl: String? = null }
案例实现
业务流程处理
上述共有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 { GetBriefInfoProcessor(binding?.logger, productId).setProcessorCallback(object : AbstractProcessor.ProcessorCallbackAdapter<BriefInfo?>() { override fun onSuccess(briefInfo: BriefInfo?) { val product = Product(briefInfo!!) val latch = CountDownLatch(4) // 2.1 获取商品的生产信息 AppExecutors.get().networkIO().execute { GetFactoryInfoProcessor( binding?.logger, product.factoryId!! ).setProcessorCallback(object : AbstractProcessor.ProcessorCallbackAdapter<FactoryInfo?>() { override fun onSuccess(result: FactoryInfo?) { product.factory = result latch.countDown() } }).process() } // 2.2 获取商品的价格信息 AppExecutors.get().networkIO().execute { GetPriceInfoProcessor( binding?.logger, product.priceId!! ).setProcessorCallback( object : AbstractProcessor.ProcessorCallbackAdapter<PriceInfo?>() { override fun onSuccess(result: PriceInfo?) { product.price = result latch.countDown() } }).process() } // 2.3 获取商品的促销信息 AppExecutors.get().networkIO().execute { GetPromotionInfoProcessor( binding?.logger, product.promotionId!! ).setProcessorCallback(object : AbstractProcessor.ProcessorCallbackAdapter<PromotionInfo?>() { override fun onSuccess(result: PromotionInfo?) { product.promotion = result latch.countDown() } }).process() } // 2.4 获取商品的富文本信息 AppExecutors.get().networkIO().execute { GetRichInfoProcessor( binding?.logger, product.richId!! ).setProcessorCallback( object : AbstractProcessor.ProcessorCallbackAdapter<RichInfo?>() { override fun onSuccess(result: RichInfo?) { product.rich = result latch.countDown() } }).process() } try { latch.await() log("总共耗时:" + (System.currentTimeMillis() - startTime) + "ms") log("查询商品信息完成, $product") } catch (e: InterruptedException) { e.printStackTrace() } } }).process() }
- Kotlin Coroutine写法
Kotlin Coroutine实现并行任务非常简单,只需要使用async
+await
的方式即可完成,而且写出来也非常简洁明了。
mainScope.launch { // 1.获取商品简要信息 val briefInfo = withContext(Dispatchers.IO) { GetBriefInfoProcessor(binding?.logger, productId).process() } val product = Product(briefInfo) // 2.1 获取商品的生产信息 val factory = async(Dispatchers.IO) { GetFactoryInfoProcessor(binding?.logger, product.factoryId!!).process() } // 2.2 获取商品的价格信息 val price = async(Dispatchers.IO) { GetPriceInfoProcessor(binding?.logger, product.factoryId!!).process() } // 2.3 获取商品的促销信息 val promotion = async(Dispatchers.IO) { GetPromotionInfoProcessor(binding?.logger, product.factoryId!!).process() } // 2.4 获取商品的富文本信息 val rich = async(Dispatchers.IO) { GetRichInfoProcessor(binding?.logger, product.factoryId!!).process() } product.factory = factory.await() product.price = price.await() product.promotion = promotion.await() product.rich = rich.await() log("总共耗时:" + (System.currentTimeMillis() - startTime) + "ms") log("查询商品信息完成, $product") }
- Kotlin Flow写法
和RxJava类似,在Kotlin Flow中执行并行任务,一般使用flatMapMerge
和zip
的组合方式,对任务流进行合并。不过说实在话,与上面Kotlin Coroutine实现的方式还是相对繁琐一些的。
mainScope.launch { flowOf(productId) .map { id -> // 1.获取商品简要信息 GetBriefInfoProcessor(binding?.logger, id).process() } .map { briefInfo -> Product(briefInfo) } .flatMapMerge { product -> // 2.1 获取商品的生产信息 flowFactory(product) // 2.2 获取商品的价格信息 .zip(flowPrice(product)) { factoryInfo, priceInfo -> product.apply { factory = factoryInfo price = priceInfo } // 2.3 获取商品的促销信息 }.zip(flowPromotion(product)) { _, promotionInfo -> product.apply { promotion = promotionInfo } // 2.4 获取商品的富文本信息 }.zip(flowRich(product)) { _, richInfo -> product.apply { rich = richInfo } } }.flowOn(Dispatchers.IO) .collect { 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(GetBriefInfoTask(binding?.logger)) .addTask( XTask.getConcurrentGroupTask(ThreadType.SYNC) // 2.1 获取商品的生产信息 .addTask(GetFactoryInfoTask(binding?.logger)) // 2.2 获取商品的价格信息 .addTask(GetPriceInfoTask(binding?.logger)) // 2.3 获取商品的促销信息 .addTask(GetPromotionInfoTask(binding?.logger)) // 2.4 获取商品的富文本信息 .addTask(GetRichInfoTask(binding?.logger)) ) .setTaskChainCallback(object : TaskChainCallbackAdapter() { override fun onTaskChainCompleted(engine: ITaskChainEngine, result: ITaskResult) { log("总共耗时:" + (System.currentTimeMillis() - startTime) + "ms") val product: Product = result.dataStore.getObject( ProductTaskConstants.KEY_PRODUCT, Product::class.java ) log("查询商品信息完成, $product") } }).start()
案例执行结果
- 程序执行结果
- XTask执行日志一览
使用对比总结
从上面的使用对比来看,我们可以简单归纳总结以下几点:
编程方式
1.Kotlin Coroutine遵循的是函数式编程的原则,可以使用阻塞的方式写出非阻塞式的代码,解决并发中常见的回调地狱。消除了并发任务之间的协作的难度,协程可以让我们轻松地写出复杂的并发代码。从这一点来看,Kotlin Coroutine无疑是非常优秀的,因为它可以大大降低异步程序的设计复杂度。
2.XTask遵循的是面向对象的编程原则,每个处理过程都对应了一个具体或者抽象的Task。这样的好处就是,减少了业务和数据结构之间的耦合,同时也减少了各个业务之间的耦合。这样即使你的数据结构或者业务流程出现大的变动,功能实现的主体也不会产生大的改动,更多的只是每个子业务Task内部的改动和调整,真正实现了高复用低耦合。
总结: 如果从编程的简洁性角度而言,无疑Kotlin Coroutine是完胜的,毕竟这是函数式编程的优势。但是如果从编程的耦合性角度而言,那XTask还是有点优势的。所以两种不同的编程方式,遵循两种不同的编程原则,无法对比孰优孰劣。
上手难度
1.如果抛开kotlin Flow不谈的话,Kotlin Coroutine上手还是相对比较容易的。相比于RXJava而言,可能更适合我们Android开发。
2.XTask作为专为Android设计的任务执行框架,功能相对单一。没有复杂的操作符,有的只是“任务链、任务、组任务、任务参数和执行结果”这五个组成要素,使用起来相对简单容易上手。
总结: 整体比较下来,两者基本相同,但是Kotlin Coroutine相关的资料比较多一些,所以可能更容易上手,也更加通用。
开发效率
1.函数式编程最大的优势就是代码简洁写得快。在这点上Kotlin Coroutine无疑是非常优秀的,基本吊打一众异步执行框架。
2.XTask由于每个业务子步骤都需要写一个Task类,相比较而言效率是明显会低一些的。
总结: 整体比较下来,Kotlin Coroutine完胜XTask。
可维护性
1.Kotlin Coroutine遵循的是函数式编程的原则,本质上还是面向过程式的编程。所有的业务流程都和数据有着比较强的耦合,当业务流程发生变动的时候,必然会导致主干代码的变动。而且对于初入项目的开发人员接手项目的时候,过多地暴露了内部实现的细节,很难从全局的视角去理解项目主体业务,很容易产生局部修改影响全局的结果。
2.XTask遵循的是面向对象的编程原则,设计之初就严格遵循面向对象的设计模式原则。
充分减少业务与业务、业务与数据流之间的耦合,这样即使你的数据结构或者业务流程出现重大的变化,主干代码也不会有很大的变动。而且XTask拥有较强的日志记录系统,能够非常清晰的记录你当前任务链的执行过程和所在线程的信息(自动的),当任务执行出现问题的时候,便能很快地定位出问题产生的位置。而对于初入项目的开发人员来说,也能快速从任务执行过程的日志中去理解项目的主体业务。待主体业务流程有了清楚的认知后再去仔细看子业务,这样才能全方位理解项目的业务,也更利于项目的维护。
总结: 整体比较下来,XTask是要优于Kotlin Coroutine的。
性能
在性能上,XTask为了实现业务与数据之间的隔离,设计了共享数据的结构,相比较Kotlin Coroutine而言,多了数据拷贝以及数据存储的过程,所以无论是在时间还是空间上而言,Kotlin Coroutine都是较优于XTask的。
最后
综合以上的论述,Kotlin Coroutine总体上是要优于XTask的。
- 如果你是函数式编程的爱好者,那么一定是选择Kotlin Coroutine; 如果你是面向对象编程的爱好者,那么XTask一定是个不错的选择;
- 如果追求开发的效率,那么可以优先考虑Kotlin Coroutine; 如果站在日后项目的稳定性和可维护性角度,选择XTask一定不会让你失望;
- 如果你使用kotlin进行开发,那么别想了,就选Kotlin Coroutine了; 如果你还是非常钟爱于用Java开发Android,那么一定要考虑一下XTask。
本文章所涉及的源码都已放在github上,项目主页:github.com/xuexiangjys…
喜欢的朋友可以关注XTask的项目主页: github.com/xuexiangjys…。