Kotlin 语言【知识点整理3】

目录

一、类与对象

1.定义与使用

[1.1 基础用法](#1.1 基础用法)

[1.2 主构造函数,直接在类名后声明](#1.2 主构造函数,直接在类名后声明)

[1.3 次构造函数(Secondary Constructor)用constructor 关键字定义:](#1.3 次构造函数(Secondary Constructor)用constructor 关键字定义:)

2.继承

3.抽象

4.接口

5.数据类

6.伴生对象

[6.1 声明方式](#6.1 声明方式)

[6.2 访问成员](#6.2 访问成员)

[6.3 默认名称](#6.3 默认名称)

7.可见性修饰符

[8.与 Java 的主要区别​](#8.与 Java 的主要区别)

二、列表与集合

1.创建集合

2.高频操作

[2.1 遍历集合](#2.1 遍历集合)

[2.2 数据类型转换](#2.2 数据类型转换)

[2.3 过滤数据(列表筛选)](#2.3 过滤数据(列表筛选))

[2.4 查找元素(代替循环)](#2.4 查找元素(代替循环))

3.常见用法和技巧

[3.1 RecyclerView数据更新](#3.1 RecyclerView数据更新)

[3.2 数据分组(如按分类显示)](#3.2 数据分组(如按分类显示))

[3.3 集合去重](#3.3 集合去重)

三、Lambda表达式

1.语法定义

2.常见用法

[2.1 事件监听(取代 Java 匿名内部类)](#2.1 事件监听(取代 Java 匿名内部类))

[2.2 集合操作(链式调用 + Lambda)](#2.2 集合操作(链式调用 + Lambda))

[2.3 作用域函数(let, apply, also)](#2.3 作用域函数(let, apply, also))

[2.4 关键注意事项](#2.4 关键注意事项)

四、协程

1.为什么需要协程?

2.协程与线程的关系

3.协程的核心组件

[3.1 协程作用域(CoroutineScope)](#3.1 协程作用域(CoroutineScope))

[3.2 协程构建器(Builders)](#3.2 协程构建器(Builders))

[3.3 调度器(Dispatchers)](#3.3 调度器(Dispatchers))

[3.4 挂起函数(Suspend Functions)](#3.4 挂起函数(Suspend Functions))

[3.5 协程上下文(CoroutineContext)](#3.5 协程上下文(CoroutineContext))

[3.6 协程任务(Job)](#3.6 协程任务(Job))

4.常见用法场景

[4.1 网络请求 + UI 更新](#4.1 网络请求 + UI 更新)

[4.2 并发执行多任务](#4.2 并发执行多任务)

[4.3 超时控制](#4.3 超时控制)

[4.4 异常统一处理](#4.4 异常统一处理)

5.Flow(流)

[5.1 flow的设计规则](#5.1 flow的设计规则)

[5.2 flow的基本用法](#5.2 flow的基本用法)

[5.3 flow操作符](#5.3 flow操作符)

[5.3.1 创建操作符(Flow Builders)](#5.3.1 创建操作符(Flow Builders))

[5.3.2 转换操作符(Transformation Operators)](#5.3.2 转换操作符(Transformation Operators))

[5.3.3 过滤操作符(Filtering Operators)](#5.3.3 过滤操作符(Filtering Operators))

[5.3.4 组合操作符(Combination Operators)](#5.3.4 组合操作符(Combination Operators))

[5.3.5 异常处理操作符(Exception Handling)](#5.3.5 异常处理操作符(Exception Handling))

[5.3.6 上下文操作符(Context Operators)](#5.3.6 上下文操作符(Context Operators))

[5.3.7 终端操作符(Terminal Operators)](#5.3.7 终端操作符(Terminal Operators))

[5.3.8 调试与辅助操作符(Debugging & Utility)](#5.3.8 调试与辅助操作符(Debugging & Utility))

[5.3.9 特殊流处理(Special Flows)](#5.3.9 特殊流处理(Special Flows))

操作符选择指南

[5.4 flow的常见用法](#5.4 flow的常见用法)

[5.4.1 网络请求分页加载](#5.4.1 网络请求分页加载)

[5.4.2 数据库实时监听](#5.4.2 数据库实时监听)


一、类与对象

  • :类是创建对象的模板。它定义了一类对象共有的属性(状态) 和 行为(方法/函数);
  • 对象:对象是类的一个具体实例(类似于java的 new Object)。它是根据类的定义在内存中创建出来的具体实体,拥有类定义的属性和方法。对象是真实存在的,一旦创建就会在内存中被分配空间。

1.定义与使用

1.1 基础用法

Kotlin 复制代码
class MyClass { 
    // 属性(成员变量)
    var property: Int = 0
    
    // 方法(成员函数)
    fun printValue() {
        println("Value: $property")
    }
}

使用

Kotlin 复制代码
// 初始化
val myObject = MyClass()

// 使用属性
myObject.property = 10  // 设置属性值
println(myObject.property)  // 获取属性值,输出: 10

// 调用方法
myObject.printValue()  // 输出: Value: 10

1.2 主构造函数,直接在类名后声明

Kotlin 复制代码
class Person(val name: String, var age: Int) {
    // 使用 `val/var` 直接声明属性
}

class Person(name: String) {
    val firstName: String/
   
    //也可以在init方法块中初始化一些属性
    init {
        firstName = name.uppercase()
    }
}

使用

Kotlin 复制代码
直接在类后面声明属性
// 初始化
val person1 = Person("Alice", 25)

// 使用属性
println(person1.name)  // 输出: Alice (val 不可变)
println(person1.age)   // 输出: 25
person1.age = 26       // 可以修改 (var 可变)
// person1.name = "Bob" // 错误,name 是 val 不可变

在init块中初始化
// 初始化
val person2 = Person("Bob")
// 使用属性
println(person2.firstName)  // 输出: BOB (因为init中有调用uppercase,所以firstName已被转为大写)

1.3 次构造函数(Secondary Constructor)用constructor 关键字定义:

Kotlin 复制代码
class Person(val name: String) {
    var age: Int = 0
    
    // 次构造函数必须委托给主构造函数
    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

使用

Kotlin 复制代码
// 使用主构造函数初始化
val person3 = Person("Charlie")

// 使用次构造函数初始化
val person4 = Person("David", 30)

// 使用属性
println(person4.name)  // 输出: David
println(person4.age)   // 输出: 30

2.继承

所有类默认继承Any(类似 Java 的 Object )。使用 open 允许继承,override重写成员

Kotlin 复制代码
//父类需要open,才允许继承
open class Animal(val name: String) {
    //父类函数需要open才能被重写
    open fun speak() = "Generic sound"
}

class Dog(name: String) : Animal(name) {
    override fun speak() = "Woof!"
}

3.抽象

定义部分实现的基类,要求子类实现抽象部分

Kotlin 复制代码
// 抽象类(不能实例化,包含抽象和具体实现)
abstract class Printer {
    abstract fun printDocument(document: String) // 抽象方法,必须由子类实现
    fun powerOn() = println("Printer powered on") // 具体方法
}
class LaserPrinter : Printer() {
    override fun printDocument(document: String) {
        println("Laser printing: $document")
    }
}

4.接口

Kotlin 复制代码
// 接口(定义契约,Kotlin接口可以有默认实现)
interface Clickable {
    fun click() // 抽象方法
    fun showOff() = println("I'm clickable!") // 带默认实现的方法
}
class Button : Clickable {
    override fun click() = println("Button clicked!")
}

5.数据类

在java中有很多常见的模板代码,但在kotlin中可以快速生成实现

Kotlin 复制代码
data class Point(val x: Int, val y: Int)

上面1句话的代码,等效于下面的java代码

java 复制代码
// Java 版需要手动实现所有方法
public class Point {
    private final int x;
    private final int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    // toString() 手动实现
    @Override
    public String toString() {
        return "Point(x=" + x + ", y=" + y + ")";
    }
    
    // equals() 手动实现
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }
    
    // hashCode() 手动实现
    @Override
    public int hashCode() {
        return 31 * x + y;
    }
    
    // copy() 方法需要自行创建
    public Point copyWith(int x, int y) {
        return new Point(x != null ? x : this.x, y != null ? y : this.y);
    }
    
    // 还需要手动实现 getters...
}

6.伴生对象

伴生对象是一个与类关联的特殊对象,主要用于替代 Java 的静态成员(静态方法和静态字段)。它允许在不创建类实例的情况下访问其成员。

6.1 声明方式

java 复制代码
class MyClass {
companion object MyConpanion {
    // 属性
    val age: Int = 100
    
    //方法
    fun loadImage(url: String): Int {

        var resultCode = 1

        try {
            println("加载了一张网络图片,地址是$url")
        } catch (e: Exception) {
            resultCode = 500
        }
        return resultCode
    }
}
}

6.2 访问成员

直接通过类名访问伴生对象成员(无需实例化):

Kotlin 复制代码
MyClass.companionMember // 类似 Java 的静态调用

6.3 默认名称

伴生对象默认名为Companion,也可自定义名称:

Kotlin 复制代码
companion object CustomName { ... }
// 访问:MyClass.CustomName.foo()

7.可见性修饰符

可见性修饰符用于控制类、函数、属性等元素的访问范围。它们决定了代码中的哪些部分可以访问被修饰的元素。Kotlin 提供了四种修饰符:public、private、protected 和 internal

  • public:默认修饰符,随处可见(任何文件、任何模块)
  • private:仅限声明它的文件或类内部访问
  • protected:仅限类内部和子类中访问(不能用于顶层声明)
  • internal:同一模块(Module)内可见(跨文件但同模块)

8.与 Java 的主要区别​

|-------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
| 特性 | Kotlin | Java |
| ​​主构造函数​​ | ​​声明在类名后​​ (class Person(val name: String)),简洁。参数可直接声明为属性(使用 val/var)。 | 没有单独的主构造语法。构造函数是与类同名的方法 (public Person(String name) { ... })。属性需要在类体中单独声明,并通常在构造函数中赋值。 |
| ​​默认可见性​​ | ​​public​​ (默认)。Kotlin 的成员默认就是 public。 | ​​包级私有 default​​ (默认)。Java 不写修饰符时,是包级私有。 |
| ​​final默认​​ | 类/方法默认是 ​​final​​。如果需要可继承/可重写,必须显式使用 open关键字。 | 类/方法默认是​​非 final​​。如果不需要继承/重写,必须显式声明为 final。 |
| ​​数据类​​ | 提供 ​​data类​​:自动生成 equals(), hashCode(), toString(), copy()方法。 | 需要手动实现或使用 IDE 生成或 Lombok 等库。没有语言内置支持。 |
| ​​new关键字​​ | ​​创建对象时不需要 new关键字​​:val myObj = MyClass()。 | ​​创建对象时必须使用 new关键字​​:MyClass myObj = new MyClass();。 |
| ​​单例对象​​ | 使用 ​​object声明​​:object MySingleton { ... },这是创建单例的最简单、推荐方式。 | 需要通过私有构造、静态实例变量和 getInstance()等方法手动实现单例模式。 |
| ​​伴生对象​​ | 使用 ​​companion object​​:在类内部声明,提供类似 Java 静态成员的作用,但本质是伴生对象的实例成员。更灵活(可以实现接口等)。 | 使用 ​​static成员变量和方法​​。 |
| ​​延迟初始化​​ | 提供 ​​lateinit​​ 关键字(用于 var)和 ​​by lazy​​ 委托(用于 val)。 | 需在构造时初始化 final变量或使用懒加载模式(如双重检查锁)。 |
| ​​属性​​ | 用 val(只读)或 var(可变)声明。底层可能有访问器(getter/setter)。 | ​​需要显式定义 getter/setter 方法(或者 public 字段不推荐)。 |
| ​​空安全​​ | ​​核心语言特性​​。类型默认非空。允许可空类型 Type?,访问时必须进行空检查或使用安全操作符 (?., ?:, !!)。 | ​​内置的空指针异常 (NPE)​​。没有强制机制,需要开发者小心处理,使用 @Nullable, @NotNull注解(IDE/框架辅助 |

二、列表与集合

Kotlin 的列表与集合是构建在 Java 集合框架之上的, Kotlin 通过大量的扩展函数为 Java 集合提供了丰富、流畅、函数式的操作(如 filter, map, groupBy 等)。

1.创建集合

Kotlin 复制代码
// 只读列表
val names = listOf("Alice", "Bob", "Charlie")
println(names[2])//这种写法更简洁,因此更为推荐,但会编译成get(2)的方式,所以两者完全是等价的
println(names.get(2))

// 可变列表(用于需要修改的场景)
//注意:这里val控制变量不可重新赋值、修改,而mutableListOf控制集合内容的可修改性
val tasks = mutableListOf("写代码", "修bug", "喝咖啡")
tasks.add("测试")

//键值对(代替Java的HashMap)
val userInfo = mapOf(
    "name" to "小明",
    "age" to 25,
    "isVIP" to true
)

//可变set集合
val fruits = mutableSetOf("Apple", "Banana", "Orange")
println(fruits) // 输出: [Apple, Banana, Orange]
fruits.add("Grape")  // 添加成功,返回 true
fruits.add("Apple")  // 添加失败("Apple" 已存在),返回 false
fruits.remove("Banana")  // 删除成功,返回 true
fruits.remove("Mango")   // 删除失败(不存在),返回 false
println(fruits)          // 输出: [Apple, Orange, Grape]


//空集合(避免NullPointerException)
val emptyList = emptyList<String>()
//假设有个函数需要返回空列表,那就可以这样用
fun findUsers(query: String): List<User> {
    return database.queryUsers(query) ?: emptyList() // 非 null 安全返回
}

顺便提供一些数组的常见用法,但是kotin的集合、列表功能更强大,数组的使用频率相对低

Kotlin 复制代码
// 1. 对象类型数组
val names = arrayOf("Alice", "Bob", "Charlie")

// 2. 原始类型数组(避免装箱开销)
val intArray = intArrayOf(1, 2, 3)       // 性能更好
val floatArray = floatArrayOf(1.5f, 2.5f)

// 3. 固定大小空数组
val emptyArray = arrayOfNulls<String>(3) // [null, null, null]

// 4. 动态初始化
val squares = Array(5) { i -> (i * i) } // [0, 1, 4, 9, 16]

// 5. 访问元素
val first = names[0]         // "Alice"
names[1] = "Robert"          // 修改元素

// 6. 安全访问(避免崩溃)
val safeValue = names.getOrNull(5) ?: "Default"

// 7. 遍历数组
names.forEach { name ->
    Log.d("Array", name)
}

// 8. 转换为集合(推荐后续使用集合操作)
val nameList = names.toList()

2.高频操作

2.1 遍历集合

Kotlin 复制代码
// 列表遍历 lambda写法
names.forEach { name ->
     println "用户名: $name")
}

//列表遍历  如果lambda只有1个参数,可以省略lambda,直接用it指代当前被遍历到的对象
names.forEach { println(it)}

// 带索引的遍历
tasks.forEachIndexed { index, task ->
     println("任务${index + 1}: $task")
}

// Map遍历
userInfo.forEach { (key, value) ->
    println("TAG", "$key -> $value")
}

//除此之外还有for的遍历方式,可以参考for循环用法示例

2.2 数据类型转换

假设我们正在进行网络请求,网络请求接口返回的数据实体类是ApiUser,其次还有User与布局关联,经常就需要把ApiUser转换成User:

Kotlin 复制代码
// API 返回的数据类
data class ApiUser(
    val id: Int,
    val username: String,
    val profileUrl: String
)

// 业务层的领域模型
data class User(
    val id: Int,
    val name: String,
    val avatar: String
)

fun main() {
    // 模拟 API 响应数据
    val apiResponse: List<ApiUser> = listOf(
        ApiUser(1, "user1", "https://example.com/avatar1.jpg"),
        ApiUser(2, "user2", "https://example.com/avatar2.jpg")
    )
    //map函数:把apiResponse列表中的每一个元素,通过大括号里面的转换逻辑,转成一个新的列表userList,类型是List<User>
    // 将 ApiUser 转换为 User
    val userList = apiResponse.map { apiUser ->
        User(
            id = apiUser.id,
            name = apiUser.username,
            avatar = apiUser.profileUrl
        )
    }

    println(userList)
}

2.3 过滤数据(列表筛选)

假设需要过滤出vip用户:

Kotlin 复制代码
data class User(val id: Int, val name: String, val isVIP: Boolean)

fun main() {
    val userList = listOf(
        User(1, "Alice", true),
        User(2, "Bob", false),
        User(3, "Charlie", true)
    )

    // 过滤出 VIP 用户
    //it 是集合元素的隐式参数,在这里我们用it代指 User 对象
    val vipUsers = userList.filter { it.isVIP }
    println(vipUsers) // 输出: [User(id=1, name=Alice, isVIP=true), User(id=3, name=Charlie, isVIP=true)]
}

filter的搜索功能:

Kotlin 复制代码
//商品类
data class Product(
    val id: Int,
    val name: String,
    val price: Double
)
//初始化一些数据
val products = listOf(
    Product(1, "iPhone 13", 699.0),
    Product(2, "Samsung Galaxy S22", 799.0),
    Product(3, "Google Pixel 6", 599.0),
    Product(4, "Xiaomi 12", 649.0),
    Product(5, "OnePlus 10 Pro", 729.0)
)
//假设用户搜索 pixel 希望得出"Google Pixel 6"并且忽略大小写:
val query = "pixel"
val searchResults = products.filter { 
    it.name.contains(query, ignoreCase = true) 
}

过滤空数据:

Kotlin 复制代码
val validData = sensorData.filterNotNull()

2.4 查找元素(代替循环)

Kotlin 复制代码
// 找第一个符合条件的元素
//这里的it代指列表中的对象
val currentUser = userList.find { it.id == userId }

// 检查是否存在至少一个满足条件的元素(比如用来检查是否有未读消息、未完成的任务)
val hasUnfinished = tasks.any { !it.isCompleted }

// 检查是否所有元素都满足条件(比如用来检查是否全选?)
val allDone = tasks.all { it.isCompleted }

3.常见用法和技巧

3.1 RecyclerView数据更新

Kotlin 复制代码
// Adapter中定义1个列表
private var _items = mutableListOf<Item>()
val items: List<Item> get() = _items//通过这种形式提供一个对外、可读的items,以确保内部数据安全

fun updateData(newItems: List<Item>) {
    _items.clear()//清除列表数据
    _items.addAll(newItems)//加入新的数据
    notifyDataSetChanged()//刷新
}

3.2 数据分组(如按分类显示)

Kotlin 复制代码
data class Product(val id: Int, val name: String, val category: String)

//productList的类型是List<Product>
val productList = listOf(
    Product(1, "手机", "电子产品"),
    Product(2, "衬衫", "服装"),
    Product(3, "笔记本电脑", "电子产品"),
    Product(4, "裤子", "服装")
)

//groupBy:将集合元素分组返回一个 Map
// 商品按类别分类:productsByCategory的类型是 Map<Category, List<Product>>
val productsByCategory = productList.groupBy { it.category }

输出:
{
    "电子产品" -> [Product(1, "手机", "电子产品"), Product(3, "笔记本电脑", "电子产品")],
    "服装" -> [Product(2, "衬衫", "服装"), Product(4, "裤子", "服装")]
}

3.3 集合去重

Kotlin 复制代码
//distinctBy:根据指定的条件去除集合中的重复元素
// 去除重复通知:去除msgId相同的对象
val uniqueNotifications = notifications.distinctBy { it.msgId }

三、Lambda表达式

一种简洁的、匿名函数的表示方式。不需要显式声明函数名,但可以有参数列表、函数体和返回值。使用lambda写法,可以让代码更简洁。在安卓中lambda写法常用于下面的场景:

  • Android 事件监听: setOnClickListener, setOnLongClickListener, TextWatcher 等;
  • 集合操作:使用 filter, map, sortedBy, groupBy, any, all, find, forEach 等标准库高阶函数进行数据处理和转换;
  • 作用域函数:let, run, with, apply, also。它们接收一个对象和一个Lambda,在Lambda内部提供一个临时的作用域,用于安全操作可空对象、初始化配置、链式调用等;
  • 线程操作: 将任务 (Runnable) 传递给线程 (Thread { ... }.start()) 或线程池 (executor.execute { ... });

1.语法定义

语法核心: { 参数列表 -> 函数体 }

  • 参数列表:在 -> 左侧声明,类型通常可推断。
  • 函数体:在 -> 右侧。如果函数体是单个表达式,其值自动作为返回值;如果是代码块,则需要显式使用 return

2.常见用法

2.1 事件监听(取代 Java 匿名内部类)

Kotlin 复制代码
// 点击事件
button.setOnClickListener { startActivity(Intent(this, NextActivity::class.java)) }

// 长按事件
button.setOnLongClickListener { 
    showDialog()
    true // 返回值(直接写最后一行)
}

2.2 集合操作(链式调用 + Lambda)

Kotlin 复制代码
val names = listOf("Alice", "Bob", "Anna")

// 过滤:保留以 "A" 开头的名字
val aNames = names.filter { it.startsWith("A") } // ["Alice", "Anna"]

// 映射:转换为长度
val lengths = names.map { it.length } // [5, 3, 4]

// 遍历
names.forEach { println("Name: $it") }

2.3 作用域函数(let, apply, also)

Kotlin 复制代码
// let:处理可空对象 + 安全调用
user?.let { 
    tvName.text = it.name 
    loadAvatar(it.url)
}

// apply:对象初始化(返回自身)
val intent = Intent().apply {
    putExtra("KEY", value)
    setAction("ACTION")
}

// also:额外操作(返回自身)
File("path").also { 
    println("创建文件: ${it.name}") 
}.writeText("Hello")

2.4 关键注意事项

  • return 的陷阱:Lambda 中直接写 return 会退出外层函数!需用标签返回
Kotlin 复制代码
list.forEach { 
   if (it == "exit") return@forEach // 只退出 Lambda
}
  • Lambda 参数类型:若编译器无法推断类型,需显式声明
Kotlin 复制代码
val callback: (String) -> Unit = { str -> println(str) }

四、协程

在移动开发领域,异步编程是很重要的。无论是网络请求、数据库访问,还是文件读写和定时任务,几乎所有耗时操作都需要在后台线程执行,以避免阻塞主线程造成界面卡顿。这一需求催生了多种异步解决方案的演进。早期的异步处理主要依赖Thread、AsyncTask和Handler等机制,但这些方案普遍存在一些痛点:

  1. 代码结构混乱:多层嵌套的回调形成"回调地狱"
  2. 异常处理复杂:错误传播路径不清晰
  3. 资源管理困难:需要手动控制线程生命周期

1.为什么需要协程?

Kotlin协程的诞生标志着异步编程范式的重大变革。kotlin通过以下几种核心特性,直接重塑开发体验:

  • 同步式编码:直接用顺序逻辑表达异步操作,彻底告别回调地狱Callback
Kotlin 复制代码
viewModelScope.launch {
    try {
        val token = login(username, password)  // 挂起点1
        val user = getUserInfo(token)        // 挂起点2 
        val orders = loadOrders(user.id)      // 挂起点3
        updateUI(orders)
    } catch (e: Exception) {
        handleError(e)
    }
}
  • 智能的线程调度:支持在多个线程间无缝切换,主线程与工作线程的切换成本极低,内置多种调度策略(IO/CPU密集型等)
  • 极致的资源效率:协程运行在线程上的轻量级任务单元,协程由编程语言层面实现,而非操作系统层面的实现,高效利用线程资源,内存占用仅为几KB级别,单台设备可轻松运行数十万协程(对比传统线程的数百个上限),创建和切换开销几乎可以忽略不计。
  • 完善的生命周期管理:支持结构化并发,提供协作式取消机制,自动避免内存泄漏风险。

2.协程与线程的关系

协程与线程并非替代关系,而是形成了高效的协作体系:

  • 运行载体:线程作为底层执行引擎,为协程提供运行环境
  • 资源复用:单个线程可同时承载多个协程(类似高速公路与车辆的关系)
  • 智能调度:协程可在不同线程间自由迁移(挂起时不阻塞线程)
  • 执行模型:协程的挂起/恢复完全由程序控制,不涉及系统内核调度

|-------|----------|----------|
| 特性 | 线程 | 协程 |
| 创建数量 | 数百个 | 数十万个 |
| 内存开销 | 高 (MB级) | 低 (KB级) |
| 切换成本 | 高 (系统调用) | 极低 (用户态) |
| 阻塞代价 | 整个线程阻塞 | 仅当前协程暂停 |
| 代码复杂度 | 回调/同步复杂 | 顺序编写简单 |

3.协程的核心组件

想要使用协程,我们应该先认识协程中的几个重要组件。掌握这些核心组件,我们就能优雅的处理很多异步场景。

3.1 协程作用域(CoroutineScope)

作用域是协程的 "管理者",负责跟踪和管理作用域内部启动的所有协程。

核心作用:

限制协程的生命周期:当作用域被取消时,其内部所有协程都会被取消(如 Activity 销毁时取消viewModelScope,避免泄漏)。

提供默认的协程上下文:作用域包含一个CoroutineContext,为协程提供默认的调度器、异常处理器等。

常见的作用域类型:

  • GlobalScope:全局作用域(慎用,生命周期与应用一致)
  • viewModelScope(在Android中才有):ViewModel 销毁时自动取消
  • lifecycleScope(在Android中才有):与 Activity/Fragment 生命周期绑定
Kotlin 复制代码
// ViewModel 中使用
class MyViewModel : ViewModel() {
    
    fun fetchData() {
        //开辟一个新的协程作用域
        viewModelScope.launch { // 作用域绑定 ViewModel,当viewModel被销毁,该作用域会自动取消所有子协程。从而防止内存泄露
            val data = repository.loadData()//具体的业务需求,比如加载某个数据
            _uiState.value = data
        }
    }
}

3.2 协程构建器(Builders)

构建器是启动协程的入口

常见的核心构建器

|-------------|-------------------|-------------|
| 构建器 | 特点 | 使用场景 |
| launch | 启动新协程,不返回结果 | 执行后台任务 |
| async | 启动新协程,返回 Deferred | 并发执行任务并获取结果 |
| runBlocking | 阻塞当前线程直到协程完成 | 测试/命令行程序 |

Kotlin 复制代码
// 并发请求示例
viewModelScope.launch {
    val userDeferred = async { getUser() }   // 异步获取用户
    val newsDeferred = async { getNews() }   // 异步获取新闻
    
    // 等待两个请求完成
    val user = userDeferred.await()
    val news = newsDeferred.await()
    
    showData(user, news) // 主线程更新UI
}

3.3 调度器(Dispatchers)

作用:指定协程运行的线程

四大调度器:

|------------------------|---------------|-------------|
| 调度器 | 用途 | 对应线程池 |
| Dispatchers.Main | Android UI 操作 | 主线程 |
| Dispatchers.IO | 网络/磁盘 I/O | 动态扩容线程池 |
| Dispatchers.Default | CPU 密集型计算 | 固定大小线程池 |
| Dispatchers.Unconfined | 无约束(不推荐) | 当前线程 → 恢复线程 |

Kotlin 复制代码
//开辟一个作用域,作用域内的操作在主线程完成,同时作用域内还有一个 withContext(Dispatchers.IO),这里面的任务需要在IO线程完成
viewModelScope.launch(Dispatchers.Main) {
    // 1. Dispatchers.Main指定在主线程启动一个任务
    val data = withContext(Dispatchers.IO) { 
        // 2. 切换到IO线程执行网络请求
        api.fetchData() 
    }
    // 3. 切回主线程更新UI(因为本身就是在main主线程)
    updateUI(data) 
}

withContext:临时切换协程执行上下文的,比如原先在Dispatchers.Main,可以通过withContext切到Dispatchers.IO,withContext是一个挂起函数,当它调用时,协程需要:

  • 挂起当前协程(释放原线程资源)
  • 切换到目标线程(如 IO 线程池)
  • 执行完代码块后恢复到原始线程

这种"挂起-切换-恢复"的能力必须依赖挂起函数的机制实现。

3.4 挂起函数(Suspend Functions)

标记可暂停执行的函数,不阻塞线程。用 suspend 关键字标记的函数。它的特殊能力是:可以在不阻塞当前线程的情况下暂停执行(挂起),并在适当的时候恢复执行。

  • 只能在协程或其他挂起函数中调用
  • 使用 suspend 关键字声明
  • 内部可调用其他挂起函数(如 delay, withContext)
Kotlin 复制代码
// 声明挂起函数
suspend fun fetchUser(): User {
    delay(1000) // 模拟耗时,不阻塞线程
    return withContext(Dispatchers.IO) {
        // 执行真实网络请求
        api.getUser()
    }
}

3.5 协程上下文(CoroutineContext)

作用:携带协程运行环境信息的容器

包含元素:

  • 调度器(指定线程)
  • 异常处理器(CoroutineExceptionHandler)
  • 协程名称(方便调试)
  • 父协程的 Job
Kotlin 复制代码
// 自定义上下文
val customContext = Dispatchers.IO + 
                    CoroutineName("MyCoroutine") +
                    CoroutineExceptionHandler { _, e ->
                        println("捕获异常: $e")
                    }

viewModelScope.launch(customContext) {
    // 在此上下文中运行
}

3.6 协程任务(Job)

作用:控制协程生命周期

关键操作:

  • job.start():启动协程(默认自动启动)
  • job.cancel():取消协程
  • job.join():等待协程结束
Kotlin 复制代码
val job = viewModelScope.launch {
    // 执行任务
}

// 当页面销毁时取消任务
override fun onDestroy() {
    super.onDestroy()
    job.cancel() // 避免内存泄漏
}

4.常见用法场景

4.1 网络请求 + UI 更新

Kotlin 复制代码
//这种没有指定调度器的写法,默认为main主线程
viewModelScope.launch {
    try {
        _uiState.value = Loading//显示加载框
        //切到io线程发起网络请求
        val data = withContext(Dispatchers.IO) { 
            api.fetchData() 
        }
        //拿到结果进行展示
        _uiState.value = Success(data)
    } catch (e: Exception) {
        //请求异常,显示错误结果
        _uiState.value = Error(e)
    }
}

4.2 并发执行多任务

Kotlin 复制代码
viewModelScope.launch {
    val result1 = async { task1() }//启动新协程
    val result2 = async { task2() }//启动新协程
    val combinedResult = result1.await() + result2.await()
}

4.3 超时控制

Kotlin 复制代码
viewModelScope.launch {
    try {
        val data = withTimeout(5000) { // 5秒超时
            fetchSlowData()
        }
    } catch (e: TimeoutCancellationException) {
        showToast("请求超时")
    }
}

4.4 异常统一处理

Kotlin 复制代码
val handler = CoroutineExceptionHandler { _, e ->
    Log.e("Coroutine", "全局异常: $e")
}

viewModelScope.launch(handler) {
    throw NetworkException("模拟错误")
}

5.Flow(流)

在 Android 开发中,有些场景只需要处理一次,而我们经常遇到需要多次更新数据的场景,用flow就能很好的适应这种需要多次更新数据的场景:

  1. 实时数据更新:数据库变化、传感器数据、WebSocket 消息
  2. 分页加载:分批获取网络数据
  3. 用户输入处理:搜索框连续输入时的实时搜索
  4. 多源数据组合:同时监听网络和数据库变化

5.1 flow的设计规则

  • 异步数据流:按需生产和消费数据

Flow 实现了这种"按需生产-消费"的模型,与传统的一次性异步操作不同,Flow 允许数据在时间维度上持续产生和消费。数据生产者只在有收集者有需要时才产生数据,且每次只发射单个值,消费者收到值后立即处理。

  • 结构化并发:自动跟随协程作用域取消

Flow 深度集成了 Kotlin 协程的生命周期感知能力。当启动 Flow 的协程作用域被取消时,Flow 会自动取消所有相关操作。例如在分页加载场景中,当用户快速滑动离开当前页面时,Flow 会自动取消尚未完成的网络请求,而开发者无需编写复杂的取消逻辑,大大减少了"回调地狱"和内存泄漏的风险。

  • 链式操作:类似集合 API 的直观处理

Flow 提供了丰富的声明式操作符,允许开发者以函数式编程风格处理数据流。这些操作符(如 map、filter、transform)形成可读性极高的链式调用,这种模式将原本分散的回调逻辑整合成自顶向下的线性流程,显著提升代码可维护性,同时保持异步操作的并发能力。

  • 线程切换:轻松切换 IO/主线程

Flow 通过 flowOn 操作符提供了优雅的线程控制方案。开发者可以明确指定每个处理阶段运行的线程上下文,Flow 会自动处理线程切换的底层细节。整个过程无需回调嵌套或手动切换线程,既保证了主线程不被阻塞导致的界面卡顿(ANR),又避免了传统多线程编程中的同步陷阱,使异步代码保持简洁直观。

5.2 flow的基本用法

假设我们正在开发一个天气应用,需要从多个城市获取温度数据:

Kotlin 复制代码
// 步骤1:创建 Flow - 模拟从三个城市获取温度
fun fetchCityTemperatures(): Flow<Int> = flow {
    // 模拟异步获取数据
    delay(100)  // 模拟网络延迟
    emit(25)    // 北京温度
    
    delay(100)  // 另一个网络请求
    emit(32)    // 上海温度
    
    delay(100)
    emit(18)    // 哈尔滨温度
}

转换数据 - 添加温度单位并过滤高温城市

Kotlin 复制代码
val processedTemperatures = fetchCityTemperatures()
    .map { temp -> "$temp°C" }       // 添加温度单位
    .filter { it.removeSuffix("°C").toInt() > 20 } // 只显示20°C以上的城市

收集数据 - 显示在UI上

Kotlin 复制代码
// 在Android的ViewModel中
class WeatherViewModel : ViewModel() {
    fun displayTemperatures() {
        viewModelScope.launch {
            processedTemperatures.collect { temperatureString ->
                // 更新UI显示
                _weatherData.value = "当前城市温度: $temperatureString"
            }
        }
    }
}

在上面这个案例中:

  • flow将多个异步操作整合到单一数据流中,
  • 每个温度数据到达时立即处理(map/filter),而不是等待所有数据到达
  • 结果:北京温度 → 显示"25°C" → 上海温度 → 显示"32°C" → 哈尔滨温度被过滤
  • 每次有新温度数据时自动更新UI,无需手动管理状态更新
  • 如果用户离开页面(ViewModel被清除),flow 自动取消,避免后台继续获取哈尔滨温度数据

5.3 flow操作符

Flow 提供了丰富的操作符来处理异步数据流,这些操作符可以分成几大类。以下是全面的 Flow 操作符分类和说明:

5.3.1 创建操作符(Flow Builders)

|--------------|----------|-------------------------------------------------------|
| 操作符 | 说明 | 示例 |
| flow{} | 基础构建器 | flow { emit(1) } |
| flowOf() | 从固定值创建 | flowOf(1, 2, 3) |
| asFlow() | 集合转Flow | listOf(1,2,3).asFlow() |
| channelFlow | 支持复杂发射逻辑 | channelFlow { send(1) } |
| callbackFlow | 封装回调API | callbackFlow { api.registerCallback { trySend(it) } } |

5.3.2 转换操作符(Transformation Operators)

|---------------|--------|-----------------------------------------|
| 操作符 | 说明 | 示例 |
| map | 转换每个元素 | .map { it * 2 } |
| transform | 更灵活的转换 | .transform { emit(it*2) } |
| mapLatest | 只处理最新值 | .mapLatest { fetchData(it) } |
| flatMapConcat | 顺序展开流 | .flatMapConcat { getDetails(it) } |
| flatMapMerge | 并发展开流 | .flatMapMerge { getDetails(it) } |
| flatMapLatest | 只处理最新流 | .flatMapLatest { getDetails(it) } |
| scan | 累积计算 | .scan(0) { acc, value -> acc + value } |

5.3.3 过滤操作符(Filtering Operators)

|----------------------|-------|-------------------------|
| 操作符 | 说明 | 示例 |
| filter | 条件过滤 | .filter { it > 10 } |
| take | 取前N个 | .take(5) |
| drop | 跳过前N个 | .drop(2) |
| debounce | 防抖 | .debounce(300) |
| distinctUntilChanged | 去重 | .distinctUntilChanged() |
| sample | 采样 | .sample(1000) |

5.3.4 组合操作符(Combination Operators)

|---------------|-------|------------------------------------------|
| 操作符 | 说明 | 示例 |
| combine | 组合最新值 | combine(flowA, flowB) { a, b -> a + b } |
| zip | 成对组合 | flowA.zip(flowB) { a, b -> a to b } |
| merge | 合并流 | merge(flowA, flowB) |
| flattenConcat | 顺序展开 | flowOf(flowA, flowB).flattenConcat() |
| flattenMerge | 并发展开 | flowOf(flowA, flowB).flattenMerge() |

5.3.5 异常处理操作符(Exception Handling)

|--------------|------|------------------------------------------------|
| 操作符 | 说明 | 示例 |
| catch | 捕获异常 | .catch { e -> emit(fallback) } |
| onCompletion | 流完成时 | .onCompletion { println("Done") } |
| retry | 重试 | .retry(3) { it is IOException } |
| retryWhen | 条件重试 | .retryWhen { cause, attempt -> attempt < 3 } |

5.3.6 上下文操作符(Context Operators)

|----------|---------|-------------------------|
| 操作符 | 说明 | 示例 |
| flowOn | 指定上游线程 | .flowOn(Dispatchers.IO) |
| buffer | 缓冲处理 | .buffer(10) |
| conflate | 合并快速发射值 | .conflate() |

5.3.7 终端操作符(Terminal Operators)

|----------|--------|-----------------------------------------|
| 操作符 | 说明 | 示例 |
| collect | 收集所有值 | .collect { println(it) } |
| toList | 转为列表 | .toList() |
| toSet | 转为集合 | .toSet() |
| first | 获取第一个值 | .first() |
| single | 获取唯一值 | .single() |
| count | 计数 | .count() |
| reduce | 累积计算 | .reduce { acc, value -> acc + value } |
| fold | 带初始值累积 | .fold(0) { acc, value -> acc + value } |
| launchIn | 在作用域启动 | .launchIn(viewModelScope) |

5.3.8 调试与辅助操作符(Debugging & Utility)

|---------------|--------|-----------------------------------------------|
| 操作符 | 说明 | 示例 |
| onStart | 流开始前 | .onStart { println("Starting") } |
| onEach | 每个值处理前 | .onEach { println("Emitting $it") } |
| onEmpty | 空流处理 | .onEmpty { emit(0) } |
| runningReduce | 实时累积 | .runningReduce { acc, value -> acc + value } |

5.3.9 特殊流处理(Special Flows)

|--------------|--------------|------------------------------------------------------------|
| 操作符 | 说明 | 示例 |
| stateIn | 转为StateFlow | .stateIn(viewModelScope, SharingStarted.Eagerly, 0) |
| shareIn | 转为SharedFlow | .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) |
| callbackFlow | 回调转Flow | 用于封装传统回调API |

操作符选择指南

|---------|------------------------------|
| 需求场景 | 推荐操作符 |
| 异步数据转换 | map 、transform |
| 复杂异步转换 | flatMapConcat 、 flatMapMerge |
| 响应快速输入 | debounce 、conflate |
| 合并多个数据源 | combine 、zip |
| 异常处理 | catch 、 retry |
| 线程切换 | flowOn |
| 生命周期感知 | stateIn 、shareIn |
| 资源清理 | onCompletion |

掌握这些操作符后,你可以优雅地处理各种异步数据流场景。建议从基础操作符开始练习,逐步掌握更高级的组合使用方式。

5.4 flow的常见用法

5.4.1 网络请求分页加载
Kotlin 复制代码
// ViewModel 中定义 Flow
class NewsViewModel : ViewModel() {
    // 创建分页数据流
    val newsFlow = flow {
        var page = 1
        while (true) {
            // 模拟网络请求(实际用 Retrofit)
            val news = fetchNewsFromNetwork(page)
            emit(news) // 发射数据
            
            if (news.isLastPage) break
            page++
        }
    }.flowOn(Dispatchers.IO) // 指定网络请求在IO线程

    private suspend fun fetchNewsFromNetwork(page: Int): List<News> {
        delay(1000) // 模拟网络延迟
        return List(10) { News("News $it - Page $page") }
    }
}

// Activity/Fragment 中收集
class NewsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 使用 lifecycleScope 确保生命周期安全
        lifecycleScope.launch {
            viewModel.newsFlow
                .catch { e -> showError("加载失败: ${e.message}") } // 异常处理
                .collect { newsList -> 
                    // 更新UI(自动切换主线程)
                    updateNewsList(newsList) 
                }
        }
    }
}
5.4.2 数据库实时监听
Kotlin 复制代码
// Room 返回 Flow
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getUsers(): Flow<List<User>> // Room 原生支持 Flow
}

// ViewModel 中处理
class UserViewModel(private val userDao: UserDao) : ViewModel() {
    
    val users: Flow<List<User>> = userDao.getUsers()
        .map { users -> 
            // 数据处理(例如:排序)
            users.sortedBy { it.name } 
        }
}

// Activity 中收集
lifecycleScope.launch {
    viewModel.users.collect { users ->
        // 数据库变化时自动触发更新
        recyclerView.adapter = UserAdapter(users)
    }
}
相关推荐
Source.Liu1 小时前
【Dioxus】Dioxus CLI (dx) 命令笔记
笔记·rust·dioxus
朝发如雪1 小时前
快速掌握Linux(4)(进程)
java·linux·服务器
2601_966949651 小时前
如何利用 1 分钟 K 线在 14:50 精准捕捉尾盘异动?Python 量化实战:1 分钟 K 线 + 全市场扫描
开发语言·人工智能·python·量化·quantdash·量化数据源
CarIise1 小时前
JavaScript基础语法与DOM操作实战课堂笔记
开发语言·javascript·笔记
pluviophile_s1 小时前
数据结构:第7讲:图
数据结构·笔记
JackSparrow4141 小时前
Kafka(七)集成Apache Avro+Apicurio Schema Registry以保障生产者与消费者的消息兼容性
java·中间件·rpc·kafka·apache·avro·schema-registry
林森lsjs1 小时前
零基础吃透二叉树:定义、遍历与高频算法 —数据结构柒
java·开发语言·数据结构·算法·二叉树
MacroZheng1 小时前
又一个神级画图Skill开源,再见draw.io!
java·人工智能·后端
2401_894915531 小时前
GEO 优化源码性能调优:高并发地域请求缓存与索引优化
java·服务器·网络·数据库·缓存