乐趣区

关于android:XTask与Kotlin-Coroutine的使用对比

背景

XTask 是我基于 RxJava 的设计思维,并结合实际我的项目中应用的教训所发明进去的一个开源我的项目,其目标就是要代替 RxJava 在 Android 中的局部应用场景,晋升开发的体验和可维护性。

前段时间写过一篇《XTask 与 RxJava 的应用比照》文章,原本只是从比照这两者应用的不同,来让大家更直观全面地理解 XTask,然而有些杠精们就开始在上面评论或者私信说“用 Kotlin 的协程它不香嘛”、“和 kotlin 的协程相比如何”等。

首先我想说的是,协程并没某些人吹得那么神乎其神,说到底它就是个利用框架而已,次要解决的就是在开发过程中的异步执行问题,这点它和 RxJava 是相似的; 其次,协程并不是 kotlin 最先提出的,协程概念的提出最早可追溯至 20 世纪 50 年代,目前支流的语言如 python、C++ 和 go 语言对于协程都有反对和实现; 最初,这世上素来就没有一本万利的框架,任何不谈应用场景的技术吹捧,都是在耍流氓。

不过既然你们想要比照,那我这就安顿上!

不过在比照之前,我还是先来简略介绍这两个框架。

简介

XTask

XTask 是一个拓展性极强的 Android 工作执行框架。通过它,你能够自在定义和组合工作来实现你想要的性能,尤其实用于解决简单的业务流程,可灵便增加前置工作或者调整执行程序。

我的项目的地址:
https://github.com/xuexiangjys/XTask

应用文档:
https://github.com/xuexiangjys/XTask/wiki

Kotlin Coroutine

kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它蕴含本指南中涵盖的很多启用高级协程的原语,包含 launch、async 等等。

协程不是零碎级线程,很多时候协程被称为“轻量级线程”、“微线程”。在 Java 中就相似 Runnable。

我的项目地址:
https://github.com/Kotlin/kotlinx.coroutines

中文文档:
https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html

应用比照

还是和上次一样,这次我还是从上面两个小且罕用的场景来给大家出现它们的不同。

  • 简单串行工作解决
  • 简单并发工作解决

简单串行工作

置信咱们在平时的开发过程中肯定会遇到很多简单的业务流程,而这些流程很多都是一环套着一环,须要一步一步走上来才行,两头有任何谬误都将进行执行。

上面我就以 [高仿网红产品] 的案例流程为例,简略解说如何通过 Kotlin CoroutineXTask去实现这一流程。

案例剖析

高仿网红产品的流程

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 CoroutineXTask去实现这一流程。

案例剖析

展现商品详细信息的流程
  • 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 中执行并行任务,个别应用 flatMapMergezip的组合形式,对工作流进行合并。不过说实在话,与下面 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 上,我的项目主页:
https://github.com/xuexiangjys/KotlinSample

喜爱的敌人能够关注 XTask 的我的项目主页: https://github.com/xuexiangjys/XTask。

我是 xuexiangjys,一枚酷爱学习,喜好编程,致力于 Android 架构钻研以及开源我的项目教训分享的技术 up 主。获取更多资讯,欢送微信搜寻公众号:【我的 Android 开源之旅】

退出移动版