文章目录
一. 集合
1. 安全获取集合元素
List.getOrElse(index: Int, defaultValue: (Int) -> T): T 函数原型 :
List.getOrNull(index: Int): T? 函数原型 :
2. mutableList
- 添加元素运算符 += 和 删除元素运算符 -=
运算符重载 : MutableList 集合 还有一些 运算符重载 , 添加元素运算符 += 和 删除元素运算符 -= ;
mutableList += "Jack"
mutableList -= "Tom"
//等价于
mutableList.add("Jack")
mutableList.remove("Tom")
- 通过 Lambda 表达式筛选要删除的元素
fun main() {
// 创建可变列表集合
val mutableList = mutableListOf("Tom", "Jerry")
// 通过 Lambda 表达式筛选要操作的元素
// 删除包含 字母 T 的元素
mutableList.removeIf {
it.contains("T")
}
println(mutableList)
}
3. list遍历
fun main() {
// 创建可变列表集合
val list = listOf("Tom", "Jerry", "Jack")
// 使用 for in 循环遍历
for (name in list) {
println("for : $name")
}
// 使用 forEach 遍历
list.forEach {
println("forEach : $it")
}
// 遍历时获取索引位置
list.forEachIndexed { index, s ->
println("$index : $s")
}
}
// 输出结果
for : Tom
for : Jerry
for : Jack
forEach : Tom
forEach : Jerry
forEach : Jack
0 : Tom
1 : Jerry
2 : Jack
4. List 通过解构一次性给多个元素赋值
fun main() {
// 创建可变列表集合
val list = listOf("Tom", "Jerry", "Jack")
// 使用 list 集合一次性给 2 个元素赋值, 第 1 个元素跳过
val (name1, _, name3) = list
println("name1 = $name1")
println("name3 = $name3")
}
//
name1 = Tom
name3 = Jack
5. Set集合
https://blog.youkuaiyun.com/shulianghan/article/details/128717667
二. 关键字
1. reified
reified关键字介绍
在Kotlin中,reified是一个特殊的关键字,用于修饰内联函数中的类型参数。这使得在函数内部可以访问类型参数的具体类型。通常情况下,由于类型擦除(type erasure),在运行时是无法直接获取泛型类型参数的具体类型的
2. noline / inline / crossline
- 局部返回
- inline 减少开销,避免重复创建fun
- noline 取消内联,一些场景下,需要把参数传给非内联函数
viewmodel源码
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: defaultViewModelProviderFactory
return ViewModelLazy(
VM::class,
{ viewModelStore },
factoryPromise,
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
)
}
三. 协程相关
1. 扔物线b站教程
https://www.bilibili.com/video/BV16S421d7tf?spm_id_from=333.788.videopod.sections&vd_source=695bce36c88a2f5a73d2b07a4041ec19
1.1 切线程
- 并发管理总结
1.切线程;
2.在各个线程之间等待各个线程;
3.互斥锁:并发安全 - 切线程原因
1.代码需要执行,不行被block
2.客户端特定切到UI主线程
java开发常用线程池实现 - 基本用法
CoroutineScope(EmptyCoroutineContext).launch {
// 把代码封装起来丢给线程池处理,类似于Java的Excute
}
- 管理任务执行的线程的工具 ContinuationInterceptor
官方默认实现4个 常用default和IO Main够用
public object Dispatchers {
// 默认的 8个
@kotlin.jvm.JvmStatic public final val Default: kotlinx.coroutines.CoroutineDispatcher /* compiled code */
// CPU 计算密集类型 IO工作是磁盘在做,CPU空闲; 64个
// 判断IO密集类型:跟磁盘/网络交互的
@kotlin.jvm.JvmStatic public final val IO: kotlinx.coroutines.CoroutineDispatcher /* compiled code */
// 主线程
@kotlin.jvm.JvmStatic public final val Main: kotlinx.coroutines.MainCoroutineDispatcher /* compiled code */
public final get
// Unconfined 调度器不会限制协程的执行线程。它的特点是:
// 当协程被启动时,它会在调用协程的线程上执行。
// 一旦协程挂起,它会恢复到调用 resume 的线程上,而不管最初的执行线程是什么。
// 适用场景:
// 轻量级的协程,特别是那些不涉及 UI 更新或不需要特定线程的任务
// 如果你的协程在被挂起时会影响性能或不需要线程上下文的场景。
@kotlin.jvm.JvmStatic public final val Unconfined: kotlinx.coroutines.CoroutineDispatcher /* compiled code */
@kotlinx.coroutines.DelicateCoroutinesApi public final fun shutdown(): kotlin.Unit { /* compiled code */ }
}
继承关系:Default -> CoroutineDispatcher -> ContinuationInterceptor
自定义管理器
val context = newFixedThreadPoolContext(5,"my_thread")
val context1 = newSingleThreadContext("my_thread_pool")
CoroutineScope(context).launch {
// TODO
}
context.close()
1.2 挂起函数
协程的优势:切完协程后还能切回来,也是相较于常规线程池最大的特点。
实现方法:挂起函数suspend。
挂起函数,挂起的是协程,继续执行挂起函数的逻辑,等待函数执行完成后,回到协程指定的线程,执行里面的逻辑
协程的挂起指的是,协程从它指定线程暂时离开;
挂起函数仅在协程里才有意义。
案例分析:contrubutors为suspend函数,切换到后台获取网络数据,再切回主线程更新UI
1.3 协程代码写法
// activity/fragment:
lifecycleScope.launch {
}
// viewModel
viewModelScope.launch {
}
1.4 手动切线程
withcontext
CoroutineScope(Dispatchers.Main).launch {
println(1)
withContext(Dispatchers.IO) {
delay(2000)
println(2)
}
println(3)
}
// out: 1/2/3
// 如果改成withContext换成launch,则输出 1/3/2
1.5 挂起函数的性能优势及原理(挂起函数为什么不卡主线程)
- 保证百分百把耗时操作放在挂起函数里去实现
// 把withContext逻辑也一起抽取出来
CoroutineScope(Dispatchers.Main).launch {
getData()
}
}
private suspend fun getData() {
withContext(Dispatchers.Default) {
// todo getData
}
}
- 原理
Android的主线程是一个无限循环的过程,在不停的刷新UI界面,同时监听各种点击和触摸事件
切换线程的底层原理都是:handler.post
挂起函数的实现原理就是在挂起函数开始前和后分别切换一次线程(回调),挂起函数调用时,实际上这时候已经离开主线程了,当然不卡主线程了
1.6 结构化并发
- 取消协程
job.cancel()
lifecycleScope.cancel() //不需要写,底层已实现
job 类似于 Rxjava中的dispose
- 父子协程
1.7 并行协程的启动与交互
- 两个请求串行执行
lifecycleScope.launch {
val timeBegin = System.currentTimeMillis()
val request1 = getData()
val request2 = getData()
updateView(request1 + request2)
val timeEnd = System.currentTimeMillis()
Log.d("TestKotlin", (timeEnd - timeBegin).toString())
}
private suspend fun getData() : List<String>{
withContext(Dispatchers.Default) {
delay(2000)
}
return mutableListOf("11")
}
// 输出4043
- 两个请求并行执行
通过async启动协程,返回deferred对象,通过await()可以获取协程的结果
lifecycleScope.launch {
coroutineScope {
val timeBegin = System.currentTimeMillis()
val deferred1 = async { getData() }
val deferred2 = async { getData() }
updateView(deferred1.await() + deferred2.await())
val timeEnd = System.currentTimeMillis()
Log.d("TestKotlin", (timeEnd - timeBegin).toString())
}
}
// 输出:2039
- coroutineScope
自动取消:在 coroutineScope 内部启动的所有协程在作用域结束时都会自动取消。
挂起函数:coroutineScope 是一个挂起函数,允许在其中使用其他挂起函数(如 delay 和 await)。
结构化并发:使用 coroutineScope 可以确保在同一作用域内启动的协程相互之间可以安全地协作,并且它们的执行顺序是可预测的。
- join函数
- 结论
- 使用 join: 当你启动一个不需要返回值的协程,并且你只想等待它完成时,例如在执行多个并发任务时,你想确保某个任务完成再继续执行后续代码。
- 使用 await: 当你启动一个需要返回值的协程(通过 async),并想在获取结果后继续处理,使用 await 是更合适的选择。
1.8 连接线程世界:和回调型 API 协作
- suspendCoroutine
把传统的回调函数变成挂起函数, it.resume()
- 捕获异常
try-catch
注意结构化并发的情况下 如何捕获子协程的异常 (这里讲的比较简单)
- 支持取消功能的协程suspendCancellableCoroutine
suspendCoroutine/线程API等不支持取消的功能,大多数场景还是需要取消功能
取消之后的操作 it.invokeOnCancellation { }
1.9 runBlocking()
runBlocking 是 Kotlin 协程库中的一个函数,它用于启动一个新的协程并阻塞当前线程,直到该协程执行完成。这个函数通常在主函数或测试代码中使用,以便在协程中执行挂起操作,同时保持代码的同步性。
基本概念
目的: runBlocking 允许你在非协程环境中执行协程代码,适用于需要阻塞当前线程以等待协程结果的场景。
阻塞线程: 与其他协程构建器(如 launch 和 async)不同,runBlocking 会阻塞调用它的线程,直到其内部的协程完成