目录
[1.1 基础用法](#1.1 基础用法)
[1.2 主构造函数,直接在类名后声明](#1.2 主构造函数,直接在类名后声明)
[1.3 次构造函数(Secondary Constructor)用constructor 关键字定义:](#1.3 次构造函数(Secondary Constructor)用constructor 关键字定义:)
[6.1 声明方式](#6.1 声明方式)
[6.2 访问成员](#6.2 访问成员)
[6.3 默认名称](#6.3 默认名称)
[8.与 Java 的主要区别](#8.与 Java 的主要区别)
[2.1 遍历集合](#2.1 遍历集合)
[2.2 数据类型转换](#2.2 数据类型转换)
[2.3 过滤数据(列表筛选)](#2.3 过滤数据(列表筛选))
[2.4 查找元素(代替循环)](#2.4 查找元素(代替循环))
[3.1 RecyclerView数据更新](#3.1 RecyclerView数据更新)
[3.2 数据分组(如按分类显示)](#3.2 数据分组(如按分类显示))
[3.3 集合去重](#3.3 集合去重)
[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 关键注意事项)
[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.1 网络请求 + UI 更新](#4.1 网络请求 + UI 更新)
[4.2 并发执行多任务](#4.2 并发执行多任务)
[4.3 超时控制](#4.3 超时控制)
[4.4 异常统一处理](#4.4 异常统一处理)
[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.为什么需要协程?
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就能很好的适应这种需要多次更新数据的场景:
- 实时数据更新:数据库变化、传感器数据、WebSocket 消息
- 分页加载:分批获取网络数据
- 用户输入处理:搜索框连续输入时的实时搜索
- 多源数据组合:同时监听网络和数据库变化
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)
}
}