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")
}
// 获取序列前五项,迭代输出
singleNumber.take(5).forEach {
// 1,3,5,7,9
Log.d("liduo", "$it")
}
}
[]( )5.协程异步流
-------------------------------------------------------------------------
协程中提供了类似RxJava的响应式编程API——Flow(官方称为异步冷数据流,官方也提供了创建热数据流的方法)。
### []( )1)基础使用
// 在主线程上调用
GlobalScope.launch(Dispatchers.Main) {
// 创建流
flow<Int> {
// 挂起,输出返回值
emit(1)
// 设置流执行的线程,并消费流
}.flowOn(Dispatchers.IO).collect {
Log.d("liduo", "$it")
}
}.join()
emit方法是一个挂起方法,类似sequence中的yield方法,用于输出返回值。flowOn方法等同于Rxjava中的subscribeOn方法,用于切换flow执行的线程。为了避免理解混淆,Flow中没有提供类似Rxjava中的observeOn方法,但可以通过指定流所在协程的上下文参数确定。collect方法等同于RxJava中的subscribe方法,用于触发和消费流。
一个流可以被多次消费,示例代码如下:
GlobalScope.launch(Dispatchers.IO) {
val mFlow = flow<Int> {
emit(1)
}.flowOn(Dispatchers.Main)
mFlow.collect { Log.d("liduo1", "$it") }
mFlow.collect { Log.d("liduo2", "$it") }
}.join()
### []( )2)异常处理
Flow支持类似try-catch-finally的异常处理。示例代码如下:
flow {
emit(1)
// 抛出异常
throw NullPointerException()
}.catch { cause: Throwable ->
Log.d("liduo", "${cause.message}")
}.onCompletion { cause: Throwable? ->
Log.d("liduo", "${cause?.message}")
}
catch方法用于捕获异常。onCompletion方法等同于finally代码块。Kotlin不建议直接在flow中通过try-catch-finally代码块去捕获异常!
Flow中还提供了类似RxJava的onErrorReturn方法的操作,示例代码如下:
flow {
emit(1)
// 抛出异常
throw NullPointerException()
}.catch { cause: Throwable ->
Log.d("liduo", "${cause.message}")
emit(-1)
}
### []( )3)触发分离
Flow支持提前写好流的消费,在必要的时候再去触发消费的操作。示例代码如下:
// 创建Flow的方法
fun myFlow() = flow {
// 生产过程
emit(1)
}.onEach {
// 消费过程
Log.d("liduo", "$it")
}
suspend fun main() {
// 写法1
GlobalScope.launch {
// 触发消费
myFlow().collect()
}.join()
// 写法2
myFlow().launchIn(GlobalScope).join()
}
### []( )4)注意
* Flow中不提供取消collect的方法。如果要取消flow的执行,可以直接取消flow所在的协程。
* emit方法不是线程安全的,因此不要在flow中调用withContext等方法切换调度器。如果需要切换,可以使用channelFlow。
[]( )6.全局上下文
-------------------------------------------------------------------------
在本文中,启动协程使用的都是GlobalScope,但在实际开发过程中,不应该使用GlobalScope。GlobalScope会开启一个全新的协程作用域,并且不受我们控制。假设Activity页面关闭时,其中的协程还没有运行结束,并且我们还无法取消协程的执行,这时可能会导致内存泄漏。因此,在实际开发中,可以自定义一个全局的协程作用域,或者至少按照以下方法书写代码:
// 实现CoroutineScope接口
class MainActivity : AppCompatActivity(),CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 直接启动协程
launch {
Log.d("liduo", "launch")
}
}
override fun onDestroy() {
super.onDestroy()
// 取消顶级父协程
cancel()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
愿你有一天,真爱自己,善待自己。
override fun onDestroy() {
super.onDestroy()
// 取消顶级父协程
cancel()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-i3MkPWZ8-1711936556649)]
[外链图片转存中…(img-476DGboZ-1711936556649)]
[外链图片转存中…(img-KB0ogRTY-1711936556650)]
[外链图片转存中…(img-TCYDIwZz-1711936556650)]
[外链图片转存中…(img-qDgVYLu5-1711936556651)]
[外链图片转存中…(img-2MJ0oR7O-1711936556651)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-FD0afFJC-1711936556652)]
最后
愿你有一天,真爱自己,善待自己。