Log.v("zx", "sleep")
delay(500)
}
}
}
如上,如果在一秒内没处理完,那么就会抛出异常 kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
那么如果网络请求在1秒内没返回,我们不想抛出异常,只想返回个默认值怎么办呢?
那么如果我们不想抛出异常,只想返回个null值的情况,该怎么做呢?
答:使用withTimeoutOrNull
runBlocking {
//1秒内处理完,如果在1秒内没处理完,那么就返回null来代替抛出异常
val result = withTimeoutOrNull(1000) {
repeat(10) {
Log.v("zx", "sleep")
delay(500)
}
"完成"
} ?: "默认数据"
Log.v("zx", "结果:$result")
}
打印:
com.z.zjetpack V/zx: sleep
com.z.zjetpack V/zx: sleep
com.z.zjetpack V/zx: 结果:默认数据
如上,如果在1秒内完成了,那么结果为 完成,如果没做完会返回结果null,为null即显示默认数据
[]( )协程的异常处理
---------------------------------------------------------------------
runBlocking {
val job1 = launch {
try {
throw NullPointerException()
} catch (e: Exception) {
log("launch,$e.toString()")
}
}
val job2 = async {
try {
throw NullPointerException()
} catch (e: Exception) {
log("async,$e.toString()")
}
}
job2.await()
}
异常的传播特性是:当一个协程生成异常,它会传给它的父级,之后父级会取消它自己的子级,然后取消它自己,最后将异常传给它的父级。
那么如果我们想要一个子协程发送异常不影响其他协程怎么办呢?
答:使用SupervisorJob和SupervisorScope
使用SupervisorJob时,一个子协程的运行失败不会影响到它的子协程。SupervisorJob不会传播异常给它的父级,他会让子协程自己处理异常
使用CoroutineExceptionHandler捕获协程异常
val handle = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.v("zx","$throwable")
}
CoroutineScope(Dispatchers.Main).launch(handle) {
throw NullPointerException()
}
Android种全局异常处理
全局异常处理器可以获取到所有协程未处理的未捕获异常,不管它并不能对异常进行捕获,虽然不能阻止程序崩溃,全局异常处理器在程序调试和异常上报场景有很大作用。
我们需要在app/src/main下面创建一个resources/META-INF/services目录并在其中创建一个名为kotlinx.coroutines.CoroutineExceptionHandler的文件,文件内容就是异常处理器的全类名。
![在这里插入图片描述](https://img-blog.csdnimg.cn/a194c4dd63aa490fae9a495510d114f3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWlpfR08=,size_20,color_FFFFFF,t_70,g_se,x_16)
package com.z.zjetpack.coroutine
import android.util.Log
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlin.coroutines.CoroutineContext
class GException:CoroutineExceptionHandler {
override val key = CoroutineExceptionHandler
override fun handleException(context: CoroutineContext, exception: Throwable) {
Log.v("zx","异常信息:$exception")
}
}
kotlinx.coroutines.CoroutineExceptionHandler文件中的内容为:包名+类名
com.z.zjetpack.coroutine.GException
![在这里插入图片描述](https://img-blog.csdnimg.cn/199f7f8a2e704dd79e007075f8191c23.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWlpfR08=,size_20,color_FFFFFF,t_70,g_se,x_16)
### []( )取消与异常
* 取消与异常紧密相关,协程内部使用CancellationException来取消,这个异常会被忽略,当子协程被取消时,不会取消它的父协程
runBlocking {
val job = launch {
val childjob = launch {
try {
delay(Long.MAX_VALUE)
}finally {
Log.v("zx","子协程被取消了")
}
}
//出让执行权,让子协程有机会执行
yield()
Log.v("zx","开始取消")
childjob.cancelAndJoin()
//childjob.cancel()
//没有AndJoin就会继续往下执行
//Log.v("zx","取消中。。。")
yield()
Log.v("zx","父协程还没被取消")
//父协程中释放资源
...
}
job.join()
}
打印:
com.z.zjetpack V/zx: 开始取消
com.z.zjetpack V/zx: 子协程被取消了
com.z.zjetpack V/zx: 父协程还没被取消
* 如果一个协程遇到了CancellationException以外的异常,它将使用该异常取消它的父协程。当父协程的所有子协程都结束后,异常才会被父协程处理。
runBlocking {
val handle = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.v("zx","捕获异常:$throwable")
}
val job1 = GlobalScope.launch(handle) {
val child1 = launch {
try {
delay(Long.MAX_VALUE)
}finally {
//这里如果要执行挂起函数要用NonCancellable
withContext(NonCancellable) {
Log.v("zx","child1子协程已被取消,但异常未被处理")
delay(100)
Log.v("zx","child1子协程已完成")
}
}
}
val child2 = launch {
delay(10)
Log.v("zx","child2 抛出异常")
throw NullPointerException()
}
}
job1.join()
}
打印:
com.z.zjetpack V/zx: child2 抛出异常
com.z.zjetpack V/zx: child1子协程已被取消,但异常未被处理
com.z.zjetpack V/zx: child1子协程已完成
com.z.zjetpack V/zx: 捕获异常:java.lang.NullPointerException
### []( )异常聚合
当协程的多个子协程因为异常而失败时,一般取第一个异常处理,在第一个异常后发生的所有异常都会绑定到第一个异常上。
runBlocking {
val handle = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.v("zx", "其他异常:${throwable.suppressed.contentToString()}")
Log.v("zx", "当前捕获异常:$throwable")
}
GlobalScope.launch(handle) {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw NullPointerException()
}
}
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw IndexOutOfBoundsException()
}
}
launch {
delay(100)
throw ArithmeticException()
}
}
}
打印:
其他异常:[java.lang.NullPointerException, java.lang.IndexOutOfBoundsException]
当前捕获异常:java.lang.ArithmeticException
[]( )(三)Flow异步流
========================================================================
[]( )flow介绍
--------------------------------------------------------------------
挂起函数可以异步返回单个值,那如何**异步多次**返回多个值呢?
使用flow,flow的特点:
* flow{…}块中的代码可以挂起
* 使用flow,suspend修饰符可以省略
* 流使用emit函数发射值
* 流使用collect的函数收集值
* flow类似冷流,flow中代码直到流被收集(调用collect)的时候才运行,类似lazy,什么时候用,什么时候执行。
* 流的连续性:流收集都是按顺序收集的
* flowOn可更改流发射的上下文,即可以指定在主线程或子线程中执行
* 与之相对的是热流,我们即将介绍的 StateFlow 和 SharedFlow 是热流,在垃圾回收之前,都是存在内存之中,并且处于活跃状态的。
//使用flow,suspend修饰符可以省略
fun doflow() = flow<Int> {
for (i in 1..5) {
//这里是挂起,不是阻塞
delay(500)
emit(i)
}
}.flowOn(Dispatchers.IO)
//调用
runBlocking {
doflow().collect {
log("value=$it")
}
}
打印(多次返回多个值)
com.z.zjetpack V/zx: value=1
com.z.zjetpack V/zx: value=2
com.z.zjetpack V/zx: value=3
com.z.zjetpack V/zx: value=4
com.z.zjetpack V/zx: value=5
[]( )flow的应用场景
-----------------------------------------------------------------------
文件下载场景
//正在下载(文件总大小为5)
fun doflow() = flow<Double> {
for (i in 1..5) {
delay(500)
emit(i.toDouble())
}
//flowOn来指定在IO线程中下载
}.flowOn(Dispatchers.IO)
//读取进度
runBlocking {
doflow().collect {
log("当前下载=${it / 5 * 100}%")
}
}
打印:
com.z.zjetpack V/zx: 当前下载=20.0%
com.z.zjetpack V/zx: 当前下载=40.0%
com.z.zjetpack V/zx: 当前下载=60.0%
com.z.zjetpack V/zx: 当前下载=80.0%
com.z.zjetpack V/zx: 当前下载=100.0%
[]( )流构建器
------------------------------------------------------------------
flowof 和asflow
runBlocking {
flowOf(1, 2, 3)
.onEach { delay(500) }
.collect {
log("value = $it")
}
(5..8).asFlow()
.onEach { delay(500) }
.collect {
log("value = $it")
}
}
使用launchin替换collect在单独的协程中启动收集流。
fun event() = (1..3)
.asFlow()
.onEach {
delay(500)
}.flowOn(Dispatchers.IO)
//调用
runBlocking {
val job = event().onEach {
log("value = $it")
}.launchIn(CoroutineScope(Dispatchers.IO))
//主线程可用this
//.launchIn(this)
job.join()
}
[]( )流的取消
------------------------------------------------------------------
超时的时候取消
fun cancelFlow() = flow<Int> {
for (i in 1..5) {
delay(1000)
emit(i)
}
}
//调用
runBlocking {
//超时的时候取消流
withTimeoutOrNull(2500) {
cancelFlow().collect {
log("value = $it")
}
}
}
打印:在2.5秒的时候超时了,取消了
com.z.zjetpack V/zx: value = 1
com.z.zjetpack V/zx: value = 2
直接取消
runBlocking {
cancelFlow().collect {
log("value = $it")
if(it == 3){
cancel()
}
}
}
繁忙的任务是不能直接取消的,需要检测取消(cancellable)
runBlocking {
(1..5).asFlow().cancellable().collect {
if(it == 3) {
cancel()
}
}
}
背压:生产者效率 > 消费者效率
![在这里插入图片描述](https://img-blog.csdnimg.cn/15cb9b79ba9840c98013f9fc145fbbd6.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWlpfR08=,size_20,color_FFFFFF,t_70,g_se,x_16)
**使用缓冲和flowon来处理背压**
buffer():并发运行流中发射元素的代码
conflate():合并发射项,不对每个值处理
collectLatest():取消并重新发送最后一个值
模拟背压代码:
fun preFlow() = flow<Int> {
for (i in 1..5) {
delay(100)
emit(i)
log("发送$i")
}
}
//调用
//100ms发送一次,300ms接收一次就产生了背压
runBlocking {
val time = measureTimeMillis {
preFlow()
//buffer可以增加缓冲,提高效率
//.buffer(100)
//flowOn自带缓冲功能
//.flowOn(Dispatchers.IO)
//conflate不对每个值处理
//.conflate()
//.collect
//取消并重新发送最后一个值
.collectLatest {
delay(300)
log("接收到:$it")
}
}
log("总耗时 $time")
}
打印:
com.z.zjetpack V/zx: 接收到:1
com.z.zjetpack V/zx: 发送1
com.z.zjetpack V/zx: 接收到:2
com.z.zjetpack V/zx: 发送2
com.z.zjetpack V/zx: 接收到:3
com.z.zjetpack V/zx: 发送3
com.z.zjetpack V/zx: 接收到:4
com.z.zjetpack V/zx: 发送4
com.z.zjetpack V/zx: 接收到:5
com.z.zjetpack V/zx: 发送5
com.z.zjetpack V/zx: 总耗时 2033
使用buffer后
com.z.zjetpack V/zx: 发送1
com.z.zjetpack V/zx: 发送2
com.z.zjetpack V/zx: 发送3
com.z.zjetpack V/zx: 接收到:1
com.z.zjetpack V/zx: 发送4
com.z.zjetpack V/zx: 发送5
com.z.zjetpack V/zx: 接收到:2
com.z.zjetpack V/zx: 接收到:3
com.z.zjetpack V/zx: 接收到:4
com.z.zjetpack V/zx: 接收到:5
com.z.zjetpack V/zx: 总耗时 1634
使用flowOn后
com.z.zjetpack V/zx: 发送1
com.z.zjetpack V/zx: 发送2
com.z.zjetpack V/zx: 发送3
com.z.zjetpack V/zx: 接收到:1
com.z.zjetpack V/zx: 发送4
com.z.zjetpack V/zx: 发送5
com.z.zjetpack V/zx: 接收到:2
com.z.zjetpack V/zx: 接收到:3
com.z.zjetpack V/zx: 接收到:4
com.z.zjetpack V/zx: 接收到:5
com.z.zjetpack V/zx: 总耗时 1639
使用conflate后
com.z.zjetpack V/zx: 发送1
com.z.zjetpack V/zx: 发送2
com.z.zjetpack V/zx: 发送3
com.z.zjetpack V/zx: 接收到:1
com.z.zjetpack V/zx: 发送4
com.z.zjetpack V/zx: 发送5
com.z.zjetpack V/zx: 接收到:3
com.z.zjetpack V/zx: 接收到:5
com.z.zjetpack V/zx: 总耗时 1034
使用collectLatest后
com.z.zjetpack V/zx: 发送1
com.z.zjetpack V/zx: 发送2
com.z.zjetpack V/zx: 发送3
com.z.zjetpack V/zx: 发送4
com.z.zjetpack V/zx: 发送5
com.z.zjetpack V/zx: 接收到:5
com.z.zjetpack V/zx: 总耗时 843
[]( )操作符
-----------------------------------------------------------------
转换操作符:map ,transform
限长操作符:取指定数量,take
末端操作符:末端操作符用于启动流收集的挂起函数,collect,tolist,toset,reduce,fold
组合操作符:zip
展平操作符:flatMapConcat(连接),flatMapMerge(合并),flatMapLatest(最新)
### []( )map
suspend fun perRequest(req: Int): String {
delay(1000)
return "转换 $req"
}
runBlocking {
(1..3).asFlow().map {
perRequest(it)
}.collect {
log(it)
}
}
打印:
com.z.zjetpack V/zx: 转换 1
com.z.zjetpack V/zx: 转换 2
com.z.zjetpack V/zx: 转换 3
### []( )transform
runBlocking {
(5…6).asFlow().transform {
emit("s $it")
emit(perRequest(it))
emit("e $it")
}
//.take(4)
.collect {
log(it)
}
}
打印:
com.z.zjetpack V/zx: s 5
com.z.zjetpack V/zx: 转换 5
com.z.zjetpack V/zx: e 5
com.z.zjetpack V/zx: s 6
com.z.zjetpack V/zx: 转换 6
com.z.zjetpack V/zx: e 6
### []( )take
加上take之后
com.z.zjetpack V/zx: s 5
com.z.zjetpack V/zx: 转换 5
com.z.zjetpack V/zx: e 5
com.z.zjetpack V/zx: s 6
### []( )末端操作符:collect,tolist,toset,reduce,fold
runBlocking {
val sum = (1..5).asFlow().map { it * it }.reduce { a, b -> a + b }
log("sum = $sum")
val nList = (1..5).asFlow().toList()
log("nList = $nList")
val nSet = listOf(1, 2, 2, 3, 3, 5).asFlow().toSet()
log("nSet = $nSet")
}
打印:
com.z.zjetpack V/zx: sum = 55
com.z.zjetpack V/zx: nList = [1, 2, 3, 4, 5]
com.z.zjetpack V/zx: nSet = [1, 2, 3, 5]
### []( )展平操作符
只使用map的时候
//返回值是一个flow
fun reqFlow(i: Int) = flow<String> {
emit("start $i")
delay(500)
emit("end $i")
}
runBlocking {
(0..1).asFlow().map {
reqFlow(it)
}.collect {
log("首次collect = $it")
it.collect {
log("二次 = $it")
}
}
}
打印:由于返回是flow所以需要collect 两次才能拿到值,Flow<Flow>
com.z.zjetpack V/zx: 首次collect = kotlinx.coroutines.flow.SafeFlow@63db1bf
com.z.zjetpack V/zx: 二次 = start 0
com.z.zjetpack V/zx: 二次 = end 0
com.z.zjetpack V/zx: 首次collect = kotlinx.coroutines.flow.SafeFlow@d27108c
com.z.zjetpack V/zx: 二次 = start 1
com.z.zjetpack V/zx: 二次 = end 1
#### []( )flatMapConcat
runBlocking {
(0..1).asFlow().flatMapConcat {
reqFlow(it)
}.collect {
log("首次collect = $it")
}
}
打印:直接展开了
com.z.zjetpack V/zx: 首次collect = start 0
com.z.zjetpack V/zx: 首次collect = end 0
com.z.zjetpack V/zx: 首次collect = start 1
com.z.zjetpack V/zx: 首次collect = end 1
### []( )
runBlocking {
(0…1).asFlow().flatMapMerge {
reqFlow(it)
}.collect {
log("首次collect = $it")
}
}
打印:
com.z.zjetpack V/zx: 首次collect = start 0
com.z.zjetpack V/zx: 首次collect = start 1
com.z.zjetpack V/zx: 首次collect = end 0
com.z.zjetpack V/zx: 首次collect = end 1
### []( )flatMapLatest
runBlocking {
(0..1).asFlow().flatMapLatest {
reqFlow(it)
}.collect {
log("首次collect = $it")
}
}
打印:
com.z.zjetpack V/zx: 首次collect = start 0
com.z.zjetpack V/zx: 首次collect = start 1
com.z.zjetpack V/zx: 首次collect = end 1
[]( )流的异常处理
--------------------------------------------------------------------
catch函数 和 try catch
flow {
emit(1)
throw NullPointerException()
//catch函数只捕获上游的异常
}.catch {
log("exception $it")
//在异常后恢复
emit(20)
}.flowOn(Dispatchers.IO)
.collect {
log("msg $it")
}
打印:
com.z.zjetpack V/zx: exception java.lang.NullPointerException
com.z.zjetpack V/zx: msg 1
com.z.zjetpack V/zx: msg 20
//不建议通过这种方式捕获上游的异常,违反了flow原则,这种适合捕获下游的异常
try {
(1..3).asFlow().collect {
check(it > 2) {
"ex $it"
}
}
} catch (e: Exception) {
log("异常 $e")
}
打印:
com.z.zjetpack V/zx: 异常 java.lang.IllegalStateException: ex 1
### []( )流的完成
finally 和 onCompletion
try {
(1..3).asFlow().collect {
check(it > 2) {
"ex $it"
}
}
} catch (e: Exception) {
log("异常 $e")
} finally {
log("流已完成")
}
//发生异常onCompletion可以拿到异常信息,但不会捕获
try {
(1..3).asFlow().onCompletion {
log("onCompletion $it")
}.collect {
check(it > 2) {
"ex $it"
}
}
} catch (e: Exception) {
log("异常 $e")
}
打印:
com.z.zjetpack V/zx: 异常 java.lang.IllegalStateException: ex 1
com.z.zjetpack V/zx: 流已完成
com.z.zjetpack V/zx: onCompletion java.lang.IllegalStateException: ex 1
com.z.zjetpack V/zx: 异常 java.lang.IllegalStateException: ex 1
[]( )StateFlow
-----------------------------------------------------------------------
StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。
1. StateFlow使用
第一步:创建 MutableStateFlow 并设置初始化的值。
class MainViewModel : ViewModel() {
val selected = MutableStateFlow<Boolean>(false)
}
第二步:同 Flow 一样,使用 collect 方法:
lifecycleScope.launch {
viewModel.selected.collect {
// ... 引起UI发生的变化
// 比如 某个按钮是否选中状态
}
}
第三步:可以给 selected设置值,从而引起 Ui 层的变化:
class MainViewModel : ViewModel() {
val selected = MutableStateFlow<Boolean>(false)
fun doSomeThing(value: Boolean) {
selected.value = value
}
}
普通的 Flow,是不具备 selected.value = value 这种能力的
StateFlow 和 LiveData 有什么区别?
有两点区别:
第一点,StateFlow 必须有初始值,LiveData 不需要。
第二点,当 View 变为 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据则不会取消注册使用方。
对于 StateFlow 在界面销毁的时仍处于活跃状态,有两种解决方法:
使用 ktx 将 Flow 转换为 LiveData。
在界面销毁的时候,手动取消(这很容易被遗忘)。
class LatestNewsActivity : AppCompatActivity() {
...
// Coroutine listening for UI states
private var uiStateJob: Job? = null
override fun onStart() {
super.onStart()
// Start collecting when the View is visible
uiStateJob = lifecycleScope.launch {
latestNewsViewModel.uiState.collect { uiState -> ... }
}
}
override fun onStop() {
// Stop collecting when the View goes to the background
uiStateJob?.cancel()
super.onStop()
}
}
[]( )SharedFlow
------------------------------------------------------------------------
SharedFlow:数据共享,有点类似广播
和 StateFlow 一样,SharedFlow 也是热流,它可以将已发送过的数据发送给新的订阅者,并且具有高的配置性。
1. SharedFlow使用场景
总的来说,SharedFlow 和 StateFlow 类似,他们都是热流,都可以用来存储状态,但 SharedFlow 配置灵活。
当你有如下场景时,需要使用 SharedFlow:
发生订阅时,需要将过去已经更新的n个值,同步给新的订阅者。
配置缓存策略。
2\. SharedFlow的使用
简单写一个 Demo吧。
第一步:创建一个 MutableSharedFlow,对应的参数解释在注释中
class MainViewModel : ViewModel() {
val sharedFlow = MutableSharedFlow<Int>(
5 // 参数一:当新的订阅者Collect时,发送几个已经发送过的数据给它
, 3 // 参数二:减去replay,MutableSharedFlow还缓存多少数据
, BufferOverflow.DROP_OLDEST // 参数三:缓存策略,三种 丢掉最新值、丢掉最旧值和挂起
)
}
第二步:使用emit或者tryEmit方法
class MainViewModel : ViewModel() {
val sharedFlow = MutableSharedFlow<Int>(
// ....
)
// 初始化时调用
init {
for (i in 0..10) {
sharedFlow.tryEmit(i)
}
}
// 在按钮中调用
fun doAsClick() {
for (i in 11..20) {
sharedFlow.tryEmit(i)
}
}
}
当 MutableSharedFlow 中缓存数据量超过阈值时,emit 方法和 tryEmit 方法的处理方式会有不同:
emit 方法:当缓存策略为 BufferOverflow.SUSPEND 时,emit 方法会挂起,直到有新的缓存空间。
tryEmit 方法:tryEmit 会返回一个 Boolean 值,true 代表传递成功,false 代表会产生一个回调,让这次数据发射挂起,直到有新的缓存空间。
第三步:接收数据
接收数据的方式,跟普通的 Flow 没什么区别。
下面是我的全部代码:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(com.example.coroutinedemo.viewmodel.MainViewModel::class.java)
val tvContent = findViewById<TextView>(R.id.tv_content)
// 启动第一个协程,接收初始化的数据
lifecycleScope.launch {
val sb = StringBuffer()
viewModel.sharedFlow.collect {
sb.append("<<${it}")
tvContent.text = sb
}
}
val btnGo = findViewById<Button>(R.id.btn_go)
val tvTwo = findViewById<TextView>(R.id.tv_2)
btnGo.setOnClickListener {
// 发送新的数据
viewModel.doAsClick()
// 发送新的数据以后,启动第二个协程
lifecycleScope.launch {
val sb = StringBuffer()
viewModel.sharedFlow.collect {
sb.append("<<${it}")
tvTwo.text = sb.toString()
}
}
}
}
}
3. 将冷流转化为SharedFlow
直接使用官网的代码,方法是使用 Flow 的扩展方法 shareIn:
class NewsRemoteDataSource(…,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed() // 启动政策
)
}
重点是参数三,分别提供了三个启动策略:
SharingStarted.WhileSubscribed():存在订阅者时,将使上游提供方保持活跃状态。
SharingStarted.Eagerly:立即启动提供方。
SharingStarted.Lazily:在第一个订阅者出现后开始共享数据,并使数据流永远保持活跃状态。
总结
Flow 给我的感觉就像古老的印刷术,版面定了就不可更改,不过,该版面可印刷多张内容;StateFlow 给我的感觉就像活字印刷,可以不停的更改版面,也可以使用同一个版面印刷很多内容。
如果你要使用 Flow 记录数据的状态,StateFlow 和 SharedFlow 会是一个不错的选择。StateFlow 和 SharedFlow 提供了在 Flow 中使用 LiveData 式更新数据的能力,但是如果要在 UI 层使用,需要注意生命周期的问题。
StateFlow 和 SharedFlow 相比,StateFlow 需要提供初始值,SharedFlow 配置灵活,可提供旧数据同步和缓存配置的功能。
[协程进阶技巧 - StateFlow和SharedFlow]( )
[]( )(四)协程并发
=====================================================================
[]( )认识channel
-----------------------------------------------------------------------
channel是一个并发安全的队列,可以连接协程,实现不同协程的通信。
![在这里插入图片描述](https://img-blog.csdnimg.cn/63a0e470b4a4495ab30b018a96e06dd0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWlpfR08=,size_20,color_FFFFFF,t_70,g_se,x_16)
Library中定义了几种类型的Channel。 它们在内部能够存储多种元素,只是在send调用是否能够挂起方面有所不一样。 对于全部通道类型,receive调用的行为方式相同:若是通道不为空,则接收元素,不然将挂起。
Unlimited channel
无限制通道(Unlimited channel)是最接近队列的模拟:生产者能够将元素发送到此通道,而且它将无限增加。 send方法将永远不会被挂起。 若是没有更多的内存,则会抛出OutOfMemoryException。 和队列不一样的是当使用者尝试从空通道接收消息并被挂起直到有一些新元素发送到该通道时继续使用。
Buffered channel
缓冲通道(Buffered channel)的大小受指定数字的限制。 生产者能够将元素发送到此通道,直到达到最大限制。 全部元素都在内部存储。 通道已满时,下一个send呼叫将被挂起,直到出现更多可用空间。
Rendezvous channel
"约定"通道(Rendezvous channel)是没有缓冲区的通道。 这与建立大小为零的缓冲通道(Buffered channel)相同。 其中一个功能(send或receive)始终被挂起,直到调用另外一个功能为止。 若是调用了send函数,但消费者没有准备好处理该元素则receive会挂起,而且send也会被挂起。 一样,若是调用了receive函数且通道为空,换句话说,没有准备好发送该元素的的send被挂起-receive也会被挂起。
Conflated channel
发送到合并通道( Conflated channel)的新元素将覆盖先前发送的元素,所以接收方将始终仅能获取最新元素。 send调用将永远不会被挂起。
建立通道时,指定其类型或缓冲区大小(若是须要缓冲的通道):
val rendezvousChannel = Channel()
val bufferedChannel = Channel(10)
val conflatedChannel = Channel(CONFLATED)
val unlimitedChannel = Channel(UNLIMITED)
默认状况下,会建立一个"约定"通道(Rendezvous channel)。
在如下示例中,将建立一个"约定"通道,两个生产者协程和一个消费者协程:
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*
fun main() = runBlocking {
val channel = Channel<String>()
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
launch {
repeat(3) {
val x = channel.receive()
log(x)
}
}
}
fun log(message: Any?) {
println("[${Thread.currentThread().name}] $message")
}
以上将会打印以下结果:
[main @coroutine#4] A1
[main @coroutine#4] B1
[main @coroutine#2] A done
[main @coroutine#3] B done
[main @coroutine#4] A2
channel实际上是一个队列,队列中一定存在缓冲区,这个缓冲区满了并且一直没有人调用receive取走函数,send就需要挂起,故意让接收端的节奏放慢,发现send总是被挂起,直到receive之后才会继续往下执行。
fun run1() {
val channel = Channel<Int>(Channel.UNLIMITED)
//Channel协程间通信,并发安全的队列
runBlocking {
//生产者
val p = launch {
for (i in 1..5) {
channel.send(i)
log("send = $i")
}
}
//消费者
val c = launch {
//正常接收数据
while (true) {
//故意让接收端的节奏放慢,发现send总是被挂起,直到receive之后才会继续往下执行
delay(2000)
val el = channel.receive()
log("re = $el")
}
//通过迭代器iterator接收数据
//val iterator = channel.iterator()
//while (iterator.hasNext()) {
// delay(2000)
// log("iterator = ${iterator.next()}")
//}
}
joinAll(p,c)
}
}
打印:
com.z.zjetpack V/zx: send = 1
com.z.zjetpack V/zx: send = 2
com.z.zjetpack V/zx: send = 3
com.z.zjetpack V/zx: send = 4
com.z.zjetpack V/zx: send = 5
com.z.zjetpack V/zx: re = 1
com.z.zjetpack V/zx: re = 2
com.z.zjetpack V/zx: re = 3
com.z.zjetpack V/zx: re = 4
com.z.zjetpack V/zx: re = 5
[]( )produce 与actor
----------------------------------------------------------------------------
* 构造生产者与消费者的便捷方法
* 我们可以通过produce方法启动一个生产者协程,并返回一个reveive channel,其他协程就可以用这个channel来接收数据了。反过来我们可以用actor启动一个消费者协程。
fun run2(){
runBlocking {
//快捷创建生产者协程,返回一个接收Channel
val receiveChannel = produce<Int> {
repeat(5){
delay(1000)
send(it)
}
}
val job2 = launch {
for (i in receiveChannel) {
log("receiveChannel = $i")
}
}
job2.join()
}
runBlocking {
//构造消费者的便捷方法
val sendChannel = actor<Int> {
while (true) {
val re = receive()
log("re = $re")
}
}
val p = launch {
for (i in 1..3) {
sendChannel.send(i)
}
}
p.join()
}
}
打印:
com.z.zjetpack V/zx: receiveChannel = 0
com.z.zjetpack V/zx: receiveChannel = 1
com.z.zjetpack V/zx: receiveChannel = 2
com.z.zjetpack V/zx: receiveChannel = 3
com.z.zjetpack V/zx: receiveChannel = 4
com.z.zjetpack V/zx: re = 1
com.z.zjetpack V/zx: re = 2
com.z.zjetpack V/zx: re = 3
[]( )channel的关闭
------------------------------------------------------------------------
* produce和actor返回的channel都会随着对应的协程执行完毕而关闭,也正式这样,channel才会被称为热数据流.
* 对于一个channel,如果我们调用了它的close方法,它会立即停止接收新元素,它的isClosedForSend会立即返回true,由于channel缓冲区的存在,可能还有一些元素没有被处理完,所以要等所有元素都被读取之后isClosedForReceive才会返回true
* channel的生命周期最好由主导方来维护,建议由**主导**的一方实现关闭。
fun run3(){
runBlocking {
val channel = Channel<Int>(3)
//生产者
launch {
List(3){
channel.send(it)
log("send = $it")
}
channel.close()
log("isClosedForSend = ${channel.isClosedForSend}")
log("isClosedForReceive = ${channel.isClosedForReceive}")
}
//消费者
launch {
for (c in channel) {
log("re = $c")
delay(1000)
}
log("消费isClosedForSend = ${channel.isClosedForSend}")
log("消费isClosedForReceive = ${channel.isClosedForReceive}")
}
}
}
打印:
com.z.zjetpack V/zx: send = 0
com.z.zjetpack V/zx: send = 1
com.z.zjetpack V/zx: send = 2
com.z.zjetpack V/zx: isClosedForSend = true
com.z.zjetpack V/zx: isClosedForReceive = false
com.z.zjetpack V/zx: re = 0
com.z.zjetpack V/zx: re = 1
com.z.zjetpack V/zx: re = 2
com.z.zjetpack V/zx: 消费isClosedForSend = true
com.z.zjetpack V/zx: 消费isClosedForReceive = true
[]( )BroadcastChannel
------------------------------------------------------------------------------
发送端和接收端在channel中存在一对多的场景,虽然有多个接收端,但是同一个元素只会被一个接收端读取到,广播则不同,多个接收端不存在互斥行为。
![在这里插入图片描述](https://img-blog.csdnimg.cn/bdfaa199b33b480085bc1ab8acf600d2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWlpfR08=,size_15,color_FFFFFF,t_70,g_se,x_16)
fun run4() {
runBlocking {
//直接创建
// val broadcastChannel = BroadcastChannel<Int>(Channel.BUFFERED)
//broadcast方法创建
val channel = Channel<Int>()
val broadcastChannel = channel.broadcast(Channel.BUFFERED)
//创建3个协程来接收
List(3) {
launch {
val receiveChannel = broadcastChannel.openSubscription()
for (r in receiveChannel) {
log("协程 $it, re = $r")
}
}
}
launch {
List(3) {
broadcastChannel.send(it)
}
broadcastChannel.close()
}
}
}
打印:
com.z.zjetpack V/zx: 协程 0, re = 0
com.z.zjetpack V/zx: 协程 0, re = 1
com.z.zjetpack V/zx: 协程 0, re = 2
com.z.zjetpack V/zx: 协程 1, re = 0
com.z.zjetpack V/zx: 协程 1, re = 1
com.z.zjetpack V/zx: 协程 1, re = 2
com.z.zjetpack V/zx: 协程 2, re = 0
com.z.zjetpack V/zx: 协程 2, re = 1
com.z.zjetpack V/zx: 协程 2, re = 2
[]( )多路复用
------------------------------------------------------------------
### []( )复用多个await
两个api分别从网络和本地获取数据,期望哪个先返回就先用哪个做显示
![在这里插入图片描述](https://img-blog.csdnimg.cn/05d3f1b42d064f22913dcafac6c2508a.png)
fun CoroutineScope.getFromLocal() = async {
delay(1000)
"返回本地数据"
}
fun CoroutineScope.getFromNet() = async {
"返回网络数据"
}
fun run5() {
runBlocking {
launch {
val local = getFromLocal()
val net = getFromNet()
val res = select<String> {
local.onAwait { it }
net.onAwait { it }
}
log("值 = $res")
}.join()
}
}
打印:
com.z.zjetpack V/zx: 值 = 返回网络数据
### []( )复用多个channel
跟await类似,会接收到最快的那个channel消息
fun run6() {
runBlocking {
val channels = listOf(Channel<Int>(), Channel<Int>())
launch {
delay(100)
channels[0].send(1)
}
launch {
delay(500)
channels[1].send(5)
}
val result = select<Int> {
channels.forEach { re ->
re.onReceive{it}
}
}
log("result = $result")
}
}
打印:
com.z.zjetpack V/zx: result = 1
### []( )SelectClause
哪些事件可以被select?SelectClause类型
包括:
SelectClause0:对应事件没有返回值,例如 join 没有返回值,对应的 onJoin 就是这个类型,使用时 onJoin 的参数是一个无参函数:
public val onJoin: SelectClause0
runBlocking {
val job1 = launch {
delay(100)
log("job1")
}
val job2 = launch {
delay(10)
log("job2")
}
select<Unit> {
job1.onJoin {
log("job1.onJoin")
}
job2.onJoin {
log("job2.onJoin")
}
}
}
打印:
com.z.zjetpack V/zx: job2
com.z.zjetpack V/zx: job2.onJoin
com.z.zjetpack V/zx: job1
SelectClause1:对应事件有返回值,前面的 onAwait 和 onReceive 都是此类情况。
public val onAwait: SelectClause1<T>
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
delay(100)
channels[0].send(1)
}
launch {
delay(500)
channels[1].send(5)
}
val result = select<Int> {
channels.forEach { re ->
re.onReceive{it}
}
}
log("result = $result")
}
}
打印:
com.z.zjetpack V/zx: result = 1
### []( )SelectClause
哪些事件可以被select?SelectClause类型
包括:
SelectClause0:对应事件没有返回值,例如 join 没有返回值,对应的 onJoin 就是这个类型,使用时 onJoin 的参数是一个无参函数:
public val onJoin: SelectClause0
runBlocking {
val job1 = launch {
delay(100)
log("job1")
}
val job2 = launch {
delay(10)
log("job2")
}
select<Unit> {
job1.onJoin {
log("job1.onJoin")
}
job2.onJoin {
log("job2.onJoin")
}
}
}
打印:
com.z.zjetpack V/zx: job2
com.z.zjetpack V/zx: job2.onJoin
com.z.zjetpack V/zx: job1
SelectClause1:对应事件有返回值,前面的 onAwait 和 onReceive 都是此类情况。
public val onAwait: SelectClause1<T>
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-hCmjKlrs-1712797493203)]
[外链图片转存中…(img-n2FI3GPF-1712797493204)]
[外链图片转存中…(img-pLE8O7i0-1712797493204)]
[外链图片转存中…(img-jqxPA7UA-1712797493204)]
[外链图片转存中…(img-qwgdJQgU-1712797493205)]
[外链图片转存中…(img-97e6V3vB-1712797493205)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-wQkRHFj6-1712797493205)]
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
[外链图片转存中…(img-9cqlFUy4-1712797493206)]
[外链图片转存中…(img-u7N7BOd3-1712797493206)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-XVPQxevh-1712797493206)]