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.

Some time ago, I wrote an article "Comparison of the Use of XTask and RxJava" . Originally, I only compared the difference between the two to let everyone understand XTask more intuitively and comprehensively. However, some gangsters began to comment or privately message below. "It's not good to use Kotlin's coroutines", "How does it compare with kotlin's coroutines", etc.

First of all, what I want to say is that coroutines are not as magical as some people say. In the final analysis, it is just an application framework. It mainly solves the problem of asynchronous execution in the development process, which is similar to RxJava; Secondly, coroutines were not first proposed by kotlin. The concept of coroutines can be traced back to the 1950s. At present, mainstream languages such as python, C++ and go languages all support and implement coroutines; finally, in this world There has never been a lucrative framework, and any technical touts that do not talk about usage scenarios are playing hooligans.

But since you want to compare, then I will arrange it!

But before comparing, let me briefly introduce these two frameworks.

Introduction

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 :
https://github.com/xuexiangjys/XTask

Use documentation :
https://github.com/xuexiangjys/XTask/wiki

Kotlin Coroutines

kotlinx.coroutines is a feature-rich coroutine library developed by JetBrains. It contains many of the advanced coroutine-enabled primitives covered in this guide, including launch, async, and more.

Coroutines are not system-level threads, and coroutines are often referred to as "lightweight threads" and "micro-threads". Similar to Runnable in Java.

Project address :
https://github.com/Kotlin/kotlinx.coroutines

Chinese document :
https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html

use contrast

Still the same as last time, this time I will show you the differences from the following two small and commonly used scenes.

  • Complex serial task processing
  • Complex concurrent task processing

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.

Next, I will take the case process of [high imitation Internet celebrity product] as an example to briefly explain how to achieve this process through Kotlin Coroutine and XTask .

case analysis

The process of high imitation Internet celebrity products

1. Get product information -> 2. Check the factories that can be produced -> 3. Contact the factory to produce products -> 4. Send it to the marketing department to evaluate the price -> 5. Product launch

Entity class design

There are mainly three entity classes involved here: Product, ProductInfo and 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
}

Case realization

business process processing

There are a total of 5 business processes above, and we simplify them into the following 4 processors for processing.

  • 1. Get product information: GetProductInfoProcessor (productId -> ProductInfo)
  • 2. Find related factories: SearchFactoryProcessor (ProductInfo -> ProductFactory)
  • 3. Evaluate the product and give the price: GivePriceProcessor (Product -> Product)
  • 4. Product release: PublicProductProcessor (Product -> Product)
business process tandem
  • common writing

In ordinary writing, we directly use the method of interface callback, and execute it layer by layer.

 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 writing

The biggest advantage of Kotlin Coroutine is that it can synchronize asynchronous code, just use withContext to complete. In fact, this is nothing new, it is similar to await in js and dart languages.

 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")
}
  • How to write Kotlin Flow

Kotlin Flow is part of the Kotlin Coroutine ecosystem and must be used in order to use it. It is designed to benchmark RxJava, and all APIs are basically the same as RxJava, which can be equivalently replaced in most scenarios.

As shown in the code below, flowOf is analogous to just, and map has the same name, flowIn is analogous to subscribeOn, and collect is analogous to 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 writing

Different from the common writing method and the RxJava writing method, XTask encapsulates all the business processors in one Task, and then adds the corresponding Tasks in sequence according to the execution order of the tasks to complete.

 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()

Case execution result

  • program execution result

  • List of XTask execution logs


complex parallel tasks

In addition to the common serial tasks we discussed above, we also encounter some complex parallel processes in the usual development process. These processes are often executable independently, and although they are not closely related, they are processes that are simultaneously executed for a certain goal.

Next, I will take the common case process of [display product details] as an example, and briefly explain how to achieve this process through Kotlin Coroutine and XTask .

case analysis

Process for displaying product details
  • 1. Obtain the brief information of the product according to the unique ID of the product
  • 2. Get the product details:

    • 2.1 Obtaining the production information of the product
    • 2.2 Get the price information of the product
    • 2.3 Get the promotion information of the product
    • 2.4 Get rich text information of products
  • 3. Display product information

The four sub-steps in step 2 are concurrent processes that can be performed at the same time without affecting each other.

Entity class design

There are mainly 6 entity classes involved here: BriefInfo, Product, FactoryInfo, PriceInfo, PromotionInfo and 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
}

Case realization

business process processing

There are 3 major business processes and 4 sub-business processes in the above, and we simplify them into the following 5 processors for processing.

  • 1. Get product brief information: GetBriefInfoProcessor (productId -> BriefInfo)
  • 2. Get the production information of the product: GetFactoryInfoProcessor (factoryId -> FactoryInfo)
  • 3. Get the price information of the product: GetPriceInfoProcessor (priceId -> PriceInfo)
  • 4. Get the promotion information of the product: GetPromotionInfoProcessor (promotionId -> PromotionInfo)
  • 5. Get the rich text information of the product: GetRichInfoProcessor (richId -> RichInfo)
