2024年安卓最全Kotlin协程:协程的基础与使用(2),面试阿里

最后

文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

  • Android进阶学习全套手册
    关于实战,我想每一个做开发的都有话要说,对于小白而言,缺乏实战经验是通病,那么除了在实际工作过程当中,我们如何去更了解实战方面的内容呢?实际上,我们很有必要去看一些实战相关的电子书。目前,我手头上整理到的电子书还算比较全面,HTTP、自定义view、c++、MVP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。

  • Android高级架构师进阶知识体系图
    关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!

  • Android对标阿里P7学习视频

  • BATJ大厂Android高频面试题
    这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// 用于注册协程执行结束的回调

fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle

...

}




### []( )1)协程状态的转换



![在这里插入图片描述](https://img-blog.csdnimg.cn/a081a96253f74fac97cdad15568cdab6.png#pic_center)  

    在**DEFAULT、ATOMIC、UNDISPATCHED**这三个模式下,启动协程会进入Active状态,而在**LAZY**模式下启动的协程会进入New状态,需要在手动调用start方法后进入Active状态。



    Completing是一个内部状态,对外不可感知。



### []( )2)状态标识的变化



| State | \[isActive\] | \[isCompleted\] | \[isCancelled\] |

| --- | --- | --- | --- |

| **New** | false | false | false |

| **Active** | true | false | false |

| **Completing** | true | false | false |

| **Cancelling** | false | false | true |

| **Cancelled** | false | true | true |

| **Completed** | fasle | true | false |



[]( )三.协程使用

========================================================================



[]( )1.协程的启动

-------------------------------------------------------------------------



### []( )1)runBlocking方法



fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T




    该方法用于在非协程作用域环境中启动一个协程,并在这个协程中执行lambda表达式中的代码。同时,调用该方法会阻塞当前线程,直到lambda表达式执行完毕。该方法不应该在协程中被调用,该方法设计的目的是为了让suspend编写的代码可以在常规的阻塞代码中调用。如果不设置协程调度器,那么协程将在当前被阻塞的线程中执行。示例代码如下:



private fun main() {

// 不指定调度器,在方法调用的线程执行

runBlocking {

    // 这里是协程的作用域

    Log.d("liduo", "123")

}

}

private fun main() {

// 指定调度器,在IO线程中执行

runBlocking(Dispatchers.IO) {

    // 这里是协程的作用域

    Log.d("liduo", "123")

}

}




### []( )2)launch方法



fun CoroutineScope.launch(

context: CoroutineContext = EmptyCoroutineContext,

start: CoroutineStart = CoroutineStart.DEFAULT,

block: suspend CoroutineScope.() -> Unit

): Job




    该方法用于在协程作用域中异步启动一个新的协程,调用该方法不会阻塞线程。示例代码如下:



private fun test() {

// 作用域为GlobalScope

// 懒启动,主线程执行

val job = GlobalScope.launch(

        context = Dispatchers.Main, 

        start = CoroutineStart.LAZY) {

    Log.d("liduo", "123")

}

// 启动协程

job.start()

}




### []( )3)async方法



fun CoroutineScope.async(

context: CoroutineContext = EmptyCoroutineContext,

start: CoroutineStart = CoroutineStart.DEFAULT,

block: suspend CoroutineScope.() -> T

): Deferred




    该方法用于在协程作用域中中异步启动一个新的协程,调用该方法不会阻塞线程。async方法与launch方法的不同之处在于可以携带返回值。调用该方法会返回一个Deferred接口指向的对象,调用该对象可以获取协程执行的结果。同时,Deferred接口继承自Job接口,因此仍然可以操作协程的生命周期。示例代码如下:



// suspend标记

private suspend fun test(): Int {

// 作用域为GlobalScope,返回值为Int类型,,泛型可省略,自动推断

val deffer = GlobalScope.async<Int> {

    Log.d("liduo", "123")

    // 延时1s

    delay(1000)

    1

}

// 获取返回值

return deffer.await()

}




    通过调用返回的Deferred接口指向对象的await方法可以获取返回值。在调用await方法时,如果协程执行完毕,则直接获取返回值。如果协程还在执行,则该方法会导致协程挂起,直到执行结束或发生异常。



### []( )4)suspend关键字



    suspend关键字用于修饰一个方法(lambda表达式)。suspend修饰的方法称为suspend方法,表示方法在执行中可能发生挂起。为什么是可能呢?比如下面的代码虽然被suspend修饰,但实际并不会发生挂起:



private suspend fun test() {

Log.d("liduo", "123")

}




    由于会发生挂起,因此suspend方法只能在协程中使用。suspend方法内部可以调用其他的suspend方法,也可以非suspend方法。但suspend方法只能被其他的suspend方法调用。



### []( )5)withContext方法



suspend fun withContext(

context: CoroutineContext,

block: suspend CoroutineScope.() -> T

): T




    该方法用于在当前协程的执行过程中,切换到调度器指定的线程去执行参数block中的代码,并返回一个结果。调用该方法可能会使当前协程挂起,并在方法执行结束时恢复挂起。示例代码如下:



private suspend fun test() {

// IO线程启动并执行,启动模式DEFAULT

GlobalScope.launch(Dispatchers.IO) {

    Log.d("liduo", "start")

    // 线程主切换并挂起,泛型可省略,自动推断

    val result = withContext<String>(Dispatchers.Main) {

        // 网络请求

        "json data"

    }

    // 切换回IO线程

    Log.d("liduo", result)

}

}




### []( )6)suspend方法



inline fun suspend(noinline block: suspend () -> R): suspend () -> R = block




    该方法用于对挂起方法进行包裹,使挂起方法可以在非挂起方法中调用。该方法需要配合createCoroutine方法启动协程。示例代码如下:



// 返回包含当前的协程代码的续体

val continuation = suspend {

// 执行协程代码

// 泛型可以修改需要的类型

}.createCoroutine(object : Continuation {

override val context: CoroutineContext

    get() = EmptyCoroutineContext + Dispatchers.Main



override fun resumeWith(result: Result<Any>) {

    // 获取最终结果

}

})

// 执行续体内容

continuation.resume(Unit)




    一般开发中不会通过该方法启动协程,但该方法可以更本质的展示协程的启动、恢复、挂起。



[]( )2.协程间通信

-------------------------------------------------------------------------



### []( )1)Channel



    Channel用于协程间的通信。Channel本质上是一个并发安全的队列,类似BlockingQueue。在使用时,通过调用同一个Channel对象的send和receive方法实现通信,示例代码如下:



suspend fun main() {

// 创建

val channel = Channel<Int>()



val producer = GlobalScope.launch {

    var i = 0

    while (true){

        // 发送

        channel.send(i++)

        delay(1000)

        // channel不需要时要及时关闭

        if(i == 10)

            channel.close()

    }

}



// 写法1:常规

val consumer = GlobalScope.launch {

    while(true){

        // 接收

        val element = channel.receive()

        Log.d("liduo", "$element")

    }

}



// 写法2:迭代器

val consumer = GlobalScope.launch {

    val iterator = channel.iterator()

    while(iterator.hasNext()){

        // 接收

        val element = iterator.next()

        Log.d("liduo", "$element")

    }

}



// 写法3:增强for循环

val consumer = GlobalScope.launch {

    for(element in channel){

        Log.d("liduo", "$element")

    }

}



// 上面的协程由于不是懒启动,因此创建完成直接就会start去执行

// 也就是说,代码走到这里,上面的两个协程已经开始工作

// join方法会挂起当前协程,而不是上面已经启动的两个协程

// 在Android环境中,下面两行代码可以不用添加

// producer.join()

// consumer.join()

}




    上述例子是一个经典的生产者-消费者模型。在写法1中,由于send方法和receive方法被suspend关键字修饰,因此,在默认情况下,当生产速度与消费速度不匹配时,调用这两个方法会导致协程挂起。  

除此之外,Channel支持使用迭代器进行接收。其中,hasNext方法也可能会导致协程挂起。



    Channel对象在不使用时要及时关闭,可以由发送者关闭,也可以由接收者关闭,具体取决于业务场景。



### []( )2)Channel的容量



    Channel方法不是Channel的构造方法,而是一个工厂方法,代码如下:



fun Channel(capacity: Int = RENDEZVOUS): Channel =

when (capacity) {

    RENDEZVOUS -> RendezvousChannel()

    UNLIMITED -> LinkedListChannel()

    CONFLATED -> ConflatedChannel()

    BUFFERED -> ArrayChannel(CHANNEL_DEFAULT_CAPACITY)

    else -> ArrayChannel(capacity)

}



    在创建Channel时可以指定容量:



*   **RENDEZVOUS**:创建一个容量为0的Channel,类似于SynchronousQueue。send之后会挂起,直到被receive。枚举值为0。

*   **UNLIMITED**:创建一个容量无限的Channel,内部通过链表实现。枚举值为Int.MAX\_VALUE。

*   **CONFLATED**:创建一个容量为1的Channel,当后一个的数据会覆盖前一个数据。枚举值为-1。

*   **BUFFERED**:创建一个默认容量的Channel,默认容量为kotlinx.coroutines.channels.defaultBuffer配置变量指定的值,未配置情况下,默认为64。枚举值为-2。

*   如果capacity的值不为上述的枚举值,则创建一个指定容量的Channel。



### []( )3)produce方法与actor方法



fun CoroutineScope.produce(

context: CoroutineContext = EmptyCoroutineContext,

capacity: Int = 0,

@BuilderInference block: suspend ProducerScope<E>.() -> Unit

): ReceiveChannel


fun CoroutineScope.actor(

context: CoroutineContext = EmptyCoroutineContext,

capacity: Int = 0,

start: CoroutineStart = CoroutineStart.DEFAULT,

onCompletion: CompletionHandler? = null,

block: suspend ActorScope<E>.() -> Unit

): SendChannel




    与launch方法和async方法相同,使用produce方法与actor方法也可以启动协程。但不同的是,在produce方法与actor方法中可以更简洁的使用Channel。示例代码如下:



// 启动协程,返回一个接收Channel

val receiveChannel: ReceiveChannel = GlobalScope.produce {

while(true){

    delay(100)

    // 发送

    send(1)

}

}

// 启动协程,返回一个发送Channel

val sendChannel: SendChannel = GlobalScope.actor {

while(true){

    // 接收

    val element = receive()

    Log.d("liduo","$element")

}

}




    produce方法与actor方法内部对Channel对象做了处理,当协程执行完毕,自动关闭Channel对象。



    但目前,produce方法还处于试验阶段(被ExperimentalCoroutinesApi注解修饰)。而actor方法也已经过时(被ObsoleteCoroutinesApi注解修饰)。因此在实际开发中最好不要使用!



### []( )4)BroadcastChannel



    当遇到一个发送者对应多个接收者的场景时,可以使用BroadcastChannel。代码如下:



fun BroadcastChannel(capacity: Int): BroadcastChannel =

when (capacity) {

    0 -> throw IllegalArgumentException("Unsupported 0 capacity for BroadcastChannel")

    UNLIMITED -> throw IllegalArgumentException("Unsupported UNLIMITED capacity for BroadcastChannel")

    CONFLATED -> ConflatedBroadcastChannel()

    BUFFERED -> ArrayBroadcastChannel(CHANNEL_DEFAULT_CAPACITY)

    else -> ArrayBroadcastChannel(capacity)

}



    创建BroadcastChannel对象时,必须指定容量大小。接收者通过调用BroadcastChannel对象的openSubscription方法,获取ReceiveChannel对象来接收消息。示例代码如下:



// 创建BroadcastChannel,容量为5

val broadcastChannel = BroadcastChannel(5)

// 创建发送者协程

GlobalScope.launch {

// 发送 1

broadcastChannel.send(1)

delay(100)

// 发送 2

broadcastChannel.send(2)

// 关闭

broadcastChannel.close()

}.join()

// 创建接收者1协程

GlobalScope.launch {

// 获取ReceiveChannel

val receiveChannel = broadcastChannel.openSubscription()

// 接收

for (element in receiveChannel) {

    Log.d("receiver_1: ", "$element")

}

}.join()

// 创建接收者2协程

GlobalScope.launch {

// 获取ReceiveChannel

val receiveChannel = broadcastChannel.openSubscription()

// 接收

for (element in receiveChannel) {

    Log.d("receiver_2: ", "$element")

}

}.join()




    每个接收者都可以收到发送者发送的每一条消息。使用扩展方法broadcast可以直接将Channel对象转化为BroadcastChannel对象,示例代码如下:



val channel = Channel()

val broadcastChannel = channel.broadcast(10)




    BroadcastChannel的很多方法也处于试验阶段(被ExperimentalCoroutinesApi注解修饰),使用时需慎重!



[]( )3.多路复用

------------------------------------------------------------------------



    协程中提供了类似Java中Nio的select方法,用于多路复用,代码如下:



suspend inline fun select(crossinline builder: SelectBuilder.() -> Unit): R




    以Channel的多路复用为例,具体看一下select方法的使用。示例代码如下:



private suspend fun test() {

// 创建一个Channel列表

val channelList = mutableListOf<Channel<Int>>()

// 假设其中有5个Channel

channelList.add(Channel())

channelList.add(Channel())

channelList.add(Channel())

channelList.add(Channel())

channelList.add(Channel())



// 调用select方法,协程挂起

val result = select<Int> {

    // 对5个Channel进行注册监听,等待接收

    channelList.forEach {

        it.onReceive

    }

}

// 当5个Channel中任意一个接收到消息时,select挂起恢复

// 并将返回值赋给result

Log.d("liduo", "$result")

}




    除此之外,协程中还有很多接口定义了名字为"onXXX"的方法,比如Job接口的onJoin方法,Deferred接口的onAwait方法,都是用于配合select方法来进行多路复用。



[]( )4.序列生成器

-------------------------------------------------------------------------



    协程中提供了sequence方法来生成序列。示例代码如下:



private suspend fun test() {

// 创建一个可以输出奇数的序列,泛型可省略,自动推断

val singleNumber = sequence<Int> {

    val i = 0

    while (true) {

        // 在需要输出的地方调用yield方法

        yield(2 * i - 1)

    }

}



// 调用迭代器,获取序列的输出

singleNumber.iterator().forEach {

    Log.d("liduo", "$it")

}



// 获取序列前五项,迭代输出

singleNumber.take(5).forEach {

    Log.d("liduo", "$it")

}

}




    调用yield方法会使协程挂起,同时输出这个序列当前生成的值。除此之外,也可以调用yieldAll方法来输出序列产生值的合集,示例代码如下:



private suspend fun test() {

// 创建一个可以输出奇数的序列,泛型可省略,自动推断

val singleNumber = sequence<Int> {

    yieldAll(listOf(1,3,5,7))

    yieldAll(listOf(9,11,13))

    yieldAll(listOf(15,17))

}



// 调用迭代器,获取序列的输出,最多为9项

singleNumber.iterator().forEach {

    Log.d("liduo", "$it")

}

自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

调用迭代器,获取序列的输出

singleNumber.iterator().forEach {

    Log.d("liduo", "$it")

}



// 获取序列前五项,迭代输出

singleNumber.take(5).forEach {

    Log.d("liduo", "$it")

}

}




    调用yield方法会使协程挂起,同时输出这个序列当前生成的值。除此之外,也可以调用yieldAll方法来输出序列产生值的合集,示例代码如下:



private suspend fun test() {

// 创建一个可以输出奇数的序列,泛型可省略,自动推断

val singleNumber = sequence<Int> {

    yieldAll(listOf(1,3,5,7))

    yieldAll(listOf(9,11,13))

    yieldAll(listOf(15,17))

}



// 调用迭代器,获取序列的输出,最多为9项

singleNumber.iterator().forEach {

    Log.d("liduo", "$it")

}

自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

[外链图片转存中…(img-BBBMJS7L-1715748849833)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值