一些学习kotlin的知识小点。。

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.coroutineScopelifecycleOwner.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.coroutineScopelifecycleOwner.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 挂机函数
1691651194224.png

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:

1691653124441.png

结论:

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")
                    }
1691654053165.png

特殊的操作符

总会有一些特殊的情况,比如我只需要取前几个,我只要最新的数据等,不过在这些情况下,数据的发射就是并发执行的。

特殊 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"
            }
        }
    
1691659813900.png
  • 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"
        }
    }
    
1691660035392.png
展平流操作符
  • 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 操作是挂起的,非阻塞的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容