美文网首页
使用kotlin协程来代替RxJava

使用kotlin协程来代替RxJava

作者: 克罗克达尔 | 来源:发表于2021-08-17 15:40 被阅读0次

我们的项目可能在以下场景中用到了RxJava:

  • 网络请求(Retrofit)
  • 并发任务
  • RxView
  • 权限请求(RxPermission)
  • startActivityForResult(RxImagePicker、RxFilePicker)

我们接下来会讲如何使用kotlin的协程来代替RxJava

网络请求

我们以玩安卓首页banner的接口为例
地址
http service的写法如下,注意返回的类型是Observable

 @GET("banner/json")
 fun banner(): Observable<BaseJsonListResponse<Banner>>

创建service的时候指定AdapterFactory为RxJava2CallAdapterFactory

 val rxJavaService: RxJavaService by lazy {
        Retrofit.Builder()
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl(baseUrl)
            .build().create(RxJavaService::class.java)
    }

调用方法如下

val disposable = Instance.rxJavaService
                .banner()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                           showResult(it, "使用RxJava获取banner")
                }
            compositeDisposable.add(disposable)

而当我们使用协程的时候,http service的写法就变成了下面这种

@GET("banner/json")
    suspend fun banner(): BaseJsonListResponse<Banner>

方法加上suspend关键字
创建service的时候不需要再指定AdapterFactory

 val coroutineService: CoroutineService by lazy {
        Retrofit.Builder()
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(baseUrl)
            .build().create(CoroutineService::class.java)
    }

调用方法如下

lifecycleScope.launch {
                val result = Instance.coroutineService.banner()
                showResult(result, "使用协程获取banner")
            }

看着比RxJava更加的直观,毕竟协程就是让你以写同步代码的方式写异步代码

并发任务

在实际的业务场景中,我们可能会遇到要同时并发进行耗时操作的情况,比如上传图片的时候并发压缩所有照片,而不是一个一个顺序压缩
比如我们压缩图片的方法是compressBitmap

private fun compressBitmap(value: Long): Long {
        log("在 ${Thread.currentThread().name} 线程中处理")
        Thread.sleep(value)
        return value
    }

我们使用Thread.sleep来模拟耗时操作
现在我们来并发对一个图片数组进行压缩

 val bitmapList = listOf<Long>(3000, 3000, 4000)

 binding.taskWithRxjava.setOnClickListener {
            val start = System.currentTimeMillis()

            Observable.zip(bitmapList.map {
                Observable
                    .just(it)
                    .observeOn(Schedulers.io())
                    .map { value ->
                        compressBitmap(value)
                    }
            }) {

                it
            }
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {

                    AlertDialog.Builder(this)
                        .setTitle("获取到结果 任务耗时 ${System.currentTimeMillis() - start}毫秒")
                        .setMessage(it.toString())
                        .setPositiveButton("确定", null)
                        .show()
                }.addTo(compositeDisposable)
        }

我们同时压缩3张图片,大概4秒后,我们可以看到一个提示成功的弹窗,再看log日志,可以看出三个压缩任务在不同的线程执行

2021-08-17 09:57:30.463 11705-11751/com.ke.rxjava_coroutine D/TAG: 在 RxCachedThreadScheduler-3 线程中处理
2021-08-17 09:57:30.463 11705-11749/com.ke.rxjava_coroutine D/TAG: 在 RxCachedThreadScheduler-1 线程中处理
2021-08-17 09:57:30.463 11705-11750/com.ke.rxjava_coroutine D/TAG: 在 RxCachedThreadScheduler-2 线程中处理

现在我们使用协程来进行并发耗时操作
代码如下

 lifecycleScope.launch {
                val start = System.currentTimeMillis()
                val result = bitmapList.map {
                    GlobalScope.async {
                        compress(it)
                    }
                }
                    .map {
                        it.await()
                    }

                AlertDialog.Builder(this@MainActivity)
                    .setTitle("协程并发 获取到结果 任务耗时 ${System.currentTimeMillis() - start}毫秒")
                    .setMessage(result.toString())
                    .setPositiveButton("确定", null)
                    .show()
            }

