Kotlin中类型转换是通过as实现的 var v : View v as Button
dip方法可以把dp转换成px,dip方法来自域anko库 import org.jetbrains.anko.dip 在模块的build.gradle中引入 compile "org.jetbrains.anko:anko-common:$anko_version" anko还提供了其他一些函数 dip -> px sp -> px px2dip px2sp dimen -> dip2sp
| kotlin | java |
|---|---|
| and | & |
| or | | |
| xor | ^ |
| shl | << |
| shr | >> |
| ushr | >>> |
java
Intent intent = new Intent(MainActivity.this, LinearLayoutActivity.class);
startActivity(intent);
Kotlin
val intent = Intent(this@MainActivity, LinearLayoutActivity::class.java)
startActivity(intent)
java MainActivity.this -> this@MainActivity , 也都可以简写为this
java LinearLayoutActivity.class -> LinearLayoutActivity::class.java
使用anko包实现跳转
startActivity<LinearLayoutActivity>()
java
Intent intent = new Intent(this, ActSecondActivity.class);
intent.putExtra("request_time", DateUtil.getNowTime());
intent.putExtra("request_content", et_request.getText().toString());
startActivity(intent);
kotlin
第一种写法
startActivity<ActSecondActivity>(
"request_time" to DateUtil.nowTime,
"request_content" to et_request.text.toString())
第二种写法
startActivity<ActSecondActivity>(
Pair("request_time", DateUtil.nowTime),
Pair("request_content", et_request.text.toString()))
使用intent进行跳转 同样来自anko库
val intent = intentFor<ActSecondActivity>(
"request_time" to DateUtil.nowTime,
"request_content" to et_request.text.toString())
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent)
Intent.FLAG_ACTIVITY_NEW_TASK 类似于launchMode.standard
Intent.FLAG_ACTIVITY_SINGLE_TOP 类似于launchMode.singletop
Intent.FLAG_ACTIVITY_CLEAR_TOP 类似于launchMode.singleTask
Intent.FLAG_ACTIVITY_NO_HISTORY 类似于launchMode.standard 但是这种方式不保存新的acitivty实例
Intent.FLAG_ACTIVITY_CLEAR_TASK 结合Intent.FLAG_ACTIVITY_NEW_TASK使用,清空所有实例
可以直接使用anko库,例如
startActivity(intent.newTask())
带返回参数
val info = MessageInfo(et_request.text.toString(), DateUtil.nowTime)
startActivityForResult<ActResponseActivity>(0, "message" to info)
val response = MessageInfo(et_response.text.toString(), DateUtil.nowTime)
val intent = Intent()
intent.putExtra("message", response)
setResult(Activity.RESULT_OK, intent)
finish()
override fun onActivityResult(requestCode: Int, resultCode: Int, data:Intent?) {
if (data != null) {
val response = data.extras.getParcelable<MessageInfo>("message")
tv_request.text = "${response.send_time}\${response.content}"
}
}
//@Parcelize表示自动实现序列化接口
@Parcelize
data class MessageInfo(val content: String, val send_time: String) : Parcelable {
}
还需要在模块的build.gradle文件中设置
//@Parcelize 标记需要设置experimental = true
androidExtensions {
experimental = true
}
使用示例
val request = MessageInfo(et_request.text.toString(), DateUtil.nowTime)
startActivity<ParcelableSecondActivity>("message" to request)
val request = intent.extras.getParcelable<MessageInfo>("message")
解构声明 解构声明(Destructuring Declarations)是 Kotlin 中一项非常实用的语法特性,它允许你将一个对象分解成多个变量,从而简化代码并提高可读性。创建并启动线程 Thread{ }.start() 配合anko库使用 doAsync{ //do something in the background thread uiThread{ //do something in the UI thread } } 返回线程的doAsyncResult, 可以对线程进行更多的控制 使用readText或readBytes访问网络 doAsync{ var url = "https://www.baidu.com" val text = Url(url).readText() //保存图片 val imageUrl = "https://aaa.com/a.png" val bytes = Url(url).readBytes() val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) val path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS). toString() + "/" val file_path = "$path${DateUtil.getFormatTime()}.png" File(file_path).writeBytes(bytes) }解构声明基于约定而非魔法 - 它依赖于对象的 componentN() 函数:编译器会为解构声明的每个变量调用对应的 componentN() 函数,component1() 对应第一个变量,component2() 对应第二个变量,以此类推 数据类(data class)自动支持解构,因为它们自动生成了 componentN() 函数,几点说明 1、解构的顺序必须与 componentN() 的顺序一致 2、不能跳过中间变量直接解构后面的变量(但可以用 _ 占位) 3、确保解构的变量数量不超过对象提供的 componentN() 数量val (a, b, c) = someObject 等价于 val a = someObject.component1() val b = someObject.component2() val c = someObject.component3()Kotlin 集合也支持解构:注意:集合只提供 component1() 到 component5(),最多解构5个元素。data class Person(val name: String, val age: Int) val person = Person("Alice", 25) val (name, age) = person // 解构 println("$name is $age years old") // 输出: Alice is 25 years old在函数返回值中使用:函数可以返回一个数据类或 Pair/Triple,然后直接解构:val list = listOf(1, 2, 3) val (a, b, c) = list println("$a, $b, $c") // 输出: 1, 2, 3下划线忽略不需要的值:如果只需要解构部分值,可以用 _ 忽略不需要的fun getPair() = Pair("key", "value") val (key, value) = getPair() //demo 2 fun getUserInfo(): Pair<String, Int> { return "Bob" to 30 } fun main() { val (name, age) = getUserInfo() println("Name: $name, Age: $age") }自定义解构:对于非数据类,可以手动实现 componentN() 函数:val (_, age) = person // 只解构age一些使用例子:class Point(val x: Int, val y: Int) { operator fun component1() = x operator fun component2() = y } val point = Point(10, 20) val (x, y) = pointwhen表达式//1从Map中提取值: val map = mapOf("name" to "Alice", "age" to 25) for ((key, value) in map) { println("$key -> $value") } //2函数返回多个值: data class Result(val data: String, val status: Int) fun process(): Result { /*...*/ } val (data, status) = process() //3在循环中使用: val listOfPairs = listOf(Pair(1, "one"), Pair(2, "two")) for ((num, str) in listOfPairs) { println("$num is $str") }作用域函数var result = when { 条件1 -> 结果1 条件2 -> 结果2 else -> 默认结果 }
| 名称 | 功能 | 用法 | 返回结果 | 使用场景 |
|---|---|---|---|---|
| also | 在对象操作过程中执行额外的副作用(如日志记录) 使用 it 作为对象引用 |
|
对象本身 | 打印日志或调试 监控对象状态变化 |
| let | 当你想对对象执行某些操作,并且需要返回一个新的结果时。 使用 it 作为对象引用 |
|
最后一行的结果 |
空值处理 (?.let) 方法链式调用 |
| run | 对象初始化后,执行一组操作并返回结果。 使用 this 作为对象引用 |
|
最后一行的结果 |
执行初始化代码块 对象配置后直接返回结果 不依赖于调用者可以独立使用 直接写一个代码块run{}也是可以的 |
| apply | 配置对象属性,并返回对象本身。 使用 this 作为对象引用 |
|
对象本身 |
对象初始化后直接修改属性 使用在对象构建者模式中 |
| with | 对已存在的对象执行操作,而不返回对象本身。 使用 this 作为对象引用 |
|
最后一行的结果 |
对单个对象执行一系列操作 代码结构更简洁 |
| 名称 | 功能 | 定义和用法 |
|---|---|---|
| use | use 是 Kotlin 给 Closeable / AutoCloseable 对象扩展的一个函数 | |
fun getAge() : String?{
return null
}
var result = getAge()?.let {
"age is not null"
}?: {"age is null"}
getAge() 返回 null,因此不会执行 let 内部的代码。由于 let 不执行,转而执行 ?: 后面的代码。
注意:?: 后面的代码是 一个 lambda 表达式 { "age is null" },而不是直接的字符串 "age is null"。
Lambda 表达式不会自动执行,它只是被当作一个函数对象返回。
可以选择不适用lambda表达式的方式
var result = getAge()?.let {
"age is not null"
}?: "age is null"
这样就可以直接执行了
也可以直接调用lambda表达式
var result = getAge()?.let { //方式一
"age is not null"
}?: {"age is null"}.invoke()
var result = getAge()?.let { //方式二
"age is not null"
}?: {"age is null"}()
注意上面的例子,如果不使用非空判断,例如下面的例子,则会直接进入到let返回age is not null
var result = getAge().let {
"age is not null"
}?: "age is null"
takeif和takeUnless扩展函数
| 扩展函数 | 功能 | 用法 | 场景 |
|---|---|---|---|
| takeif | 当对象满足条件时,返回对象本身。 当对象不满足条件时,返回 null。 |
|
过滤数据时 条件检查并执行后续操作 替代简单的 if 判断 |
| takeUnless | 当对象不满足条件时,返回对象本身。 当对象满足条件时,返回 null。 |
|
处理异常或边界条件 防止无效数据传递 简化条件判断 |
val sum = { x: Int,y: Int -> x+y }
val sum : (Int,Int) -> Int = { x,y -> x+y }
也可以写成
val sum = fun(x :Int,y: Int) = x+y
val sum = fun(x :Int,y: Int) : Int { return x+y }
匿名函数的返回类型推断机制与正常函数一样,对于具有表达式函数体的匿名函数将自动推断返回类型,而具有代码块函数体的匿名函数的返回类型必须显式指定(或者假定为Unit)
在 Kotlin 中,如果一个函数的最后一个参数是函数,并且传递一个Lambda表达式作为相应的参数,那么可以在括号外指定 Lambda 表达式中的值。示例代码如下:
ints.filter{ it > 0 }
可以使用返回语句返回 Lambda 表达式的显式带有数据类型的值或者不显示数据类型的值。因此,下面两行代码是等价的。
ints.filter{ val shouldFilter = it > 0 shouldFilter }
ints.filter{ val shouldFilter = it > 0 return@filter shouldFilter }
使用延迟初始化的一个例子,可以动态的修改函数体
lateinit var sum : (Int, Int)-> Int
fun print(){
sum = {x,y->x+y}
}
协程
协程是轻量级的线程:它不是操作系统线程,而是由库(kotlinx.coroutines)调度的“可挂起的执行单元”。协程让你用同步的代码风格写异步逻辑,通过 suspend 函数在必要时挂起而不阻塞线程。
一些API启动长时间运行的操作(如网络I/O、文件I/O、CPU、GPU、密集型任务等),并要求调用者阻塞直到它们完成。协程提供了一种方法避免阻塞线程,并通过更廉价、更可控的操作来替代线程阻塞,如协程挂起。
Coroutine:一个可挂起/恢复的计算单元(类似“轻线程”)。 CoroutineScope:协程运行的范围(负责协程的 Job 生命周期和 CoroutineContext)。 CoroutineContext:包含 Job、Dispatcher、异常处理器等组成的信息(是个键值集合)。 Job:协程的句柄,表示协程的生命周期,可取消、join 等。 Dispatcher:用于选择在哪些线程/线程池上运行(Dispatchers.Main, Dispatchers.IO, Dispatchers.Default)。 suspend 函数:可以挂起协程的函数(不会阻塞线程)。 Structured Concurrency(结构化并发):协程会在 scope 内启动,scope 控制子协程生命周期(避免泄漏)。
// 阻塞主线程直到内部完成,通常在 main() 或单元测试使用
fun exampleRunBlocking() {
println("Start")
runBlocking {
launch {
delay(1000L)
println("Inside runBlocking")
}
}
println("End")
}
// 启动一个不返回结果的协程(返回 Job)
fun exampleLaunch() {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L) // 等待协程完成
}
// 启动并返回 Deferred<T>(可 await() 获取结果)
suspend fun exampleAsync(): Int {
val deferred1 = GlobalScope.async {
delay(1000L)
10
}
val deferred2 = GlobalScope.async {
delay(1500L)
20
}
// await() 是挂起函数,等待结果但不阻塞线程,deferred1和deferred2一起挂起,等待结果。
return deferred1.await() + deferred2.await() // 返回 30
}
// 切换上下文并返回结果(不会创建新协程,只挂起当前协程)
val res = withContext(Dispatchers.IO) {
// 阻塞式 IO 操作放这儿
}
suspend 函数意味着它可能挂起当前协程并允许线程去执行其它任务。挂起函数(Suspending Functions)是 Kotlin 协程中的特殊函数,它可以挂起当前协程,并等待结果返回。挂起函数使用 suspend 关键字声明,并返回一个 Deferred 对象。
// 使用 suspend 关键字声明挂起函数
suspend fun fetchUserData(userId: String): User {
// 这里可以调用其他挂起函数
delay(1000) // 模拟耗时操作,不会阻塞线程
return User(userId, "John Doe")
}
// 挂起函数可以在协程作用域内调用
suspend fun updateUserProfile(userId: String) {
val user = fetchUserData(userId) // 挂起点,等待数据返回
println("User: ${user.name}")
}
协程调用挂起函数时,会挂起当前协程,并等待挂起函数返回结果。挂起函数返回结果时,会恢复当前协程,继续执行后续代码。 一个协程可以调用多个挂起函数,这些挂起函数会按照顺序执行,并等待返回结果。 挂起函数的返回结果会作为下一个挂起函数的参数,传递给下一个挂起函数。
//例子:
suspend fun fetchUserData(userid : String) : String{
ILog.LogDebug("fetchUserData start")
delay(1000)
ILog.LogDebug("fetchUserData end")
return "userid : $userid"
}
fun exampleLaunch(){
ILog.LogDebug("start launch")
lifecycleScope.launch {
ILog.LogDebug("start fetch")
fetchUserData("1")
ILog.LogDebug("after fetch")
}
ILog.LogDebug("end launch")
}
//调用exampleLaunch会以此打印
start launch
start fetch
fetchUserData start
end launch
fetchUserData end
after fetch
//例子2
suspend fun exampleAsync() : Int{
ILog.LogDebug("start exampleAsync")
val defered1 = lifecycleScope.async {
ILog.LogDebug("start defered1")
delay(1500)
ILog.LogDebug("end defered1")
10
}
ILog.LogDebug("after defered1")
val defered2 = lifecycleScope.async {
ILog.LogDebug("start defered2")
delay(1000)
ILog.LogDebug("end defered2")
20
}
ILog.LogDebug("after defered2")
return defered1.await() + defered2.await()
}
lifecycleScope.launch {
ILog.LogDebug("start launch")
val ret = exampleAsync()
Log.d("Thor","ret : $ret")
}
//上面代码会打印
start launch
start exampleAsync
after defered1
after defered2
start defered1
start defered2
end defered2
end defered1
ret : 30
//例子3
suspend fun fetchA(): String { /* ... */ }
suspend fun fetchB(): String { /* ... */ }
// 在某个 scope(例如 viewModelScope)中:
val result = coroutineScope { // 保证结构化并发
val aDeferred = async(Dispatchers.IO) { fetchA() }
val bDeferred = async(Dispatchers.IO) { fetchB() }
// 并发执行,await 会挂起直到对应结果完成
val a = aDeferred.await()
val b = bDeferred.await()
"$a + $b"
}
//要点:async 并发且并行(如果 Dispatcher 有多个线程),coroutineScope 确保若内部某个子协程抛异常,会取消其它子协程并把异常向上抛(结构化并发)。
suspend fun differentDispatchers() {
// 1. Main - 主线程,用于 UI 操作
withContext(Dispatchers.Main) {
updateUI() // 更新 UI 必须在主线程
}
// 2. IO - IO 密集型操作
withContext(Dispatchers.IO) {
performNetworkRequest() // 网络请求
readFromFile() // 文件读写
queryDatabase() // 数据库操作
}
// 3. Default - CPU 密集型操作
withContext(Dispatchers.Default) {
complexCalculation() // 复杂计算
sortingLargeList() // 排序大数据
imageProcessing() // 图片处理
}
// 4. Unconfined - 不限制在任何特定线程
withContext(Dispatchers.Unconfined) {
println("Thread: ${Thread.currentThread().name}")
}
}
实际调度器使用示例
fun loadUserData(userId: String) {
viewModelScope.launch {
// 在 IO 线程执行网络请求
val user = withContext(Dispatchers.IO) {
apiService.getUser(userId)
}
// 回到 Main 线程更新 UI
withContext(Dispatchers.Main) {
userNameTextView.text = user.name
userAvatarImageView.setImageBitmap(user.avatar)
}
// 在 Default 线程处理数据
val processedData = withContext(Dispatchers.Default) {
user.analyzeBehavior() // CPU 密集型分析
}
// 再次回到 Main 线程显示结果
withContext(Dispatchers.Main) {
showAnalysisResult(processedData)
}
}
}
//例子2
// 创建一个协程作用域
val scope = CoroutineScope(Dispatchers.Default)
// 创建一个协程任务
val job = scope.launch {
// 模拟一个耗时操作
delay(1000)
println("Task completed")
}
// 等待任务完成
job.join()
println("Task end")
//job.join()函数的解释:挂起当前协程(或当前线程),直到 job 对应的协程执行完毕。
也就是说:
scope.launch { ... } 启动了一个新协程;
这时主协程(或当前线程)会继续往下执行;
调用 job.join() 会暂停当前主协程(或线程);
直到子协程执行完毕(即 “Task completed” 打印出来);
然后 join() 返回,程序继续执行。
打印结果:
Task completed
Task end
1. 协程的取消:
协程的取消是通过抛出 CancellationException 来实现的。
当协程被取消时,会抛出一个 CancellationException 异常,这个异常会传递给协程的 catch 块,
然后协程会执行 catch 块中的代码,并返回结果。
fun cancellationExample() {
val job = GlobalScope.launch {
try {
repeat(1000) { i ->
println("Job: I'm sleeping $i ...")
delay(500L)
}
} catch (e: CancellationException) {
println("Job: I was cancelled!")
// 清理资源
releaseResources()
} finally {
// 可在此处执行清理操作
println("Job: I'm running finally")
}
}
// 延迟一段时间后取消
GlobalScope.launch {
delay(2000L)
println("Main: I'm tired of waiting!")
job.cancel() // 取消协程
job.join() // 等待协程结束
// 或者直接使用 job.cancelAndJoin()
}
}
//例子2
val job = scope.launch {
repeat(1000) { i ->
if (!isActive) return@launch // 早退出
doWork()
delay(100) // 这是可挂起点,会响应取消
}
}
job.cancel() // 触发取消
上面例子2的一个解释
启动一个协程(launch);
循环执行 doWork();
每次执行后 delay(100);
调用 job.cancel() 后,请求取消该协程;
协程在下一个挂起点(delay)或检查点(isActive)处检测到取消,随后结束。
🧠关键点:协程的取消是协作式(Cooperative Cancellation)
协程的取消是协作式(Cooperative Cancellation),即协程需要自行检查取消状态,并自行处理取消。
🧩 1️⃣ “挂起点(suspension point)” 是自动检查点
比如 delay(), yield(), withContext(), await() 等函数,
它们内部会自动检测当前协程是否被取消。
✅ 有挂起点时:
delay(100) // 如果协程被取消,这里会抛出 CancellationException,协程结束
流程:
调用 job.cancel();
协程被标记为 isCancelled = true;
下一次 delay() 检测到取消状态;
抛出 CancellationException;
协程停止执行。
❌ 没有挂起点时:
假设改成这样:
val job = scope.launch {
repeat(1000) { i ->
doWork() // 假设是一个死循环或 CPU 密集任务
// 没有 delay / yield / isActive 检查
}
}
job.cancel()
这时协程不会立刻停止!
因为协程在执行计算时一直占用线程,没有挂起,也没有检查取消状态。
即使调用 job.cancel(),协程依然会继续跑,直到循环自然结束或线程退出。
⚙️ 那为什么要 isActive 呢?
isActive 是一个协程上下文属性,代表当前协程是否还处于激活状态。
if (!isActive) return@launch
这行代码是一个手动取消检查点。
当协程中有一段纯计算逻辑、没有挂起点时,可以主动插入:
while (isActive) {
heavyComputation()
}
或者:
repeat(1000) {
if (!isActive) return@launch
compute()
}
这样即使 compute() 中没有挂起点,取消信号也能被感知到,协程会主动结束。
默认情况下,isActive 总是返回 true,表示协程处于激活状态。
但是,如果协程被取消,isActive 会返回 false,表示协程已被取消。
因此,我们可以在协程中检查 isActive,并自行处理取消。
如果你的任务是 CPU 密集 的,比如循环计算、图像处理等,
推荐这样写:
val job = scope.launch(Dispatchers.Default) {
for (i in 0..1_000_000) {
if (!isActive) break // 主动检测
doHeavyCalculation()
yield() // 主动让出执行权,并响应取消
}
}
这里的 yield() 会:
暂时挂起协程;
让调度器有机会检查取消;
再恢复执行。
yield() 是一个 挂起函数,官方说明:暂时让出当前协程的执行权,让调度器有机会运行其他协程,同时检查当前协程是否被取消。
换句话说:
✅ 它是一个挂起点(可以响应取消)
✅ 它会让出线程执行权(让其他协程执行)
🚫 但它不会终止协程 —— 一旦被调度回来,协程会从挂起处继续执行下一步(比如下一次循环)
🔁 是否每次循环都“切线程”?
不一定。yield() 是**“建议性让出”**(cooperative),而不是强制切换线程。
调度器可能让当前协程重新继续执行(线程没变);
也可能切到别的线程再回来(尤其在多核 Default dispatcher 上);
但无论如何,yield() 期间协程会检查是否被取消,因此是一个安全的取消检测点
⚡ 如果协程被取消,会怎样?
协程被取消时,会抛出一个 CancellationException 异常,这个异常会传递给协程的 catch 块,
然后协程会执行 catch 块中的代码,并返回结果。
因此,你可以在 catch 块中处理取消,并返回一个默认值或错误码。
超时处理
suspend fun timeoutExample() {
try {
val result = withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
"Done" // 在超时前完成则返回这个结果
}
println("Result: $result")
} catch (e: TimeoutCancellationException) {
println("Timed out!")
}
}
withTimeout(1000) {
// 若 1s 未完成,会抛 TimeoutCancellationException 并取消协程
}
withTimeoutOrNull(1000) {
// 超时返回 null 而不是抛出
}
异常处理
在 launch 中抛出的异常会由 CoroutineExceptionHandler 或父 scope 处理(会取消父)。 在 async 中,异常会被封装到 Deferred,调用 await() 时抛出。
fun exceptionHandling() {
// 1. try-catch 在协程内部
val job1 = GlobalScope.launch {
try {
throw RuntimeException("Error inside coroutine")
} catch (e: Exception) {
println("Caught exception: ${e.message}")
}
}
// 2. 使用 CoroutineExceptionHandler,当协程中未捕获的异常发生时,会调用这个处理器。
//只有 顶层协程(root coroutine) 抛出的未捕获异常才会触发这里。
//如果异常在子协程中被 try-catch 捕获,或者 SupervisorJob 隔离了,就不会触发。
//_ → 是当前协程的上下文(CoroutineContext),通常不用;exception → 抛出的异常对象。
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job2 = GlobalScope.launch(exceptionHandler) {
throw RuntimeException("Error with handler")
}
// 3. async 中的异常处理
val deferred = GlobalScope.async {
throw RuntimeException("Error in async")
"Result"
}
GlobalScope.launch {
try {
deferred.await()
} catch (e: Exception) {
println("Async error caught: ${e.message}")
}
}
// 示例1:直接子协程的异常会被 handler 捕获
scope.launch {
throw RuntimeException("直接子协程异常") // 会被 exceptionHandler 捕获
}
// 示例2:子协程的子协程异常不会被 handler 捕获
scope.launch {
launch {
throw RuntimeException("孙子协程异常") // 不会被 exceptionHandler 捕获
}
}
// 示例3:async 的异常在 await 时抛出
val deferred = scope.async {
throw RuntimeException("async异常")
}
// 需要在调用 deferred.await() 时处理异常
}
Supervisor、异常隔离
supervisorScope {
val a = launch { /* may fail */ }
val b = launch { /* independent */ }
// a 抛异常不会取消 b
}
// 1. SupervisorScope
fun supervisorScopeExample() {
val scope = SupervisorScope()
scope.launch {
throw RuntimeException("Error in child coroutine")
}
scope.launch {
println("This will not be affected by the error")
}
}
// 2. 普通job,抛出异常,子协程被取消
fun exceptionIsolationExample() {
val scope = CoroutineScope(Job())
scope.launch {
throw RuntimeException("Error in child coroutine")
}
}
Android 常见用法
在 Activity/Fragment 中,使用 lifecycleScope 或 lifecycleScope.launchWhenStarted{}。
在 ViewModel 中,使用 viewModelScope(自动在 ViewModel 清理时取消)。
Compose 中,rememberCoroutineScope() 返回一个与 Composition 生命周期绑定的 CoroutineScope(适合触发单次协程任务,但对长期任务应使用 viewModelScope)。
// 1. 协程作用域
val scope = CoroutineScope(Dispatchers.IO)
// 2. 协程作用域 + 协程取消
val scope = CoroutineScope(Dispatchers.IO + Job())
// 3. 协程作用域 + 协程取消 + 协程异常处理
val scope = CoroutineScope(Dispatchers.IO + Job())
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
//4.自定义协程作用域
CoroutineScope(Dispatchers.IO + SupervisorJob() + exceptionHandler)
Dispatchers.IO 协程运行的调度器,适合 I/O 密集任务(如网络、文件读写)。底层线程池有很多线程。
SupervisorJob() 父 Job,用于管理子协程。与普通 Job() 的区别是:子协程失败不会取消其他兄弟协程。
exceptionHandler 协程异常处理器,用来处理作用域中未捕获的异常。
//例子2
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch { viewModel.doSomething() } // 只用于 UI 触发的短任务
}) { Text("Run") }
}
// 在 Android 中,需要在合适的生命周期取消
class MyActivity : AppCompatActivity() {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onDestroy() {
super.onDestroy()
scope.cancel() // 必须手动取消,避免内存泄漏
}
}
Job 是协程的生命周期控制器。 它表示一个可取消的任务,有四个核心状态: New → Active → Completing → Completed / Cancelled 当你创建协程时,内部其实都会自动创建一个 Job:
val job = launch { ... }
这个 job 控制协程的生命周期,比如:
job.cancel() // 取消协程
job.join() // 等待协程结束
fun jobExample() {
val job = Job()
val scope = CoroutineScope(Dispatchers.Default + job)
val child1 = scope.launch {
delay(1000)
println("Child 1 completed") // 可能不会执行
}
val child2 = scope.launch {
delay(2000)
println("Child 2 completed") // 可能不会执行
}
// 取消父Job会导致所有子协程取消
scope.launch {
delay(500)
job.cancel() // 这会取消 child1 和 child2
}
}
fun jobExceptionExample() {
val job = Job()
val scope = CoroutineScope(Dispatchers.Default + job)
scope.launch {
delay(100)
println("This will execute") // 会执行
}
scope.launch {
delay(500)
throw RuntimeException("Child failed!") // 这个异常会取消整个作用域
}
scope.launch {
delay(1500)
println("This won't execute") // 不会执行,因为作用域已被取消
}
}
SupervisorJob 是一个 监督者 Job,和普通 Job 最大的区别在于: ❗ 子协程失败时,不会影响其他兄弟协程或父作用域。 也就是说: 每个子协程可以独立失败;不会导致整个作用域被取消。 例子👇
fun supervisorJobCancelExample() {
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Default + supervisorJob)
val child1 = scope.launch {
delay(1000)
println("Child 1 completed") // 会正常执行
}
val child2 = scope.launch {
delay(2000)
println("Child 2 completed") // 会正常执行
}
scope.launch {
delay(500)
supervisorJob.cancel() // 取消 child1 和 child2!
}
}
fun supervisorExceptionExample() {
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Default + supervisorJob)
scope.launch {
delay(1000)
println("Task 1 completed") // 会正常执行
}
scope.launch {
delay(500)
throw RuntimeException("Task 2 failed!") // 只影响自己,不影响其他协程
}
scope.launch {
delay(1500)
println("Task 3 completed") // 会正常执行
}
}
适合普通 Job 的场景
// 用户操作 - 一组相关任务,要么全部完成,要么全部取消
fun userRegistration() {
val job = Job()
val scope = CoroutineScope(Dispatchers.IO + job)
scope.launch {
// 如果任何一个步骤失败,整个注册流程应该取消
val user = createUser()
val profile = createProfile(user)
sendWelcomeEmail(user)
println("Registration completed")
}
// 用户取消注册
fun cancelRegistration() {
job.cancel() // 取消所有相关任务
}
}
适合 SupervisorJob 的场景
// 仪表盘 - 多个独立的数据加载任务
fun loadDashboard() {
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + supervisorJob)
// 用户信息加载
scope.launch {
try {
val userInfo = loadUserInfo()
updateUI { showUserInfo(userInfo) }
} catch (e: Exception) {
updateUI { showUserInfoError() }
}
}
// 消息列表加载
scope.launch {
try {
val messages = loadMessages()
updateUI { showMessages(messages) }
} catch (e: Exception) {
updateUI { showMessagesError() }
}
}
// 通知加载
scope.launch {
try {
val notifications = loadNotifications()
updateUI { showNotifications(notifications) }
} catch (e: Exception) {
updateUI { showNotificationsError() }
}
}
// 即使加载用户信息失败,消息和通知仍然会继续加载
}
在 Android 中的实际使用
//ViewModel 中使用 SupervisorJob
class MyViewModel : ViewModel() {
private val supervisorJob = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Main + supervisorJob)
fun loadData() {
// 多个独立的数据加载任务
scope.launch { loadUserData() }
scope.launch { loadNewsFeed() }
scope.launch { loadNotifications() }
}
override fun onCleared() {
super.onCleared()
scope.cancel() // 取消所有协程
}
}
//与 supervisorScope 的配合使用
suspend fun parallelTasks() = supervisorScope {
// 在这个作用域内,所有子协程都遵循 SupervisorJob 规则
val task1 = async { loadData1() }
val task2 = async { loadData2() }
val task3 = async { loadData3() }
// 即使 task1 失败,task2 和 task3 的结果仍然可以获取
try {
val result1 = task1.await()
} catch (e: Exception) {
// 处理 task1 的异常
}
val result2 = task2.await() // 正常执行
val result3 = task3.await() // 正常执行
}
supervisorScope { ... }
SupervisorScope 是一个挂起函数; 它内部自动创建一个 SupervisorJob; 它只影响当前协程作用域,出了作用域就销毁。 等价关系:
supervisorScope { ... }
等价于
coroutineScope { withContext(SupervisorJob()) { ... } }
Kotlin Flow = 一个支持异步、可组合、可持续发射数据的流式数据结构。Flow 是协程世界里的 RxJava。 Flow 的两个核心特性 ① Flow 是异步的 Flow 内部可以使用任意 suspend function:
flow {
emit(loadFromNetwork()) // 可以挂起
}
② Flow 是可组合的(Operators)
和 Rx 类似:
map
.map { it * 2 }
filter
.filter { it > 10 }
debounce
节流(输入搜索框)
textInputFlow
.debounce(300)
distinctUntilChanged
只发射不重复的数据(UI 状态常用)
flatMapConcat / flatMapConcat / Latest
处理异步流的流
onEach
适合调试、打印、不改变值
catch
异常捕获
retry
自动重试网络
buffer
排队,逐个处理
conflate
只保留最
collectLatest
前一个任务取消
combine
网络 + 数据库多重 flow 合并
flow {
emit(api())
}.combine(dao.flow()) { a, b ->
...
}
Flow 能链式组合异步逻辑。
//一个标准 Flow 生命周期包含 3 个部分:
//1. 创建(Producer)
val f = flow {
emit(1)
emit(2)
}
//2. 变换(Operators)
f.map { it * 2 }
.filter { it > 2 }
//3. 收集(Collector)
f.collect { value ->
println(value)
}
//🔥 没有 collect,就不会执行(懒执行)
Flow = cold stream(冷流) 这意味着: 每次 collect 都会重新执行代码 每个 Collector 都会独立执行 Flow 内部逻辑 例子:
val f = flow {
println("Flow started")
emit(1)
}
f.collect()
f.collect()
//输出:
Flow started
1
Flow started
1
因此: Cold Flow = “你来订阅,我才开始发射” (就像按需取水的水龙头)
1. 默认初始值 StateFlow 默认有初始值,而 SharedFlow 没有 2. 缓存 StateFlow 缓存最近一次发射的值,而 SharedFlow 不缓存 3. 订阅 StateFlow 每次订阅都会重新发射最近一次的值,而 SharedFlow 只发射一次 SharedFlow:默认不能 除非你配置 replay,否则晚订阅者是啥都收不到的。
//replay = 0 → 不回放(默认) 晚订阅的观察者不会收到任何历史事件
val flow = MutableSharedFlow<Int>(replay = 0)
flow.emit(1)
flow.emit(2)
// 晚订阅
flow.collect { println(it) }
//输出:
(什么也不会打印)
//因为订阅时事件已经发过去了,不回放。
//replay = 1 → 回放最近 1 条事件,新订阅者会立即收到“最后一条”消息
val flow = MutableSharedFlow<Int>(replay = 1)
flow.emit(1)
flow.emit(2)
// 晚订阅
flow.collect { println(it) }
//输出:
2 ← replay 取的是最近的一条
//replay = N → 回放最近 N 条事件
val flow = MutableSharedFlow<Int>(replay = 2)
flow.emit(1)
flow.emit(2)
flow.emit(3)
// 晚订阅
flow.collect { println(it) }
//输出:
2
3
StateFlow = 状态容器(State Holder) 持续的状态 永远有值 UI 绑定(Compose / ViewModel) 可替代 LiveData。 SharedFlow = 事件广播器(Event Bus) 无状态 可以并行发送多个事件 适合“一次性事件” 用于: Snackbar 显示事件 导航事件 Toast 点击事件 一次性请求完成信号
stateIn 是 Flow 的扩展函数,可以把一个 普通 Flow 转换成 StateFlow。 特点: 把 Flow 变为 StateFlow,可以缓存最新值。 支持共享订阅:多个收集者共享同一个 Flow,上游不会重复执行。 可以设置初始值 (initialValue)。 可以控制启动策略 (SharingStarted)。 用法解析:
val flow = MutableSharedFlow<List<Int>>()
// 创建 StateFlow,并指定初始值 0
val stateFlow = flow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
逐个参数解释: scope = viewModelScope StateFlow 在这个 CoroutineScope 内启动上游 Flow ViewModel 销毁时,Flow 会自动取消 started = SharingStarted.WhileSubscribed(5000) 控制上游 Flow 的启动和停止策略 WhileSubscribed(5000) 意思: 当 有订阅者时,Flow 会启动 当 没有订阅者时,Flow 会延迟 5 秒后取消(避免短暂取消/重启) initialValue = emptyList() StateFlow 必须有初始值 初始值为空列表,UI 订阅时可以直接显示(不会空白或崩溃) Compose 中使用示例
@Composable
fun DueListScreen(viewModel: MemoryViewModel) {
val dueItems by viewModel.dueItems.collectAsState()
LazyColumn {
items(dueItems) { item ->
Text(text = item.title)
}
}
}
SharingStarted 三种模式
SharingStarted.Lazily
────────────────────
第一次有人订阅时启动上游
订阅者全部取消后 → 上游继续保持运行(不会停)
SharingStarted.Eagerly
──────────────────────
stateIn 被创建后 → 立即启动上游(无需订阅者)
订阅者取消后 → 上游继续运行(永不停)
SharingStarted.WhileSubscribed(timeout)
────────────────────────────────────────
有订阅者时 → 启动上游
没有订阅者 → 等待 timeout 毫秒后停止上游
timeout 期间若再次订阅 → 不会停止
(这是 ViewModel + Compose 最常用的模式)
代码对比示例 1️⃣用 stateIn 创建 StateFlow —— 永远有状态(有初始值)
val uiState: StateFlow<Int> = flow {
emit(1)
emit(2)
emit(3)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = 0
)
订阅结果(无论何时订阅) 永远可以立即拿到最新的数字,比如 3。 2️⃣ 用 shareIn 创建 SharedFlow —— 不保存状态
val events: SharedFlow<Int> = flow {
emit(1)
emit(2)
emit(3)
}.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
replay = 0
)
订阅结果 如果你订阅得太晚,你可能一个都收不到。🧠 那 ViewModel 什么时候用 shareIn / stateIn?
✔ 推荐使用 stateIn 的情况(90% UI 状态)
列表数据
UI 状态对象(UiState)
当前选中项
页面刷新状态
网络加载进度
✔ 推荐使用 shareIn 的情况(单次事件)
Toast 消息
Snackbar 事件
路由跳转事件
点击事件
一次性错误提示
⚠️ collect 是一个挂起函数(suspend)且会一直挂起等待下一条数据只要 Flow 没有结束(StateFlow 永远不会结束),collect 就不会返回。
只有 “冷流 Flow(Flow {})” 才可能结束,比如
flow {
emit(1)
emit(2)
emit(3)
}
这个 Flow 内部发射了 1、2、3 后就结束了。 它结束后: collect 会返回(停止挂起) 后续代码能继续运行 🔥 重点:StateFlow / SharedFlow 永远不会结束 为什么 StateFlow/SharedFlow 永不结束? 因为它们是: Hot Flow(热流) 是“始终存在的状态容器或事件通道” 用来持续发射 UI 状态或事件 设计上就不该“结束” collect 在它们上面做什么? collect 会一直挂起等待新值 等不到新值就一直挂着 所以后续代码永远执行不到 🔥 什么时候 SharedFlow / StateFlow 的 collect 会结束? 只有一种情况: collect 的协程被取消 例如: job.cancel() 或者作用域结束: viewModelScope.cancel() 但 流本身永远不会结束。