协程原理
Kotlin协程的本质是通过状态机管理挂起点,由编译器进行CPS变换实现的轻量级并发抽象。其核心原理和状态推进机制如下:
核心原理
1. 挂起函数
- 用suspend修饰的函数
- 编译器会将其编译为状态机代码(而非阻塞线程),支持在任意位置挂起/恢复
2. 续体
- 类似回调的接口
Continuation<T>
,其关键方法是resumeWith(result)
- 协程的每一步执行都依附于一个续体对象,存储当前执行状态和上下文
3. 状态机转换
- 编译器将挂起函数拆解成一个状态机(通过
label
标记状态) - 每个挂起点对应一个状态迁移
状态推进流程
以下代码展示状态机的运作:
suspend fun fetchData(): String {
val data1 = fetchPart1() //挂起点1
val data2 = fetchPart2() //挂起点2
return data1 + data2
}
编译器转换后(伪代码)
class FetchDataStateMachine(
val completion: Continuation<String>,
var label: Int = 0
) : Continuation<Unit> {
var data1: String? = null
var data2: String? = null
override fun resumeWith(result: Result<Any?>) {
when(label) {
0 -> {
label = 1
fetchPart1(this)
}
1 -> {
data1 = result.getOrThrow() as String
label = 2
fetchPart2(this)
}
2 -> {
data2 = result.getOrThrow() as String
completion.resumeWith(data1 + data2) //返回最终结果
}
}
}
}
关键机制
1. 挂起不阻塞线程:
- 协程挂起时,底层线程立即释放(例如返回到线程池),避免资源浪费
- 异步操作完成后,任务被派发到合适的线程继续执行(通过
Dispatcher
)
2.续体传递风格
- 挂起函数被编译为接受额外
Continuation
参数的函数 - 例如
suspend fun foo()
→fun foo(continuation: Continuation)
3. 协程上下文(CoroutineContext)
- 通过
CoroutineContext
传递调度器、异常处理器等。 - 状态机中通过
Continuation.context
获取当前上下文
4. 结构化并发
- 协程树通过父-子关系管理生命周期
- 父协程取消时,自动取消所有子协程
状态推进
在FetchDataStateMachine
的resumeWith
中并没有循环,label的状态是如何推进的呢?实际上状态推进是通过递归链式调用与间接跳转实现的。
gradle相关知识
一、插件(Plugins) vs 库(Libraries)
特征 | 插件 (Plugins) | 库 (Libraries) |
---|---|---|
本质 | 构建逻辑扩展工具 | 运行时依赖的代码组件 |
作用 | 添加任务/配置/目录结构 | 提供可调用的具体代码实现 |
声明位置 | plugins {} 块 |
dependencies {} 块 |
影响范围 | 构建过程 | 运行时或编译时 |
典型示例 | java ,android |
gson , junit |
1. 插件详解
核心作用:
- 添加新任务(如
compileJava
,assemble
) - 定义默认目录结构(如
src/main/java
) - 引入预置配置(如
implementation
依赖配置)
使用场景:
plugins {
id 'com.android.application' // Android APP插件
id 'org.jetbrains.kotlin.android' // Kotlin支持
}
2. 库详解
关键特征:
- 通过坐标声明:
group:name:version
(如com.google.guava:guava:32.0-jre
) - 传递依赖:库可能自带其他依赖(如 Retrofit 自动引入 OkHttp)
使用场景:
dependencies {
implementation 'androidx.core:core-ktx:1.12.0' // 主代码依赖
testImplementation 'junit:junit:4.13.2' // 测试代码专用
}
二、依赖配置详解
1. implementation
(最常用)
特点:
Android打包apk流程
android应用的打包流程是将代码、资源文件、清单文件等编译和压缩成可在设备上安装的APK/AAB文件的过程。以下是详细步骤:
一、主要流程
1. 编写代码与资源管理
- 创建
/src
目录存放Kotlin/Java源码 - 在
/res
目录添加资源 - 配置
AndroidManifest.xml
(声明组件、权限等)。
2. 依赖管理
- 在build.gradle中添加所需依赖库
3. 编译过程
- 编译代码: kotlin源码 →
.class
字节码(javac/kotlinc) - 转换为Dex:
.class
文件→.dex
文件(d8
/dx
工具),用于Android的ART虚拟机 - 编译资源:
AAPT2
编译资源文件(res/
→ 二进制格式),生成R.java
和临时资源包(.flat
)
4.打包与签名
- 合并资源: AAPT2链接编译后的资源,生成resources.arsc(资源索引表)和优化后的res/目录
- 打包成APK:APK Builder将以下文件合并为未签名的APK:
- 编译后的字节码(
.dex
) - 资源文件(
res/
+resources.arsc
) AndroidManifest.xml
- 原生库(
.so
,若有JNI)
- 编译后的字节码(
- 签名APK:使用签名证书(keystore)进行V1/V2/V3签名(通过
apksigner
或Gradle配置)
5.优化与对齐
- ZIP对齐:
zipalign
优化APK文件结构(4字节对齐),减少运行时内存占用 - 生成最终的APK:输出
app-release.apk
二、名词解释
1. 临时资源包
在AAPT2(Android Asset Packaging Tool 2
)的资源预编译阶段会生成.flat文件,这些文件是中间产物
- 独立编译:AAPT2将
/res
目录下的每个资源文件单独编译成二进制格式的.flat
文件 - 支持增量编译:若只修改了单个资源文件,只需重新编译该文件的.flat文件,避免全量编译,加快构建速度
- 分阶段处理
- 编译阶段:资源→
.flat
文件 - 链接阶段:合并所有
.flat
文件 → 生成resources.arsc
和最终的res/
目录
- 编译阶段:资源→
- 优势
- 提升大型项目的编译速度
- 支持资源混淆
- 更严格的资源验证
2. 对齐
- 内存对齐:解决CPU访问效率问题(
数据项首地址 % n == 0
),但会增加数据结构大小 - 文件对齐(zipalign):解决内存映射效率的问题(
文件偏移 % 4096 == 0
),通过消除跨页碎片减少运行时内存占用 - 内存页机制
- 系统内存管理以**页(通常4KB)**为单位
- 对齐后,每次文件读取 = 整数倍内存页 → 减少I/O次数