business process tandem
  • common writing

In the common writing method, we need to realize the concurrency and coordination of tasks through interface callback + synchronization lock.

 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 writing

It is very simple to implement parallel tasks in Kotlin Coroutine. It only needs to use async + await to complete it, and it is very concise and clear to write.

 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")
}
  • How to write Kotlin Flow

Similar to RxJava, to execute parallel tasks in Kotlin Flow, the combination of flatMapMerge and zip is generally used to merge task flows. But to be honest, the implementation of Kotlin Coroutine above is relatively cumbersome.

 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 writing

XTask encapsulates all business processors in one Task, and then parallel tasks need to be wrapped by a ConcurrentGroupTask (synchronous group task), and other tasks can be added in the normal execution order.

 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()

Case execution result

  • program execution result

  • List of XTask execution logs


Use comparison summary

From the above comparison, we can briefly summarize the following points:

programmatically

1. Kotlin Coroutine follows the principles of functional programming. It can write non-blocking code in a blocking way to solve the common callback hell in concurrency. Eliminating the difficulty of cooperation between concurrent tasks, coroutines allow us to easily write complex concurrent code. From this point of view, Kotlin Coroutine is undoubtedly excellent, because it can greatly reduce the design complexity of asynchronous programs.

2. XTask follows the principle of object-oriented programming, and each processing process corresponds to a specific or abstract Task. The advantage of this is that the coupling between the business and the data structure is reduced, and the coupling between the various businesses is also reduced. In this way, even if there are major changes in your data structure or business process, the main body of the function implementation will not have major changes, but only the changes and adjustments within each sub-business task, which truly realizes high reuse and low coupling.

Summary : From the perspective of programming simplicity, Kotlin Coroutine is undoubtedly the winner, after all, this is the advantage of functional programming. But from the perspective of programming coupling, XTask still has some advantages. Therefore, two different programming methods follow two different programming principles, and it is impossible to compare which one is better.

Difficulty getting started

1. If you put aside kotlin Flow, it is relatively easy to get started with Kotlin Coroutine. Compared with RXJava, it may be more suitable for our Android development.

2. XTask, as a task execution framework specially designed for Android, has a relatively single function. There are no complicated operators, there are only five components of "task chain, task, group task, task parameters and execution result", which are relatively simple and easy to use.

Summary : Overall, the two are basically the same, but there are more Kotlin Coroutine-related materials, so it may be easier to use and more general.

Development efficiency

1. The biggest advantage of functional programming is that the code is concise and fast to write. At this point, Kotlin Coroutine is undoubtedly very good, and basically hangs a lot of asynchronous execution frameworks.

2. XTask needs to write a Task class for each business sub-step, and the efficiency is obviously lower in comparison.

Summary : Overall, Kotlin Coroutine beats XTask.

maintainability

1. Kotlin Coroutine follows the principles of functional programming and is essentially procedural-oriented programming. All business processes are strongly coupled with data. When the business process changes, it will inevitably lead to changes in the main code. Moreover, when developers who are new to the project take over the project, they expose too many details of the internal implementation. It is difficult to understand the main business of the project from a global perspective, and it is easy to produce local changes that affect the overall results.

2. XTask follows the principle of object-oriented programming, and strictly follows the principle of object-oriented design pattern at the beginning of the design. Fully reduce the coupling between business and business, business and data flow, so that even if your data structure or business process changes significantly, the main code will not change greatly. Moreover, XTask has a strong logging system, which can clearly record the execution process of your current task chain and the information of the thread (automatic). When there is a problem with the task execution, it can quickly locate the problem. Location. For developers who are new to the project, they can also quickly understand the main business of the project from the log of the task execution process. After you have a clear understanding of the main business process, you can look at the sub-business carefully, so that you can fully understand the business of the project, and it is more conducive to the maintenance of the project.

Summary : Overall, XTask is better than Kotlin Coroutine.

performance

In terms of performance, XTask has designed a shared data structure in order to achieve the isolation between business and data. Compared with Kotlin Coroutine, there are more data copying and data storage processes, so both in terms of time and space, Kotlin Coroutine is better than XTask.


At last

Based on the above discussion, Kotlin Coroutine is generally better than XTask.

  • If you are a fan of functional programming, then you must choose Kotlin Coroutine; if you are a fan of object-oriented programming, then XTask must be a good choice;
  • If you pursue the efficiency of development, you can give priority to Kotlin Coroutine; if you stand in the perspective of future project stability and maintainability, choosing XTask will not disappoint you;
  • If you use kotlin for development, then don't think about it, choose Kotlin Coroutine; if you still love to develop Android in Java, then you must consider XTask.

The source code involved in this article has been placed on github, the project home page:
https://github.com/xuexiangjys/KotlinSample

Friends who like it can follow the project homepage of XTask: https://github.com/xuexiangjys/XTask .

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]

xuexiangjys
357 声望4.1k 粉丝