flow 是啥

按顺序发出值并正常完成或异常完成的冷流异步数据流

flow咋用?

flow 是kotlin coroutines 库里面的类,所以使用 flow 之前,要确保添加了协程依赖

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"

引入依赖后,我们写一段代码:

       flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.collect {
            //接收结果
            Log.d("flow-", "value $it")
        }

如果你这样写就会报错
Kotlin实战指南二十:flow_zhaoyanjun
意思是:collect 方法是 suspend 修饰的挂起函数,只能在协程里,或者其他挂起函数中使用。我们来修改一下:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                emit(1)  //发射数字 1
                emit(2)  //发射数字 2
            }.collect {
                //接收结果
                Log.d("flow-", "value: $it")
            }
        }
    }
}

输出结果:

D/flow-: value: 1
D/flow-: value: 2
创建flow
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            val flow = flow {
                emit(1)
                emit(2)
            }

            val flow2 = flowOf(1, 2.3)
            val flow3 = mutableListOf(1, 2, 3, 4, 5).asFlow()
            val flow4 = (1..4).asFlow()
        }
    }
}
map 操作符

例子1

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                emit(1)  //发射数字 1
                emit(2)  //发射数字 2
            }.map {
                "map $it"
            }.collect {
                //接收结果
                Log.d("flow-", "value: $it")
            }
        }
    }
}

输出结果:

D/flow-: value: map 1
D/flow-: value: map 2