同样,大约4秒后,我们看到弹窗提示处理完成,同时通过日志也能看出三个任务在不同线程中进行

2021-08-17 10:09:56.081 13978-14300/com.ke.rxjava_coroutine D/TAG: 在 DefaultDispatcher-worker-1 线程中处理
2021-08-17 10:09:56.081 13978-14301/com.ke.rxjava_coroutine D/TAG: 在 DefaultDispatcher-worker-2 线程中处理
2021-08-17 10:09:56.082 13978-14302/com.ke.rxjava_coroutine D/TAG: 在 DefaultDispatcher-worker-3 线程中处理

RxView

RxView把View的事件(点击,选中状态改变,文字改变)都变成了Observable,我们可以以处理流的形式处理View的事件,这里主要讨论两个场景

  • 防止重复点击
    使用RxJava处理方式如下
binding.clickWithRxjava.clicks()
            .throttleFirst(5, java.util.concurrent.TimeUnit.SECONDS)
            .subscribe {
                log("点击了按钮")
            }

加了throttleFirst后,订阅到的点击事件在不停点击按钮后每5秒只会触发一次
使用flow可以达到类似的效果

@ExperimentalCoroutinesApi
fun View.clickEventFlow(): Flow<Unit> {

    var lastClickTime = System.currentTimeMillis()

    return callbackFlow {
        setOnClickListener {
            if (System.currentTimeMillis() - lastClickTime > 5000) {
                lastClickTime = System.currentTimeMillis()
                offer(Unit)
            }
        }
        awaitClose {
            setOnClickListener(null)
        }
    }
}
  • 优化搜索
    大致意思是如果用户输入的是123456789,我们希望只发出一次123456789网络请求,而不是输入一个1发一次网络请求,输入一个2发出12的网络请求
    使用RxView处理方式如下
 binding.etContent.textChanges().debounce(1, TimeUnit.SECONDS)
            .subscribe {
                log("输入框信息发生变化 来自RxJava $it")
            }

在停止输入一秒后才发起网络请求,避免不必要的网络请求
我们也可以使用Flow来实现这个功能。首先把TextView的文字变化转换成Flow


@ExperimentalCoroutinesApi
fun TextView.textChangeFlow(): Flow<CharSequence> {

    return callbackFlow {

        val watcher = object : TextWatcher {
            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            }

            override fun onTextChanged(content: CharSequence?, p1: Int, p2: Int, p3: Int) {
                if (content != null) {
                    offer(content)
                }
            }

            override fun afterTextChanged(p0: Editable?) {
            }

        }

        addTextChangedListener(watcher)
        awaitClose {
            removeTextChangedListener(watcher)
        }

    }
}

然后同样需要加上debounce

 lifecycleScope.launch {
            binding.etContent.textChangeFlow().debounce(1000)
                .collect {
                    log("输入框信息发生变化 来自Flow $it")

                }
        }

权限请求

不使用任何库请求权限的方式如下:

ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 422
            )

onRequestPermissionsResult回调方法里判断用户是否授权

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if(requestCode == 422){
            
        }
    }

使用RxPermissions请求就看起来方便很多,代码如下

 RxPermissions(this)
                .request(Manifest.permission.ACCESS_FINE_LOCATION)
                .subscribe {
                    log("使用RxJava请求权限结果 $it")
                }

RxPermissions的原理是创建一个新的Fragment去请求权限,我们也可以利用这点建立一个不可见的Fragment并用它去请求权限和处理回调。
Github上已经有一个现成的框架,地址
不用协程,仅用Kotlin的方法参数就可以实现。

startActivityForResult(RxImagePicker、RxFilePicker)

和上面说的一样,创建一个不可见的fragment来启动activity和处理回调

结尾

现在,可以放弃RxJava了吗

相关文章

网友评论

      本文标题:使用kotlin协程来代替RxJava

      本文链接:https://www.haomeiwen.com/subject/pcambltx.html