一.Flow是什么?
官方文档给予了一句话简单的介绍:
Flow — cold asynchronous stream with flow builder and comprehensive operator set (filter, map, etc);
翻译一下:
具有流构建器和综合运算符集(过滤器、映射等)的冷异步流;
为了便于理解我们先简单看一下它的结构:
他的Rxjava类似都是上游产生数据,操作符,下游接受数据这种基于数据流驱动的结构。为了便于理解和上手,我做了一些flow和rxjava的使用类比。但需要注意flow是协程中的库,只能用于协程环境。
再熟悉一下Rxjava的定义:
RxJava:a library for composing asynchronous and event-based programs using observable sequences for the Java VM
翻译:RxJava是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库
总结:RxJava是一个 基于事件流、实现异步等操作的库
[if !supportLists]二.[endif]初识flow:
2.1简单创建
第一步:创建一个flow
val flow1 = flow {emit(1)}
第二步:收集数据流
flow1.collect {print(it)}
类比Rxjava
第一步:创建一个被观察者
val observable = Observable.create
emitter.onNext(1)
}
第二步:观察者订阅被观察者
observable.subscribe(Consumer {
print(it)
});
2.2其他创建方式:
第一种
[if !supportLists]1. [endif] flowOf(1,2,3,4,5)
[if !supportLists]2. [endif] .onEach {
[if !supportLists]3. [endif] delay(100)
[if !supportLists]4. [endif] }
[if !supportLists]5. [endif] .collect{
[if !supportLists]6. [endif] println(it)
[if !supportLists]7. [endif] }
类比rxjava
val observable: Observable<*> = Observable.just("A", "B", "C")
第二种
listOf(1, 2, 3, 4, 5).asFlow()
类比rxjava
val observable1: Observable
2.3线程切换
[if !supportLists]1. [endif] flow {
[if !supportLists]2. [endif] for (i in 1..5) {
[if !supportLists]3. [endif] delay(100)
[if !supportLists]4. [endif] emit(i)
[if !supportLists]5. [endif] }
[if !supportLists]6. [endif] }.map {
[if !supportLists]7. [endif] it * it
[if !supportLists]8. [endif] }.flowOn(Dispatchers.IO)
[if !supportLists]9. [endif] .collect {
[if !supportLists]10. [endif] println(it)
[if !supportLists]11. [endif] }
类比rxjava
[if !supportLists]1. [endif]Observable.just("A", "B", "C")
[if !supportLists]2. [endif] .subscribeOn(Schedulers.io())
[if !supportLists]3. [endif] .observeOn(AndroidSchedulers.mainThread())
[if !supportLists]4. [endif] .subscribe(object : Observer
[if !supportLists]5. [endif] override fun onSubscribe(d: Disposable) {
[if !supportLists]6. [endif] }
[if !supportLists]7. [endif] override fun onNext(t: Any) {
[if !supportLists]8. [endif] print(t)
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif] override fun onError(e: Throwable) {
[if !supportLists]11. [endif] }
[if !supportLists]12. [endif] override fun onComplete() {
[if !supportLists]13. [endif] }
[if !supportLists]14. [endif] });
这里面值得注意的一个点是
flow builder 和 map 操作符都会受到 flowOn 的影响。
而collect() 指定哪个线程,则需要看整个 flow 处于哪个 CoroutineScope 下。
2.4取消
这里和rxjava有些不同
如果 flow 是在一个挂起函数内被挂起了,那么 flow 是可以被取消的,否则不能取消。也就是说flow的取消,要搭配协程的取消
[if !supportLists]1. [endif]lifecycleScope.launch {
[if !supportLists]2. [endif] withTimeoutOrNull(2500) {
[if !supportLists]3. [endif] flow {
[if !supportLists]4. [endif] for (i in 1..5) {
[if !supportLists]5. [endif] delay(1000)
[if !supportLists]6. [endif] emit(i)
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif] }.collect {
[if !supportLists]9. [endif] println(it)
[if !supportLists]10. [endif] }
[if !supportLists]11. [endif] }
[if !supportLists]12. [endif] println("Done")
[if !supportLists]13. [endif] }
rxjava的取消
[if !supportLists]1. [endif]Observable.interval(0,1000, TimeUnit.MILLISECONDS)
[if !supportLists]2. [endif] .observeOn(AndroidSchedulers.mainThread())
[if !supportLists]3. [endif] .subscribe(object : Observer
[if !supportLists]4. [endif] var d : Disposable? = null
[if !supportLists]5. [endif] override fun onSubscribe(d: Disposable) {
[if !supportLists]6. [endif] this.d = d
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif] override fun onNext(t: Long) {
[if !supportLists]9. [endif] println(t)
[if !supportLists]10. [endif] if(t>10){
[if !supportLists]11. [endif] d?.dispose()
[if !supportLists]12. [endif] }
[if !supportLists]13. [endif] }
[if !supportLists]14. [endif] override fun onError(e: Throwable) {
[if !supportLists]15. [endif] }
[if !supportLists]16. [endif] override fun onComplete() {
[if !supportLists]17. [endif] }
[if !supportLists]18. [endif] })
2.5生命周期(状态回调)
[if !supportLists]1. [endif]lifecycleScope.launch {
[if !supportLists]2. [endif] val time = measureTimeMillis {
[if !supportLists]3. [endif] flow {
[if !supportLists]4. [endif] for (i in 0..3) {
[if !supportLists]5. [endif] emit(i.toString())
[if !supportLists]6. [endif] }
[if !supportLists]7. [endif] throw Exception("Test")
[if !supportLists]8. [endif] }.onStart {
[if !supportLists]9. [endif] Log.d("xys", "Start Flow in ${Thread.currentThread().name}")
[if !supportLists]10. [endif] }.onEach {
[if !supportLists]11. [endif] Log.d("xys", "emit value---$it")
[if !supportLists]12. [endif] }.onCompletion {
[if !supportLists]13. [endif] Log.d("xys", "Flow Complete")
[if !supportLists]14. [endif] }.collect {
[if !supportLists]15. [endif] Log.d("xys", "Result---$it")
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif] }
[if !supportLists]18. [endif] Log.d("xys", "Time---$time")
[if !supportLists]19. [endif] }
[if !supportLists]五.[endif]重试
[if !supportLists]1. [endif]lifecycleScope.launch {
[if !supportLists]2. [endif] flow {
[if !supportLists]3. [endif] for (i in 0..3) {
[if !supportLists]4. [endif] emit(i.toString())
[if !supportLists]5. [endif] }
[if !supportLists]6. [endif] }.retryWhen { _, retryCount ->
[if !supportLists]7. [endif] retryCount <= 3
[if !supportLists]8. [endif] }.onStart {
[if !supportLists]9. [endif] Log.d("xys", "Start Flow in ${Thread.currentThread().name}")
[if !supportLists]10. [endif] }.onEach {
[if !supportLists]11. [endif] Log.d("xys", "emit value---$it")
[if !supportLists]12. [endif] }.onCompletion {
[if !supportLists]13. [endif] Log.d("xys", "Flow Complete")
[if !supportLists]14. [endif] }.collect {
[if !supportLists]15. [endif] Log.d("xys", "Result---$it")
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif] }
[if !supportLists]六.[endif]异常捕获
[if !supportLists]1. [endif]lifecycleScope.launch {
[if !supportLists]2. [endif] flow {
[if !supportLists]3. [endif] for (i in 0..3) {
[if !supportLists]4. [endif] emit(i.toString())
[if !supportLists]5. [endif] }
[if !supportLists]6. [endif] throw Exception("Test")
[if !supportLists]7. [endif] }.retryWhen { _, retryCount ->
[if !supportLists]8. [endif] retryCount <= 3
[if !supportLists]9. [endif] }.onStart {
[if !supportLists]10. [endif] Log.d("xys", "Start Flow in ${Thread.currentThread().name}")
[if !supportLists]11. [endif] }.onEach {
[if !supportLists]12. [endif] Log.d("xys", "emit value---$it")
[if !supportLists]13. [endif] }.onCompletion {
[if !supportLists]14. [endif] Log.d("xys", "Flow Complete")
[if !supportLists]15. [endif] }.catch { error ->
[if !supportLists]16. [endif] Log.d("xys", "Flow Error $error")
[if !supportLists]17. [endif] }.collect {
[if !supportLists]18. [endif] Log.d("xys", "Result---$it")
[if !supportLists]19. [endif] }
[if !supportLists]20. [endif] }
也可以写在onCompletion中
[if !supportLists]1. [endif]lifecycleScope.launch {
[if !supportLists]2. [endif] flow {
[if !supportLists]3. [endif] for (i in 0..3) {
[if !supportLists]4. [endif] emit(i.toString())
[if !supportLists]5. [endif] }
[if !supportLists]6. [endif] throw Exception("Test")
[if !supportLists]7. [endif] }.retryWhen { _, retryCount ->
[if !supportLists]8. [endif] retryCount <= 3
[if !supportLists]9. [endif] }.onStart {
[if !supportLists]10. [endif] Log.d("xys", "Start Flow in ${Thread.currentThread().name}")
[if !supportLists]11. [endif] }.onEach {
[if !supportLists]12. [endif] Log.d("xys", "emit value---$it")
[if !supportLists]13. [endif] }.onCompletion {e->
[if !supportLists]14. [endif] if(e!=null){
[if !supportLists]15. [endif] Log.d("xys", "Flow Exception ${e}")
[if !supportLists]16. [endif] }else{
[if !supportLists]17. [endif] Log.d("xys", "Flow Complete")
[if !supportLists]18. [endif] }
[if !supportLists]19. [endif] }.collect {
[if !supportLists]20. [endif] Log.d("xys", "Result---$it")
[if !supportLists]21. [endif] }
[if !supportLists]22. [endif] }
在我们了解了一下简单的常用用法之后,flow对于我们已经变得没有那么陌生了,我们来真正重新认识一下flow
三.重识Flow:
3.1什么是冷流,热流?
冷流当数据被订阅的时候,发布者才开始执行发射数据流的代码。并且当有多个订阅者的时候,每一个订阅者何发布者都是一对一的关系,每个订阅者都会收到发布者完整的数据。热流无论有没有订阅者订阅,事件始终都会发生。当热流有多个订阅者时,发布者跟订阅者是一对多的关系,热流可以与多个订阅者共享信息。
示例:
[if !supportLists]1. [endif]fun simpleFlow2() = flow
[if !supportLists]2. [endif] println("Flow started")
[if !supportLists]3. [endif] for (i in 1..3) {
[if !supportLists]4. [endif] delay(1000)
[if !supportLists]5. [endif] emit(i)
[if !supportLists]6. [endif] }
[if !supportLists]7. [endif]
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif]
[if !supportLists]11. [endif] fun testCold(){
[if !supportLists]12. [endif]
[if !supportLists]13. [endif] viewModelScope.launch {
[if !supportLists]14. [endif] val flow = simpleFlow2()
[if !supportLists]15. [endif] println("Calling collect...")
[if !supportLists]16. [endif] flow.collect { value -> println(value) }
[if !supportLists]17. [endif]
[if !supportLists]18. [endif] println("Calling collect again...")
[if !supportLists]19. [endif] flow.collect { value -> println(value) }
[if !supportLists]20. [endif] }
[if !supportLists]21. [endif] }
[if !supportLists]22. [endif] val _events = MutableSharedFlow
[if !supportLists]23. [endif]
[if !supportLists]24. [endif] //这里会每隔1s发送一个数据
[if !supportLists]25. [endif] suspend fun foo1(){
[if !supportLists]26. [endif] Log.i(TAG, "foo: 开始发送数据")
[if !supportLists]27. [endif] Log.i(TAG, "foo: 开始发送A")
[if !supportLists]28. [endif] _events.emit("A")
[if !supportLists]29. [endif] Log.i(TAG, "foo: 结束发送A")
[if !supportLists]30. [endif] delay(1000)
[if !supportLists]31. [endif] Log.i(TAG, "foo: 开始发送B")
[if !supportLists]32. [endif] _events.emit("B")
[if !supportLists]33. [endif] Log.i(TAG, "foo: 结束发送B")
[if !supportLists]34. [endif] delay(1000)
[if !supportLists]35. [endif] Log.i(TAG, "foo: 开始发送C")
[if !supportLists]36. [endif] _events.emit("C")
[if !supportLists]37. [endif] Log.i(TAG, "foo: 结束发送C")
[if !supportLists]38. [endif] Log.i(TAG, "foo: 结束发送数据")
[if !supportLists]39. [endif] }
[if !supportLists]40. [endif]
[if !supportLists]41. [endif] fun testHot(){
[if !supportLists]42. [endif] //先开启协程,创建出SharedFlow
[if !supportLists]43. [endif] viewModelScope.launch {
[if !supportLists]44. [endif] foo1()
[if !supportLists]45. [endif] }
[if !supportLists]46. [endif] //立马进行收集
[if !supportLists]47. [endif] viewModelScope.launch(Dispatchers.IO) {
[if !supportLists]48. [endif] _events.collect {
[if !supportLists]49. [endif] Log.i(TAG, "initData: A开始收集 $it")
[if !supportLists]50. [endif] }
[if !supportLists]51. [endif] }
[if !supportLists]52. [endif] //延迟2秒再进行收集
[if !supportLists]53. [endif] viewModelScope.launch {
[if !supportLists]54. [endif] delay(2000)
[if !supportLists]55. [endif] _events.collect {
[if !supportLists]56. [endif] Log.i(TAG, "initData: B开始收集 $it")
[if !supportLists]57. [endif] }
[if !supportLists]58. [endif] }
[if !supportLists]59. [endif] }
从控制台可以观察到打印的信息:
foo: 开始发送数据
foo: 开始发送A
foo: 结束发送A
foo: 开始发送B
initData: A开始收集 B
foo: 结束发送B
foo: 开始发送C
initData: A开始收集 C
initData: B开始收集 C
foo: 结束发送C
foo: 结束发送数据
热流之一SharedFlow
构造函数:
public fun
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow
replay:表示当有新的订阅者collect时,发送几个已经发送过的数据给它,默认是0,即默认新订阅者不会获取到订阅之前的数据。
-extraBufferCapacity:表示减去replay,这个Flow还可以缓存多少个数据,默认是0;
-onBufferOverflow:表示缓存策略,即缓冲区满了后,如何处理,默认是挂起。z
热流之二喜闻乐见的StateFlow
官方就是希望用这个来替代LiveData,那么它到底是如何来替代LiveData的呢?
1)、特性
StateFlow是SharedFlow的子类,根据前面说的那么它是个热流,可以被多个观察者观察,同时可以设置它消失的scope以及条件。StateFlow只更新最新的数据,也就是它是一个replay为0的SharedFlow。
StateFlow里面和LiveData很像,都有个value来保存其值,也可以通过这个属性来获取或者设置它的值。
2)、使用
使用MutableStateFlow就和LiveData一样,不过它需要一个默认值。
也可以使用stateIn函数把一个Flow转成StateFlow,直接看这个函数:
val result = userId.mapLatest { newUserId ->
repository.observeItem(newUserId)
}.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5000),
initialValue = Result.Loading
)
从这个最基本的使用,我们可以看出以下信息:
它的范围是viewModelScope,当viewModelScope结束时,流会停止。
当观察者都被移除后,超过5s,为了不浪费性能,流会停止。
会有个默认值,也就是这个initivalValue。
3)、观察
从上面来看,确实很像LiveData,不过LiveData有个很重要的功能就是具有生命周期感知性能力,在UI处于活跃状态时才会更新数据,那这个StateFlow在收集时并没有传递lifecycleOwner,那如何达到一样的效果呢?
直接推荐最佳写法:
onCreateView(...) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
myViewModel.myUiState.collect { ... }
}
}
}
解释一下为什么是这样:
如果我们直接使用launch的话,可能会在onStop执行时这时来个数据更新,我的View根本没法更新,所以会造成错误,这里要达到和LiveData一样的效果在界面活跃时进行更新,所以这里启动协程就需要使用launchWhenStarted。
这个看起来没啥问题,不过我们前面不是有个WhileSubscribed这个优化项呢,表示没有观察者时5s后停止上游数据发射,那现在这个则无法做到,这时就需要使用repeatOnLifecycle
这个repeatOnLifecycle的作用就是当满足特定状态时启动协程,并且在生命周期退出这个状态时停止协程。那这就可以比如我APP退到后台,这时如果超过5S,则不会再订阅,协程终止,这时上游发射逻辑也会停止,提高性能。当APP再切换到前台时,会执行onStart,这时又会有观察者,这时上游逻辑又会开始,
3.2 flow的操作符
通过之前与rxjava对比,已经熟悉了一些的操作符。接下来再看一下还有哪些常用的操作符。
3.2.1创建操作符
Flow,flowOf,asFlow
3.2.2末端操作符
是在流上用于启动流收集的挂起函数。collect是最基础的末端操作符,但是还有另外一些更方便使用的末端操作符:
Collect,collectIndexed,collectLatest
toCollection、toSet、toList
这些操作符用于将Flow转换为Collection、Set和List。
launchIn
在指定的协程作用域中直接执行Flow。
last、lastOrNull、first、firstOrNull
3.2.3状态操作符
状态操作符不做任何修改,只是在合适的节点返回状态。
onStart:在上游生产数据前调用
onCompletion:在流完成或者取消时调用
onEach:在上游每次emit前调用
onEmpty:流中未产生任何数据时调用
catch:对上游中的异常进行捕获
retry、retryWhen:在发生异常时进行重试,retryWhen中可以拿到异常和当前重试的次数
3.2.4转换操作符
map、mapLatest、mapNotNull
3.2.5过滤操作符
filter、filterInstance、filterNot、filterNotNull
3.2.6组合操作符
combine、combineTransform
Merge、zip
3.2.7其他
[if !supportLists]四.[endif]Flow的应用
[if !supportLists]1.[endif]使用flow实现倒计时
[if !supportLists]1. [endif]fun countDownCoroutines(
[if !supportLists]2. [endif] total: Int,
[if !supportLists]3. [endif] scope: CoroutineScope,
[if !supportLists]4. [endif] onTick: (Int) -> Unit,
[if !supportLists]5. [endif] onStart: (() -> Unit)? = null,
[if !supportLists]6. [endif] onFinish: (() -> Unit)? = null,
[if !supportLists]7. [endif] ): Job {
[if !supportLists]8. [endif] return flow {
[if !supportLists]9. [endif] for (i in total downTo 0) {
[if !supportLists]10. [endif] emit(i)
[if !supportLists]11. [endif] delay(1000)
[if !supportLists]12. [endif] }
[if !supportLists]13. [endif] }.flowOn(Dispatchers.Main)
[if !supportLists]14. [endif] .onStart { onStart?.invoke() }
[if !supportLists]15. [endif] .onCompletion { onFinish?.invoke() }
[if !supportLists]16. [endif] .onEach { onTick.invoke(it) }
[if !supportLists]17. [endif] .launchIn(scope)
[if !supportLists]18. [endif] }
[if !supportLists]1. [endif]countDownCoroutines(5, lifecycleScope,
[if !supportLists]2. [endif] onTick = { second ->
[if !supportLists]3. [endif] tvSkip.text = "${second}s跳过"
[if !supportLists]4. [endif] }, onStart = {
[if !supportLists]5. [endif] // 倒计时开始
[if !supportLists]6. [endif] if (show) {
[if !supportLists]7. [endif] tvSkip.visibility = View.VISIBLE
[if !supportLists]8. [endif] }
[if !supportLists]9. [endif] }, onFinish = {
[if !supportLists]10. [endif] // 倒计时结束,重置状态
[if !supportLists]11. [endif] if (show) {
[if !supportLists]12. [endif] enterHomePage()
[if !supportLists]13. [endif] }
[if !supportLists]14. [endif] })
[if !supportLists]2.[endif]flow做搜索限流
[if !supportLists]1. [endif]lifecycleScope.launchWhenCreated {
[if !supportLists]2. [endif] _etState.
[if !supportLists]3. [endif] sample(500).filter {
[if !supportLists]4. [endif] it.isNotEmpty()
[if !supportLists]5. [endif] }.collect {
[if !supportLists]6. [endif] viewModel.searchArticles(it)
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif] }
[if !supportLists]3.[endif]flow结合room
[if !supportLists]1. [endif]@Dao
[if !supportLists]2. [endif]interface UserDao {
[if !supportLists]3. [endif]
[if !supportLists]4. [endif] //返回插入行 ID 的Insert DAO 方法永远不会返回 -1,因为即使存在冲突,此策略也将始终插入行
[if !supportLists]5. [endif] @Insert(onConflict = OnConflictStrategy.REPLACE)
[if !supportLists]6. [endif] suspend fun insert(user: User)
[if !supportLists]7. [endif]
[if !supportLists]8. [endif] @Query("SELECT * FROM user")
[if !supportLists]9. [endif] fun getAll(): Flow>
[if !supportLists]10. [endif]
[if !supportLists]11. [endif]}
[if !supportLists]1. [endif] fun getAll(): Flow> {
[if !supportLists]2. [endif] return AppDatabase.getInstance(getApplication())
[if !supportLists]3. [endif] .userDao()
[if !supportLists]4. [endif] .getAll()
[if !supportLists]5. [endif] .catch { e -> e.printStackTrace() }
[if !supportLists]6. [endif] .flowOn(Dispatchers.IO) //切换上下文为IO异步
[if !supportLists]7. [endif] }
[if !supportLists]4.[endif]flow结合Retrofit
[if !supportLists]1. [endif] viewModelScope.launch {
[if !supportLists]2. [endif] flow {
[if !supportLists]3. [endif] //这里就是通过Retrofit从服务器拿到对应key过滤后的文章内容
[if !supportLists]4. [endif] val map = mapOf("word" to key, "search_match_type" to 1.toString(), "page_size" to 30)
[if !supportLists]5. [endif] val list = RetrofitClient.articleApi.getSearchResult(map as Map
[if !supportLists]6. [endif] //将对应数据发射出去
[if !supportLists]7. [endif] emit(list)
[if !supportLists]8. [endif] }.flowOn(Dispatchers.IO)
[if !supportLists]9. [endif] .catch {
[if !supportLists]10. [endif] e -> e.printStackTrace()
[if !supportLists]11. [endif] }
[if !supportLists]12. [endif] .collect {
[if !supportLists]13. [endif] //这里收到对应数据,更新对应的LiveData数据
[if !supportLists]14. [endif] articles.setValue(it.article?.items)
[if !supportLists]15. [endif] }
[if !supportLists]16. [endif] }
代码已经托管到github
[email protected]:mm46468648/flowTest.git
补充小知识:
一.Navigation+BottomNavigationView的用法
[if !supportLists]1.[endif]创建nav_graph
[if !supportLists]2.[endif]创建bottom_nav_menu
[if !supportLists]3.[endif]布局
[if !supportLists]1. [endif] [if !supportLists]2. [endif] android:id="@+id/fragment" [if !supportLists]3. [endif] android:name="androidx.navigation.fragment.NavHostFragment" [if !supportLists]4. [endif] android:layout_width="match_parent" [if !supportLists]5. [endif] android:layout_height="match_parent" [if !supportLists]6. [endif] app:defaultNavHost="true" [if !supportLists]7. [endif] app:layout_constraintBottom_toBottomOf="parent" [if !supportLists]8. [endif] app:layout_constraintLeft_toLeftOf="parent" [if !supportLists]9. [endif] app:layout_constraintRight_toRightOf="parent" [if !supportLists]10. [endif] app:layout_constraintTop_toTopOf="parent" [if !supportLists]11. [endif] app:navGraph="@navigation/nav_graph" /> [if !supportLists]12. [endif] [if !supportLists]13. [endif] [if !supportLists]14. [endif] [if !supportLists]15. [endif] android:id="@+id/bottom_nav_view" [if !supportLists]16. [endif] android:layout_width="match_parent" [if !supportLists]17. [endif] android:layout_height="wrap_content" [if !supportLists]18. [endif] app:layout_constraintBottom_toBottomOf="parent" [if !supportLists]19. [endif] app:layout_constraintLeft_toLeftOf="parent" [if !supportLists]20. [endif] app:layout_constraintRight_toRightOf="parent" [if !supportLists]21. [endif] app:menu="@menu/bottom_nav_menu" /> [if !supportLists]4.[endif]关联: [if !supportLists]1. [endif] //fragment的容器视图,navHost的默认实现——NavHostFragment [if !supportLists]2. [endif]val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment [if !supportLists]3. [endif] [if !supportLists]4. [endif] //管理应用导航的对象 [if !supportLists]5. [endif]val navController = navHostFragment.navController [if !supportLists]6. [endif] [if !supportLists]7. [endif]//fragment与BottomNavigationView的交互交给NavigationUI [if !supportLists]8. [endif]bottom_nav_view.setupWithNavController(navController) 注意: menu中的id和nav_garph中的id一定要对应,否则会找不到需要展示的fragment 参考: https://juejin.cn/post/6898234461361307655/