例子2

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow: Flow<String> = flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.map {
            "map $it"
        }

        GlobalScope.launch {
            flow.collect {
                //接收结果
                Log.d("flow-", "value: $it")
            }
        }
    }
}
输出结果:
```java
D/flow-: value: map 1
D/flow-: value: map 2

在接收数据的时候,我们用了 collect 方法,collect 英文含义就是收集的意思

catch 捕获上游出现的异常

当 flow 流操作中发生异常的情况时,程序会发生崩溃:

例子1:不捕获异常

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow: Flow<String> = flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.map {
            0 / 0  //人为制造异常
            "map $it"
        }

        GlobalScope.launch {
            flow
                .collect {
                    //接收结果
                    Log.d("flow-", "value: $it")
                }
        }
    }
}

输出结果:程序发生崩溃
Kotlin实战指南二十:flow_赵彦军_02

例子2:捕获异常

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow: Flow<String> = flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.map {
            0 / 0  //人为制造异常
            "map $it"
        }

        GlobalScope.launch {
            flow
                .catch {
                    //捕获异常
                    Log.d("flow-", "value: ${it.message}")
                }.collect {
                    //接收结果
                    Log.d("flow-", "value: $it")
                }
        }
    }
}

输出结果:

D/flow-: exception: divide by zero

可以看到程序可以正常运行,我们也正常捕获了异常

取消flow

Flow创建后并不返回可以cancel的句柄,但是一个flow的collect是suspend的,所以可以像取消一个suspend方法一样取消flow的collection。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val job = GlobalScope.launch {
            flow {
                emit(1)
                kotlinx.coroutines.delay(3000)
                emit(2)
                kotlinx.coroutines.delay(3000)
                emit(3)
            }
                .collect {
                    //接收结果
                    Log.d("flow-", "value: $it")
                }
        }

        findViewById<Button>(R.id.cancel).setOnClickListener {
            job.cancel()
        }
    }
}

collectIndexed

输出带有索引的结果
Kotlin实战指南二十:flow_flow_03

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                Log.d("flow-", "init")
                emit(1)
                emit(2)
            }.collectIndexed { index, value ->
                Log.d("flow-", "onEach $index $value")
            }
        }
    }
}

//输出结果
D/flow-: init
D/flow-: onEach 0 1
D/flow-: onEach 1 2
distinctUntilChanged 过滤重复的值

如果生产的值和上个发送的值相同,值就会被过滤掉

flow {
    emit(1)
    emit(1)
    emit(2)
    emit(2)
    emit(3)
    emit(4)
}.distinctUntilChanged()

// 结果:1 2 3 4
// 解释:
// 第一个1被释放
// 第二个1由于和第一个1相同,被过滤掉
// 第一个2被释放
// 第二个2由于和第一个2相同,被过滤掉
// 第一个3被释放
// 第一个4被释放

可以传参(old: T, new: T) -> Boolean,进行自定义的比较

private class Person(val age: Int, val name: String)

flow {
    emit(Person(20, "张三"))
    emit(Person(21, "李四"))
    emit(Person(21, "王五"))
    emit(Person(22, "赵六"))
}.distinctUntilChanged{old, new -> old.age == new.age }
.collect{ value -> println(value.name) }
    
// 结果:张三 李四 赵六
// 解释:本例子定义如果年龄相同就认为是相同的值,所以王五被过滤掉了

可以用 distinctUntilChangedBy转换成年龄进行对比

flow {
    emit(Person(20, "张三"))
    emit(Person(21, "李四"))
    emit(Person(21, "王五"))
    emit(Person(22, "赵六"))
}.distinctUntilChangedBy { person -> person.age }

// 结果:张三 李四 赵六
transform

对每个值进行转换,用一个新的 FlowCollector 发射数据

Kotlin实战指南二十:flow_kotlin flow_04

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                emit(1)
                emit(2)
                emit(3)
                emit(4)
                emit(5)
            }
                .transform {
                    if (it % 2 == 0) {
                        emit("value-$it")
                    }
                }
                .collect {
                    Log.d("flow-", "collect $it")
                }
        }
    }
}

//输出结果
D/flow-: collect value-2
D/flow-: collect value-4
filter 过滤
val list = mutableListOf(1, 2, 3, 4, 5)

val job = GlobalScope.launch {
    list.asFlow()
        .filter {
           it > 3
         }
        .collect {
           Log.d("flow-", "value: $it")
        }
}

输出结果:

 D/flow-: value: 4
 D/flow-: value: 5

类似的还有 filterNot 反向过滤,这里就不举例子了

flowOn 数据发射的线程

可以切换CoroutineContext

说明:flowOn只影响该运算符之前的CoroutineContext,对它之后的CoroutineContext没有任何影响
我们先看一下默认情况下的线程问题

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            Log.d("flow-", "init->thread:${Thread.currentThread().name}")
            emit(1)
            emit(2)
        }

        val job = GlobalScope.launch {
            flow
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果

D/flow-: init->thread:DefaultDispatcher-worker-1
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 2

我们可以看到默认情况下,flow 数据发射的线程就是当前协程所在的线程。

我们可以用 flowOn 方法指定数据发射的线程


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            Log.d("flow-", "init->thread:${Thread.currentThread().name}")
            emit(1)
            emit(2)
        }

        GlobalScope.launch {
            flow
                .flowOn(Dispatchers.Main) //指定数数据发射的线程为主线程
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果

D/flow-: init->thread:main
D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 2

可以看到数据发射的线程已经被切换到了主线程

多个操作符的情况下,如下

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flowDemo()
        }
    }

    suspend fun flowDemo() {
        flow {
            Log.d("flow-", "init ${Thread.currentThread().name}")
            emit(1)
        }.map {
            Log.d("flow-", "map ${Thread.currentThread().name}")
            it
        }.flowOn(Dispatchers.Main)
            .collect {
                Log.d("flow-", "collect ${Thread.currentThread().name} value:$it")
            }
    }
}
D/flow-: init main
D/flow-: map main
D/flow-: collect DefaultDispatcher-worker-1 value:1
launchIn

scope.launch { flow.collect() }的缩写, 代表在某个协程上下文环境中去接收释放的值
Kotlin实战指南二十:flow_zhaoyanjun_05

例子:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            val flow = flow {
                Log.d("flow-", "init")
                emit(1)
                emit(2)
            }.onEach {
                Log.d("flow-", "onEach $it")
            }
            flow.launchIn(this)
        }
    }
}

输出结果:

D/flow-: init
D/flow-: onEach 1
D/flow-: onEach 2
conflate()

如果值的生产速度大于值的消耗速度,就忽略掉中间未来得及处理的值,只处理最新的值。

val flow1 = flow {
    delay(2000)
    emit(1)
    delay(2000)
    emit(2)
    delay(2000)
    emit(3)
    delay(2000)
    emit(4)
}.conflate()

flow1.collect { value ->
    println(value)
    delay(5000)
}

// 结果: 1 3 4
// 解释:
// 2000毫秒后生产了1这个值,交由collect执行,花费了5000毫秒,当1这个值执行collect完成后已经经过了7000毫秒。
// 这7000毫秒中,生产了2,但是collect还没执行完成又生产了3,所以7000毫秒以后会直接执行3的collect方法,忽略了2这个值
// collect执行完3后,还有一个4,继续执行。

withIndex

将值封装成IndexedValue对象
Kotlin实战指南二十:flow_zhaoyanjun_06
Kotlin实战指南二十:flow_kotlin flow_07

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.withIndex()

// 结果:
// I/System.out: IndexedValue(index=0, value=1)
// I/System.out: IndexedValue(index=1, value=2)
// I/System.out: IndexedValue(index=2, value=3)
// I/System.out: IndexedValue(index=3, value=4)
onEach

每个值释放的时候可以执行的一段代码

Kotlin实战指南二十:flow_赵彦军_08
onEach 函数,执行一段代码后,再释放值

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.onEach { println("接收到$it") }

// 结果:
I/System.out: 接收到1
I/System.out: 1
I/System.out: 接收到2
I/System.out: 2
I/System.out: 接收到3
I/System.out: 3
I/System.out: 接收到4
I/System.out: 4

onStart 在数据发射之前触发
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            Log.d("flow-", "init->thread:${Thread.currentThread().name}")
            emit(1)
            emit(2)
            emit(3)
        }

        GlobalScope.launch {
            flow
                .onStart {
                    Log.d("flow-", "onStart->thread:${Thread.currentThread().name}")
                }
                .flowOn(Dispatchers.Main) //指定数数据发射的线程为主线程
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果:

D/flow-: onStart->thread:main
D/flow-: init->thread:main
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 2
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 3

结论:
onStart 方法在数据发射之前调用,onStart 所在的线程是数据产生的线程。

onCompletion
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            val flow = flow {
                Log.d("flow-", "init ${Thread.currentThread().name}")
                emit(1)
                emit(2)
            }

            flow
                .onStart {
                    Log.d("flow-", "onStart ${Thread.currentThread().name}")
                }
                .onCompletion {
                    Log.d("flow-", "onCompletion ${Thread.currentThread().name}")
                }.collect {
                    Log.d("flow-", "collect ${Thread.currentThread().name} value:$it")
                }
        }
    }
}

输出结果:

D/flow-: onStart DefaultDispatcher-worker-1
D/flow-: init DefaultDispatcher-worker-1
D/flow-: collect DefaultDispatcher-worker-1 value:1
D/flow-: collect DefaultDispatcher-worker-1 value:2
D/flow-: onCompletion DefaultDispatcher-worker-1
drop(n) 忽略最开始释放的值
flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.drop(2)

// 结果:3 4
// 解释:
// 最开始释放的两个值(1,2)被忽略了
dropWhile

判断第一个值如果满足(T) -> Boolean这个条件就忽略

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.dropWhile {
    it % 2 == 0
}

// 结果:1 2 3 4
// 解释:
// 第一个值不是偶数,所以1被释放

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.dropWhile {
    it % 2 != 0
}

// 结果:2 3 4
// 解释:
// 第一个值是偶数,所以1被忽略

take 取指定数量的数据
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            emit(1)
            emit(2)
            emit(3)
        }

        GlobalScope.launch {
            flow
                .take(2)
                .flowOn(Dispatchers.Main) //指定数数据发射的线程为主线程
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果:

D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 2
takeWhile

只会释放第一个值,但也不是必须释放,要看条件
Kotlin实战指南二十:flow_flow_09

判断第一个值如果满足(T) -> Boolean这个条件就释放

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.takeWhile { it%2 != 0 }

// 结果:1
// 解释:
// 第一个值满足是奇数条件

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.takeWhile { it%2 == 0 }

// 结果:无
// 解释:
// 第一个值不满足是奇数条件
实现一个定时器功能
GlobalScope.launch {
      val flow = flow {
          while (true) {
              emit(0)
              //每隔一秒产生一个数据
              delay(1000)
         }
      }
      flow.collect {
          UtilLog.d(TAG, "定时任务..")
           
      }
}