• 一手遮天 Android kotlin 协程: Flow(异步流,通过 flow 发送和接收数据,flow 的超时处理,取消处理,异常处理,重试处理,指定 flow 阶段的运行协程使其不同于 collect 阶段的运行协程,让 collect 阶段运行到其他协程从而不阻塞当前协程)


    项目地址 https://github.com/webabcd/AndroidDemo
    作者 webabcd

    一手遮天 Android - kotlin 协程: Flow(异步流,通过 flow 发送和接收数据,flow 的超时处理,取消处理,异常处理,重试处理,指定 flow 阶段的运行协程使其不同于 collect 阶段的运行协程,让 collect 阶段运行到其他协程从而不阻塞当前协程)

    示例如下:

    /kotlin/coroutine/Demo6.kt

    /**
     * flow - 异步流
     * 流程上简单来说就是,collect 的时候会去异步执行 flow,然后接收 flow 发送的数据
     *
     * 本例用于演示通过 flow 发送和接收数据,flow 的超时处理,取消处理,异常处理,重试处理,指定 flow 阶段的运行协程使其不同于 collect 阶段的运行协程,让 collect 阶段运行到其他协程从而不阻塞当前协程
     */
    
    package com.webabcd.androiddemo.kotlin.coroutine
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.util.Log
    import com.webabcd.androiddemo.R
    import kotlinx.android.synthetic.main.activity_kotlin_coroutine_demo6.*
    import kotlinx.coroutines.*
    import kotlinx.coroutines.flow.*
    import java.text.SimpleDateFormat
    import java.util.*
    
    class Demo6 : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_kotlin_coroutine_demo6)
    
            // 通过 flow 发送和接收数据
            button1.setOnClickListener {
                sample1()
            }
    
            // flow 的超时处理
            button2.setOnClickListener {
                sample2()
            }
    
            // flow 的取消处理
            button3.setOnClickListener {
                sample3()
            }
    
            // flow 的异常处理
            button4.setOnClickListener {
                sample4()
            }
    
            // flow 的重试处理
            button5.setOnClickListener {
                sample5()
            }
    
            // 指定 flow 阶段的运行协程,使其不同于 collect 阶段的运行协程
            button6.setOnClickListener {
                sample6()
            }
    
            // 让 collect 阶段运行到其他协程,从而不阻塞当前协程
            button7.setOnClickListener {
                sample7()
            }
        }
    
        fun sample1() {
            // flow<T> {} - 实例化一个异步流,T 是 emit() 返回的数据类型,如果能通过 emit() 推导出数据类型的话,则可以省略 T
            val flow = flow<String> {
                appendMessage("start flow")
                for (i in 1..2) {
                    delay(500)
                    // emit() - 发出数据
                    emit("emit: $i")
                }
            }
    
            CoroutineScope(Dispatchers.Default).launch {
                // collect {} - 接收 flow 的 emit() 的数据,并且阻塞直到接收完所有数据
                // 注:
                // 1、实例化 flow 后他并不会自动启动,而是等你调用 collect {} 的时候 flow {} 才会启动
                // 2、在无缓冲区的情况下,发送数据会阻塞,直到发送的数据被接收为止;接收数据会阻塞,直到接收到数据为止
                flow.collect { value -> appendMessage(value) }
                flow.collect { value -> appendMessage(value) }
            }
            // start flow(DefaultDispatcher-worker-1)
            // emit: 1(DefaultDispatcher-worker-1)
            // emit: 2(DefaultDispatcher-worker-1)
            // start flow(DefaultDispatcher-worker-1)
            // emit: 1(DefaultDispatcher-worker-1)
            // emit: 2(DefaultDispatcher-worker-1)
        }
    
        fun sample2() {
            val flow = flow {
                for (i in 1..100) {
                    delay(500)
                    emit("emit: $i")
                }
            }
    
            CoroutineScope(Dispatchers.Default).launch {
                // flow 的超时处理,关于 withTimeout() 和 withTimeoutOrNull() 请参见 Demo2.kt 中的相关说明
                withTimeoutOrNull(1200) {
                    flow.collect { value -> appendMessage(value) }
                }
                appendMessage("done")
            }
            // emit: 1(DefaultDispatcher-worker-1)
            // emit: 2(DefaultDispatcher-worker-1)
            // done(DefaultDispatcher-worker-1)
        }
    
        fun sample3() {
            val flow = flow {
                for (i in 1..100) {
                    try {
                        emit(i)
                    } catch (e: CancellationException) {
                        // 运行到 suspend 函数时,如果发现取消了,则会抛出异常,你的 flow 就退出了
                        // 注意,所有处理程序都会忽略 CancellationException 异常(也就是说抛出此异常并不会导致崩溃),因为它就是用于退出 flow 用的
                        // 本例仅演示用,实际开发中你可以不必捕获 CancellationException 异常
                        throw e
                    }
                }
            }
    
            CoroutineScope(Dispatchers.Default).launch {
                flow.collect { value ->
                    if (value == 3) {
                        // 取消 flow(这个和 Job 的取消是很像的,请参见 Demo2.kt 中的相关说明)
                        cancel()
                    }
                    appendMessage("$value")
                }
                appendMessage("done")
            }
            // 1(DefaultDispatcher-worker-1)
            // 2(DefaultDispatcher-worker-1)
            // 3(DefaultDispatcher-worker-1)
        }
    
        fun sample4() {
            CoroutineScope(Dispatchers.Default).launch {
                // 在 flow {} 外做 try/catch(无论是 collect{} 之前的,还是 collect{} 之中的异常都是能 catch 到的)
                try {
                    flow<Int> {
                        throw Exception("sample1 ex")
                    }.collect { _ ->
    
                    }
                } catch (e: Exception) {
                    appendMessage("catch: $e")
                } finally {
                    appendMessage("finally sample1")
                }
                // catch: java.lang.Exception: sample1 ex(DefaultDispatcher-worker-1)
                // finally sample1(DefaultDispatcher-worker-1)
    
    
                // 在 flow {} 外做 try/catch(无论是 collect{} 之前的,还是 collect{} 之中的异常都是能 catch 到的)
                try {
                    flow<Int> {
                        emit(0)
                    }.collect { _ ->
                        throw Exception("sample2 ex")
                    }
                } catch (e: Exception) {
                    appendMessage("catch: $e")
                } finally {
                    appendMessage("finally sample2")
                }
                // catch: java.lang.Exception: sample2 ex(DefaultDispatcher-worker-1)
                // finally sample2(DefaultDispatcher-worker-1)
    
    
                // 通过 Flow 的 catch {} 方法捕获异常(只能 catch 到 collect{} 之前的异常)
                // Flow 的 onCompletion {} 类似 finally,而且你可以通过 onCompletion {} 中的参数知道是否是因为异常导致了完成
                //   注:onCompletion {} 必须在 catch {} 之前调用
                flow<Int> {
                    throw Exception("sample3 ex")
                }.onCompletion { cause ->
                    // cause 不为 null 则是因为异常导致的完成;cause 为 null 则是正常完成
                    if (cause != null) {
                        appendMessage("finally sample3, cause:$cause")
                    }
                }.catch { e ->
                    appendMessage("catch: $e")
                }.collect { _ ->
    
                }
                // finally sample3, cause:java.lang.Exception: sample3 ex(DefaultDispatcher-worker-1)
                // catch: java.lang.Exception: sample3 ex(DefaultDispatcher-worker-1)
    
    
                // 通过 Flow 的 catch {} 方法无法捕获 collect{} 之中的异常,下面这段代码会崩溃的
                /*
                flow<Int> {
                    emit(1)
                }.catch { e ->
                    appendMessage("catch: $e")
                }.collect { _ ->
                    throw Exception("exception")
                }
                */
            }
        }
    
        fun sample5() {
            CoroutineScope(Dispatchers.Default).launch {
                flow<Unit> {
                    appendMessage("a")
                    throw Exception("ex")
                }.retry (2) { e ->
                    // 在 retry() 时指定重试次数
                    // 发生了指定次数或指定次数内的异常就会走到这里,返回 true 则重新走 flow{} 内的逻辑,返回 false 则不重试
                    appendMessage("retry, exception:$e")
                    e is Exception // 返回值,用于指定是否需要重试
                }.catch { e ->
                    appendMessage("catch: $e")
                }.collect()
                // a(DefaultDispatcher-worker-1)
                // retry, exception:java.lang.Exception: ex(DefaultDispatcher-worker-1)
                // a(DefaultDispatcher-worker-1)
                // retry, exception:java.lang.Exception: ex(DefaultDispatcher-worker-1)
                // a(DefaultDispatcher-worker-1)
                // catch: java.lang.Exception: ex(DefaultDispatcher-worker-1)
    
    
                flow<Unit> {
                    appendMessage("a")
                    throw Exception("ex")
                }.retryWhen { e, retryTimes ->
                    // retryWhen {} 的第 2 个参数代表重试次数,你可以根据此值自行决定是否需要重试,即是否需要重新走 flow{} 内的逻辑
                    // 返回 true 则重试,返回 false 则不重试
                    appendMessage("retryWhen, exception:$e, retryTimes:$retryTimes")
                    if (retryTimes > 1) {
                        false
                    } else {
                        e is Exception
                    }
                }.catch { e ->
                    appendMessage("catch: $e")
                }.collect()
                // a(DefaultDispatcher-worker-1)
                // retryWhen, exception:java.lang.Exception: ex, retryTimes:0(DefaultDispatcher-worker-1)
                // a(DefaultDispatcher-worker-1)
                // retryWhen, exception:java.lang.Exception: ex, retryTimes:1(DefaultDispatcher-worker-1)
                // a(DefaultDispatcher-worker-1)
                // retryWhen, exception:java.lang.Exception: ex, retryTimes:2(DefaultDispatcher-worker-1)
                // catch: java.lang.Exception: ex(DefaultDispatcher-worker-1)
            }
        }
    
        fun sample6() {
            val flow = flow {
                for (i in 1..2) {
                    delay(500)
                    appendMessage("flow: $i, ${currentCoroutineContext()[CoroutineName]?.name}")
                    emit("$i")
                }
            }
    
            CoroutineScope(Dispatchers.Default + CoroutineName("c2")).launch {
                // flowOn() - 用于指定 flow 阶段的运行协程,如果不指定的话,默认 flow 阶段的运行协程和 collect 阶段的运行协程是一样的
                flow.flowOn(Dispatchers.Default + CoroutineName("c1"))
                    .collect { value ->
                        appendMessage("collect $value, ${currentCoroutineContext()[CoroutineName]?.name}")
                    }
            }
            // flow: 1, c1(DefaultDispatcher-worker-1)
            // collect 1, c2(DefaultDispatcher-worker-1)
            // flow: 2, c1(DefaultDispatcher-worker-1)
            // collect 2, c2(DefaultDispatcher-worker-1)
        }
    
        fun sample7() {
            val flow = flow {
                for (i in 1..2) {
                    delay(500)
                    emit(i)
                }
            }
    
            CoroutineScope(Dispatchers.Default).launch {
                // 这是常规写法,调用 collect 的时候就会执行 flow,然后阻塞直到 flow 执行完
                flow.collect { appendMessage("collect: $it") }
                appendMessage("done")
                // collect: 1(DefaultDispatcher-worker-1)
                // collect: 2(DefaultDispatcher-worker-1)
                // done(DefaultDispatcher-worker-1)
    
    
                // 如果想要调用 collect 的时候不阻塞的话,可以像下面这样做
                // launchIn() - 在指定的 CoroutineScope 中启动 collect(这样就不会阻塞当前的协程了)
                // onEach {} - 其会在数据发给 collect {} 之前调用
                //   因为 collect 在 launchIn() 中做了,所以可以在 launchIn() 之前通过 onEach {} 收集数据
                flow
                    .onEach { appendMessage("onEach: $it") }
                    .launchIn(this)
                appendMessage("done")
                // done(DefaultDispatcher-worker-1)
                // onEach: 1(DefaultDispatcher-worker-1)
                // onEach: 2(DefaultDispatcher-worker-1)
            }
        }
    
    
    
        fun appendMessage(message: String) {
            val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.ENGLISH)
            val time = dateFormat.format(Date());
            val threadName = Thread.currentThread().name
    
            CoroutineScope(Dispatchers.Main).launch{
                val log = "$time: $message($threadName)"
                textView1.append(log);
                textView1.append("\n");
    
                Log.d("coroutine", log)
            }
        }
    }
    

    /layout/activity_kotlin_coroutine_demo6.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="通过 flow 发送和接收数据"/>
    
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="flow 的超时处理"/>
    
        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="flow 的取消处理"/>
    
        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="flow 的异常处理"/>
    
        <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="flow 的重试处理"/>
    
        <Button
            android:id="@+id/button6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="指定 flow 阶段的运行协程,使其不同于 collect 阶段的运行协程"/>
    
        <Button
            android:id="@+id/button7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="让 collect 阶段运行到其他协程,从而不阻塞当前协程"/>
    
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    
    

    项目地址 https://github.com/webabcd/AndroidDemo
    作者 webabcd

  • 相关阅读:
    星球居民突破 1800 人!
    测试数据管理
    解决InnoDB: Table mysql/innodb_index_stats has length mismatch in the column name table_name. Please run mysql_upgrade
    Warning: file_get_contents(): open_basedir restriction in effect. File(/proc/uptime) is not within the allowed path(s)解决方法
    Java终止线程的三种方式
    线程中断interrupt
    Linux 开启防火墙 避免非干系人误操作的处理
    Oracle12c 快速启动命令设置
    Docker 运行 Redis Rabbitmq seata-server ftp 的简单办法
    mysql8 CentOS7 简要安装说明
  • 原文地址:https://www.cnblogs.com/webabcd/p/android_kotlin_coroutine_Demo6.html
Copyright © 2020-2023  润新知