三、Kotlin协程+异步加载+Loading状态
Kotlin协程是执行异步操作的关键知识点,对于不可以在UI线程执行的耗时操作我们需要通过协程进行异步操作,加载页面。
复习状态管理,操作Loading状态
1.Kotlin协程
-
为什么需要协程
- Android主线程(UI)线程中不可以执行耗时操作(网络操作、数据库等)
- 有需求需要异步执行
- 传统方案:AsyncTask(已废弃)、Handler(复杂)
- 使用协程可以用同步代码的风格写异步逻辑,简洁、安全、可取消
- Kotlin的原生支持使用起来更加便捷
-
协程的三大核心概念
-
挂起函数(Suspend function)是一种特殊的可暂停且不阻塞线程的函数,在其中可以进行耗时操作,本质上挂起函数是一个回调函数,代码执行到挂起函数后,挂起函数在子线程中执行耗时操作,执行完毕之后回调后续代码继续运行页面逻辑。
kotlin//挂起函数模拟 suspend fun requestNotes(){ delay(2000) }挂起函数使用suspend修饰,其他与普通函数无异。
-
协程作用域(CoroutineScope)是用来控制协程生命周期的一个函数/对象,常见的有LifecycleScope,viewModelScope,rememberCoroutineScope()。还有后边要讲的Jetpack Compose的副作用(LaunchedEffect)
kotlinval scope = rememberCoroutineScope() val showMsg = { msg: String -> scope.launch { snackBarHostState.showSnackbar(msg) } Unit }使用场景,例如我们显示Snackbar时,为了防止调用方处于非UI线程,所以在方法中使用协程,调用时明确在UI线程。
-
调度器(Dispatcher)决定协程运行在哪类线程:Dispatchers.Main(UI线程),Dispatchers.IO(磁盘/网络)
kotlin//使用调度器切换到IO线程 suspend fun requestNotes(){ return withContext(Dispatchers.IO){ delay(2000) } //自动切回原线程 } -
-
协程的关键规则
-
只能在协程或者挂起函数中调用挂起函数!!!
-
UI更新必须在Main线程
-
不要手动创建GlobalScope(容易内存泄露)
kotlin//这是GlobalScope的用法 GlobalScope.launch(Dispatchers.Main) { val data = fetchData() updateUI(data) // 可能在 UI 销毁后调用! }因为没有绑定自己的生命周期,GlobalScope中的代码不会随着组件销毁而销毁,所以会造成报错等难以估量的bug,所以实际中应该避免使用。
-
2、Jetpack Compose的副作用
-
什么是副作用(Side Effect)?
- 改变Compose作用域之外的状态,例如:
- 启动协程
- 修改ViewModel数据
- 调用系统API(如权限要求)
- 副作用就是UI组件Compose与外界沟通的桥梁。
- 为什么要使用:Composable 函数可能被 频繁、多次、非预期地调用,如果不使用副作用可能会因为
- 改变Compose作用域之外的状态,例如:
-
LaunchedEffect的作用
-
当 key 发生变化 或 Composable 首次进入 Composition 时,启动一个 生命周期绑定的协程
-
Composable 退出时,协程自动取消
kotlin//示例代码 LaunchedEffect(Unit) { //模拟请求 requestNotes() //生成模拟数据 val mockNotes = listOf<Note>( Note( id = 1L, title = "协程入门", content = "今天学习了 Kotlin 协程和 LaunchedEffect,实现了异步加载笔记列表。", timestamp = "2026-01-07 09:00", tags = listOf("Kotlin", "Coroutines") ), Note( id = 2L, title = "状态管理进阶", content = "理解了 mutableStateOf 和 remember 的配合使用。", timestamp = "2026-01-07 10:30", tags = listOf("Compose", "State") ), Note( id = 3L, title = "副作用处理", content = "学会了用 LaunchedEffect 安全地执行异步操作。", timestamp = "2026-01-07 11:15", tags = listOf("SideEffect", "Android") ) ) notes = mockNotes isLoading = false }key = Unit → 只在首次进入时执行一次
key = someId → 当
someId变化时重新执行 -
-
rememberCoroutineScope的使用
- 已经在前面介绍过了,不重复赘述
3.项目代码更新
-
创建一个卡片列表组件
kotlinpackage com.jiahengfei.ktdm import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext /** * Create by zFox from AndroidStudio2022.01 * 日期:2026/1/7 19:10 * 描述:笔记本列表 */ /** * 挂起函数 */ suspend fun requestNotes(){ return withContext(Dispatchers.IO){ delay(2000) } } @Composable fun NoteListScreen( modifier: Modifier = Modifier, showSnackBar: (String) -> Unit, ) { var notes by remember { mutableStateOf<List<Note>>(emptyList()) } var isLoading by remember { mutableStateOf(true) } LaunchedEffect(Unit) { //模拟请求 requestNotes() //生成模拟数据 val mockNotes = listOf<Note>( Note( id = 1L, title = "协程入门", content = "今天学习了 Kotlin 协程和 LaunchedEffect,实现了异步加载笔记列表。", timestamp = "2026-01-07 09:00", tags = listOf("Kotlin", "Coroutines") ), Note( id = 2L, title = "状态管理进阶", content = "理解了 mutableStateOf 和 remember 的配合使用。", timestamp = "2026-01-07 10:30", tags = listOf("Compose", "State") ), Note( id = 3L, title = "副作用处理", content = "学会了用 LaunchedEffect 安全地执行异步操作。", timestamp = "2026-01-07 11:15", tags = listOf("SideEffect", "Android") ) ) notes = mockNotes isLoading = false } Box( modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { if (isLoading) { CircularProgressIndicator() } else { //显示笔记本列表 LazyColumn( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(16.dp) ) { items(notes) { note -> NoteCard( note = note, showSnackBar, modifier = Modifier.fillParentMaxWidth() ) } } } } }这是新的入口 Composable,替代之前的单卡片测试
-
在MainActivity中使用这个组件
kotlinpackage com.jiahengfei.ktdm import android.os.Bundle import android.widget.TextView import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import com.jiahengfei.jnote.ui.theme.NoteAppTheme import kotlinx.coroutines.launch /** * ================================================ * 项 目:ktdm * 日 期:2026/1/5 19:17 * 包 名:com.jiahengfei.ktdm * 描 述:进阶的第一行代码 * Create by zFox from AndroidStudio2020.3 * ================================================ */ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { NoteAppTheme() { val snackBarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() Scaffold( snackbarHost = { SnackbarHost(snackBarHostState) } ) { Surface( modifier = Modifier .fillMaxWidth() .systemBarsPadding(), color = MaterialTheme.colorScheme.background, ) { val showMsg = { msg: String -> scope.launch { snackBarHostState.showSnackbar(msg) } Unit } NoteListScreen( showSnackBar = showMsg ) // Column( // modifier = Modifier // .fillMaxWidth() // ) { // val sampleNote = Note( // 1L, // "第一天学习笔记", // "今天非常之Nice!", // "2026-01-05", // ) // NoteCard(sampleNote, showMsg) // // val sampleNote2 = Note( // 2L, // "第二天学习笔记", // "今天学习了 Kotlin 扩展函数和 Compose 状态管理,实现了可交互的笔记卡片!", // "2026-01-06", // listOf("Kotlin", "Compose", "Android") // ) // NoteCard(sampleNote2, showMsg) // } } } } } } } -
运行效果


这就是第三天的全部知识点了,明天我们将学习使用ViewModel统一管理状态!