1. kotlin是init先执行还是成员变量先执行?
谁写前面谁先执行。
2. kotlin+databing 无法生成BR文件。
手动执行该task试试。
3. SparseArray put 重复的id会怎么样
val sparseArray = SparseArray<String>()
sparseArray.put(1, "one")
sparseArray.put(1, "two")
sparseArray 中键为 1 的值将为 "two"
4.生命感知组件协程范围
ViewModelScope
在这个范围内启动的协程会在viewmodel清除时自动取消,可以避免消耗资源
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
LifecycleScope
每个 Lifecycle
对象都定义了 LifecycleScope
,在此范围内启动的协程会在 Lifecycle
被销毁时取消.可以通过lifecycle.coroutineScope
或 lifecycleOwner.lifecycleScope
属性访问
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
...
}
}
}
5.协程
一、概念
协程可以使用阻塞的方式写出非阻塞的代码,解决并发常用的回调问题。
二、使用
GlobalScope.launch(Dispatchers.Main) {
val res = getResult(2)
mNumTv.text = res.toString()
}
分三部分:协程作用域(GlobalScope)、调度器(Dispatchers)、挂起函数
2.1 协程作用域
runBlocking:顶层函数,它和 coroutineScope
不一样,它会阻塞当前线程来等待,所以这个方法在业务中并不适用;
fun main() = runBlocking {
testFlow().collect {
Log.i("mali_test", "main: $it")
}
}
GlobalScope:全局协程作用域,可以在整个应用的声明周期中操作,且不能取消,所以仍用的时候要慎重,可能会引起泄漏
lifecycleScope:生命周期组件作用域,每个 Lifecycle
对象都定义了 LifecycleScope
,在此范围内启动的协程会在 Lifecycle
被销毁时取消.可以通过lifecycle.coroutineScope
或 lifecycleOwner.lifecycleScope
属性访问
viewLifecycleOwner.lifecycleScope.launch {
...
}
ViewModelScope:协程会在viewmodel清除时自动取消,可以避免消耗资源
自定义作用域:
class MainActivity : AppCompatActivity() {
// 1. 创建一个 MainScope
val scope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 2. 启动协程
scope.launch(Dispatchers.Unconfined) {
val one = getResult(20)
val two = getResult(40)
mNumTv.text = (one + two).toString()
}
}
// 3. 销毁的时候释放
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
private suspend fun getResult(num: Int): Int {
delay(5000)
return num * num
}
}
2.2 调度器
作用:限制协程在什么线程执行,主要的调度器有:
-
Dispatchers.Main
:指定执行的线程是主线程, -
Dispatchers.IO
:指定执行的线程是 IO 线程; -
Dispatchers.Default
:默认的调度器,适合执行 CPU 密集性的任务; -
Dispatchers.Unconfined
:非限制的调度器,指定的线程可能会随着挂起的函数的发生变化;
2.3 挂机函数
launch,启动一个新的协程,返回一个job,可以通过cancel
取消这个协程
async,创建一个协程,可以并发操作,调用await获取返回的值
scope.launch(Dispatchers.Unconfined) {
val one = async { getResult(20) }
val two = async { getResult(40) }
mNumTv.text = (one.await() + two.await()).toString()
}
2.3.1 suspent 修饰挂起函数的关键字。提醒调用者这个方法耗时
2.3.2 使用 withContext
切换到指定的 IO 线程去进行网络或者数据库请求;
private suspend fun getResult(num: Int): Int {
return withContext(Dispatchers.IO) {
num * num
}
}
2.3.3 使用 delay
方法去等待某个事件;
private suspend fun getResult(num: Int): Int {
delay(5000)
return num * num
}
三、Flow
创建一个flow
fun testFlow(): Flow<Int> {
return flow {
for (i in 1..10) {
delay(100)
Log.i(TAG, "testFlow emit: $i")
if (i == 2) throw java.lang.IllegalArgumentException("this is a fake error.")
emit(i)
}
}
}
createBtn("testFlow") {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
kotlinStarter.testFlow()
.flowOn(Dispatchers.IO)
.onCompletion {
logi(TAG, "onCompletion the flow value $it")
}
.catch { e ->
logi(TAG, "catch some error $e")
}
.collect {
logi(TAG, "collect the flow value $it")
}
}
}
输出log:
结论:
flowOn(Dispatchers.IO)
可以设置flow发射端的线程,在热流中是不生效的。
collect 订阅端运行的线程是lifecycleScope指定的。
3.1 操作符
普通操作符
-
map
:转换操作符,将 A 变成 B; -
take
:后面跟Int
类型的参数,表示接收多少个emit
出的值; -
filter
:过滤操作符;
kotlinStarter.testFlow()
.flowOn(Dispatchers.IO)
.onCompletion {
logi(TAG, "onCompletion the flow value $it")
}
.catch { e ->
logi(TAG, "catch some error $e")
}
.take(2) //取前2个
.map {
it + 5 //将数据改成另外一个值,会影响collect
}
.filter {
it % 2 == 0 //符合这个条件才会被collect
}
.collect {
logi(TAG, "collect the flow value $it")
}
特殊的操作符
总会有一些特殊的情况,比如我只需要取前几个,我只要最新的数据等,不过在这些情况下,数据的发射就是并发执行的。
特殊 Flow 操作符的作用:
-
buffer
:数据发射并发,collect
不并发; -
conflate
:发射数据太快,只处理最新发射的; -
collectLatest
:接收处理太慢,只处理最新接收的;
组合操作符
组合 Flow 操作符的作用:
-
zip
:组合两个流,双方都有新数据才会发射处理;fun zip(): Flow<String> { val flow = flowOf(1, 2, 3, 4, 5).onEach { delay(10) } val flow2 = flowOf("a", "b", "c", "d").onEach { delay(15) } return flow.zip(flow2) { num, name -> "$num:$name" } }
-
combine
:组合两个流,在经过第一次发射以后,任意方有新数据来的时候就可以发射,另一方有可能是已经发射过的数据;fun combine(): Flow<String> { val flow = flowOf(1, 2, 3, 4, 5).onEach { delay(10) } val flow2 = flowOf("a", "b", "c", "d").onEach { delay(15) } return flow.combine(flow2) { num, name -> "$num:$name" } }
展平流操作符
-
flatMapConcat
:串行处理数据; -
flatMapMerge
:并发collect
数据; -
flatMapLatest
:在每次emit
新的数据以后,会取消先前的collect
;
末端操作符
顾名思义,就是帮你做 collect
处理,collect
是最基础的末端操作符。
末端流操作符的作用:
-
collect
:最基础的消费数据; -
toList
:转化为List
集合; -
toSet
:转化为Set
集合; -
first
:仅仅取第一个值; -
single
:确保流发射单个值; -
reduce
:规约,如果发射的是Int
,最终会得到一个Int
,可做累加操作; -
fold
:规约,可以说是reduce
的升级版,可以自定义返回类型;
四、channel
Channel
是一个面向多协程之间数据传输的 BlockQueue。
实现协程之间的数据传输需要三步。
4.1 创建channel
- 直接创建
- 使用produce
2. 发送数据
发送数据使用的 Channel#send()
方法,当我们数据发送完毕的时候,可以使用 Channel#close()
来表明通道已经结束数据的发送。
3. 接收数据
正常情况下,我们仅需要调用 Channel#receive()
获取数据,但是该方法只能获取一次传递的数据,如果我们仅需获取指定次数的数据,可以这么操作:
lifecycleScope.launch {
// 1. 生成一个 Channel
val channel = Channel<Int>()
val channel2 = produce {
for(i in 1..5){
delay(200)
send(i * i)
}
close()
}
// 2. Channel 发送数据
launch {
for(i in 1..5){
delay(200)
channel.send(i * i)
}
channel.close()
}
// 3. Channel 接收数据
launch {
for( y in channel)//接收所有数据
Log.e(TAG, "get the data: $y from channel")
}
repeat(2){//接收2个数据
Log.e(TAG, "get the data: ${channel2.receive()} from channel2")
}
}
四、多协程数据处理
多协程处理并发数据的时候,原子性同样也得不到保证,协程中出了一种叫 Mutex
的锁,区别是它的 lock
操作是挂起的,非阻塞的