Kotlin

Kotlin中类型转换是通过as实现的
var v : View
v as Button
anko库
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")
线程

    创建并启动线程    
    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)

    }
解构声明 解构声明(Destructuring Declarations)是 Kotlin 中一项非常实用的语法特性,它允许你将一个对象分解成多个变量,从而简化代码并提高可读性。

    val (a, b, c) = someObject
    等价于
    val a = someObject.component1()
    val b = someObject.component2()
    val c = someObject.component3()
解构声明基于约定而非魔法 - 它依赖于对象的 componentN() 函数:编译器会为解构声明的每个变量调用对应的 componentN() 函数,component1() 对应第一个变量,component2() 对应第二个变量,以此类推 数据类(data class)自动支持解构,因为它们自动生成了 componentN() 函数,几点说明 1、解构的顺序必须与 componentN() 的顺序一致 2、不能跳过中间变量直接解构后面的变量(但可以用 _ 占位) 3、确保解构的变量数量不超过对象提供的 componentN() 数量

    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
Kotlin 集合也支持解构:注意:集合只提供 component1() 到 component5(),最多解构5个元素。

    val list = listOf(1, 2, 3)
    val (a, b, c) = list
    println("$a, $b, $c") // 输出: 1, 2, 3
在函数返回值中使用:函数可以返回一个数据类或 Pair/Triple,然后直接解构:

    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")
    }
下划线忽略不需要的值:如果只需要解构部分值,可以用 _ 忽略不需要的

    val (_, age) = person // 只解构age
自定义解构:对于非数据类,可以手动实现 componentN() 函数:

    class Point(val x: Int, val y: Int) {
        operator fun component1() = x
        operator fun component2() = y
    }
    
    val point = Point(10, 20)
    val (x, y) = point
一些使用例子:

    //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")
    }
when表达式

    var result = when {
        条件1 -> 结果1
        条件2 -> 结果2
        else -> 默认结果
    }
作用域函数
名称 功能 用法 返回结果 使用场景
also 在对象操作过程中执行额外的副作用(如日志记录)
使用 it 作为对象引用

val result = someObject.also {
    // 使用 it 访问 someObject 的属性和方法
    it.someProperty = "New Value"
    it.someMethod()
}
// result 是 someObject 本身
对象本身 打印日志或调试
监控对象状态变化
let 当你想对对象执行某些操作,并且需要返回一个新的结果时。
使用 it 作为对象引用

val result = nullableObject?.let {
    // 在非空对象上执行操作
    it.someMethod()
    it.anotherMethod()
    "Result" // 返回结果
}
最后一行的结果 空值处理 (?.let)
方法链式调用
run 对象初始化后,执行一组操作并返回结果。
使用 this 作为对象引用

val result = someObject.run {
    // 直接访问 someObject 的属性和方法
    someProperty = "New Value"
    someMethod()
    "Result" // 返回结果
}
最后一行的结果 执行初始化代码块
对象配置后直接返回结果
不依赖于调用者可以独立使用
直接写一个代码块run{}也是可以的
apply 配置对象属性,并返回对象本身。
使用 this 作为对象引用

val person = Person("Bob", 20).apply {
    age = 22
    println("Updated Age: $age")
}
println(person) // Person(name=Bob, age=22)
对象本身 对象初始化后直接修改属性
使用在对象构建者模式中
with 对已存在的对象执行操作,而不返回对象本身。
使用 this 作为对象引用

val person = Person("Alice", 30)
val result = with(person) {
    println("Hello, $name!")
    age + 5
}
println(result) // 35
最后一行的结果 对单个对象执行一系列操作
代码结构更简洁
扩展函数
名称 功能 定义和用法
use use 是 Kotlin 给 Closeable / AutoCloseable 对象扩展的一个函数

        //作用:在代码执行完 block 后 自动调用 close() 方法,即使发生异常也能关闭资源。
        //它和 Java 的 try-with-resources 作用一样。
        //适用于:InputStream / OutputStream
        //FileInputStream / FileOutputStream
        //BufferedReader / BufferedWriter
        //OkHttp Response
        //SQLite Cursor
        //ZipInputStream
        //任何实现 Closeable 的对象
        public inline fun <T : Closeable?, R> T.use(block: (T) -< R): R

        //读取文件:
        FileInputStream("test.txt").use { fis ->
            val content = fis.readBytes()
            println(String(content))
        }
        //不用手写:
        try {
            ...
        } finally {
            fis.close()
        }
        //Kotlin 会自动关闭 fis。
        //坑 1:block 返回后就关闭,不能继续使用
        val input = FileInputStream("a.txt").use { it } // WRONG
        input.read()  // ❌ 已经关闭了
        //因为 use{} 内 return 的就是“关闭后的对象”。
    
关于lambda表达式的一个例子

    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。

    val number = 10
    val result = number.takeIf { it > 0 }
    println(result) // 输出: 10
过滤数据时
条件检查并执行后续操作
替代简单的 if 判断
takeUnless 当对象不满足条件时,返回对象本身。
当对象满足条件时,返回 null。

    val number = -5
    val result = number.takeUnless { it < 0 }
    println(result) // 输出: null

    fun getUserName(name: String?): String {
        return name.takeUnless { it.isNullOrBlank() } ?: "Unknown User"
    }
    println(getUserName("Alice")) // 输出: Alice
    println(getUserName(""))      // 输出: Unknown User
处理异常或边界条件
防止无效数据传递
简化条件判断
Lambda表达式 Lambda表达式是定义匿名函数的简单形式,Lambda表达式避免了在抽象类和接口中编写明确的函数声明,进而也避免了类的实现部分,因此十分有用。在Kotlin中,可以将一个函数作为另一个函数的参数 一个Lambda表达式只有一个参数是很常见的,函数文本有时只有一个参数。如果Kotlin可以通过本身进行计算,那么可以省略这个唯一的参数,并通过it隐式声明它。 定义Lambda表达式两种方式

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 控制子协程生命周期(避免泄漏)。
常用构建器(Builder)

// 阻塞主线程直到内部完成,通常在 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 操作放这儿
}

挂起函数(Suspending Functions)
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 确保若内部某个子协程抛异常,会取消其它子协程并把异常向上抛(结构化并发)。

调度器(Dispatchers)

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

Job 是协程的生命周期控制器。
它表示一个可取消的任务,有四个核心状态:
New → Active → Completing → Completed / Cancelled
当你创建协程时,内部其实都会自动创建一个 Job:

val job = launch { ... }
这个 job 控制协程的生命周期,比如:

job.cancel()  // 取消协程
job.join()    // 等待协程结束
普通 Job 的特性(默认行为)
默认情况下,一个 Job 会形成父子层级结构,这被称为结构化并发。

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)
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 与 SupervisorJob 的区别
除了 SupervisorJob,还有一个作用域函数:

supervisorScope { ... }
SupervisorScope 是一个挂起函数;
它内部自动创建一个 SupervisorJob;
它只影响当前协程作用域,出了作用域就销毁。
等价关系:

supervisorScope { ... }
等价于
coroutineScope { withContext(SupervisorJob()) { ... } }

Flow

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 的三部分(最重要的基础)

//一个标准 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 的执行特性(很关键)
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 = “你来订阅,我才开始发射”
(就像按需取水的水龙头)

StateFlow和ShareFlow

StateFlow 和 SharedFlow 都是 Hot Flow(热流),但它们有 3 个核心本质差异:
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和shareIn
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()
但 流本身永远不会结束。