kotlin-协程基本操作_kotlin mainscope-CSDN博客
一. 协程是什么
kotlin协程是一个线程框架,就是对线程的封装,提供了一种轻量级的并发处理方式,通过非阻塞挂起和恢复实现了用同步代码的方式编写异步代码。把原本运行在不同线程的代码写在一个代码块{}里面,看起来就像是同步代码。 协程的目的是,简化复杂的异步代码逻辑,用同步的代码编写方式实现复杂异步代码逻辑。
二.几种封装好的协程
1.viewModelScope启动协程(默认运行在主线程,生命周期和ViewModel绑定)
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
2.lifecycleScope启动协程(默认运行在主线程,生命周期和Activity或者Fragment绑定,实现了LifeycleOwner接口)
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
3.MainScope()启动协程(默认运行在主线程,没有绑定Activity|Frament生命周期,需要手动在onDestory中通过job.cancel()取消协程.
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
4.GlobalScope启动协程(默认不是主线程,是一个静态单例,和应用的生命周期相同,一般不使用,容易导致内存泄漏)
public object GlobalScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
5.CoroutineScope(Dispatchers.IO)构造函数启动协程 ,通过Dispatchers指定运行的线程。
注意:await() 只有在 async 未执行完成返回结果时,才会挂起协程。若 async 已经有结果了,await() 则直接获取其结果并赋值给变量,此时不会挂起协程。
三.协程的基本使用.
1.启动协程的几种方式.
(1).launch{} CoroutineScope接口的扩展方法,启动一个携程,不阻塞当前线程,返回一个Job对象,通过Job.cancel()可以取消协程,控制协程的生命周期.
public fun CoroutineScope.launch(
context:CoroutineScope=EmptyCoroutineContext,
start:CoroutineStart = CoroutineStart.DEFAULT,
block:suspend CoroutineScope.()->Unit
):Job { //Job是返回值, {} 是launch的方法体实现 【不要搞混淆了】
//launch方法体实现,返回Job对象
val newcontenxt=newCoroutineContext(context)
val coroutine=if(start.isLazy){
LazyStandaloneCoroutine(newContext,block);//实现了job接口
}else{
StandaloneCoroutine(newContext,active=true) //实现了job接口
}
coroutine.start(start,coroutine,block) //启动job
return coroutine;//返回实现了Job接口的协程对象
}
(2).async{} CoroutienScope接口的扩展方法,启动一个协程,不阻塞当前线程,返回一个Deferred类,可以通过await获取T对象.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine= if (start.isLazy){
LazyDeferredCoroutine<T>(newContext, block) //实现了Deferred接口
} else{
DeferredCoroutine<T>(newContext, active = true) //实现了Deferred接口
}
coroutine.start(start, coroutine, block) //启动协程
return coroutine //返回实现了Deferred接口的协程对象
}
(3). runBlocking{} 全局方法,创建并启动协程,返回值是lambda表达式block的最后一行的返回值.(Unit或者其他具体类型)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
//这一段代码会阻塞主线程,直接导致黑屏。
runBlocking {
delay(10000)
}
//即使指定启动的协程运行在IO线程,也会阻塞主线程,导致黑屏
runBlocking(Dispatchers.IO) {
delay(10000L)
}
//依然会阻塞主线程导致黑屏
GlobalScope.launch(Dispatchers.Main){
runBlocking {
delay(10000)
}
}
//不会阻塞主线程
GlobalScope.launch(Dispatchers.IO){
runBlocking {
delay(10000)
}
}
2.withContext()用于切换线程,并挂起协程(suspend,返回值lambda表达式最后一行的返回值,只能在suspend方法或者协程种调用).
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
lifecycleScope.launch(Dispatchers.IO) {
delay(1000)
//切换到主线程,此时启动协程的IO线程可以去执行其他任务.
withContext(Dispatchers.Main){
println("Test001:withContext:${Thread.currentThread().name}")
}
}
3.协程上下文,一般用调度器Dispatchers来切换线程,它是CoroutineContext接口的实现类.
-
Dispatchers.Main 协程代码执行在Android主线程
-
Dispatcher.IO IO线程池分配线程执行协程代码
-
Dispatchers.Unconfined 不限制协程代码执行在哪个线程【主线程或者其他空闲线程】
-
Dispatcher.Default JVM共享线程池分配线程执行协程代码
4.协程的挂起和阻塞
(1).协程都被挂起了,那么挂起函数的函数体由谁执行呢?
协程执行到挂起函数式,协程的执行会被暂停(即协程被挂起),但它并不会阻塞执行该挂起函数的线程,挂起函数的函数体任然由当前线程继续执行。
为了更清楚的解释这一点,我们可以使用一下步骤:
-
遇到挂起函数:当协程遇到挂起函数时,协程的执行会被暂停。
-
挂起函数执行:尽管协程被暂停了,挂起函数的函数体任然会在原始线程上执行。(除非该挂起函数明确指定了其他的执行上下文)
-
挂起函数完成:一旦挂起函数完成其工作,它会通知协程库,然后协程会恢复执行。 注意:当协程被挂起时,主线程并没有被阻塞,而是可以执行其他的任务,等到挂起函数执行完毕,协程恢复时,又可以在主线程中继续执行。
(2).suspend表示挂起(非阻塞式挂起,不影响主线程的执行),例如下面的例子:
fun testCoroutineInActivity() {
GlobalScope.launch(Dispatchers.Main) { //1.启动协程1
println("Test001:执行在协程中...")
GlobalScope.launch (Dispatchers.IO){//2.启动协程2
println("Test001:异步执行result1")
delay(1000)
println("Test001:result1:1234")
}
GlobalScope.launch(Dispatchers.IO) {//3.启动协程3
println("Test001:异步执行result2.")
delay(1000)
println("Test001:result2:123456")
}
println("Test001:执行完毕...")
}
}
执行结果如下:
注意:协程2和协程3中的delay函数只是挂起了协程2和协程3,不会影响协程1中的主线程的执行.
总结一下:在协程中使用挂起函数时,任何可能得的"阻塞"操作都会转移到其他线程上执行,这样启动协程的原始线程(例如主线程)就不会被实际阻塞,这样使得协程特别适合UI线程编程,因为它可以确保UI线程保持响应。
四.协程处理并发问题
1.协程就是一个线程框架,是对线程的封装,提供了一种轻量并发的处理方式.
fun testCoroutineInActivity() {
//1.launch启动协程,返回Job对象,通过job.cancel()取消任务
val job:Job=CoroutineScope(Dispatchers.IO).launch() {
println("Test001:查看运行的线程1:"+Thread.currentThread().name)
val start= System.currentTimeMillis()
val result1=async {//运行在主线程,非异步
println("Test001:查看运行的线程2:"+Thread.currentThread().name)
delay(1000)
"123"
}
//2.async 启动协程,得到有返回值的Deferred对象
val result2:Deferred<String> = async(Dispatchers.IO) { //异步
println("Test001:查看运行的线程3:"+Thread.currentThread().name)
delay(2000)
"456"
}
val result3=result1.await()+result2.await();
println("Test001:并发耗时:"+ (System.currentTimeMillis() - start)+"||result3:"+result3)
withContext(Dispatchers.IO){
println("Test001:查看运行的线程4:"+ Thread.currentThread().name)
delay(1000)
}
println("Test001:查看运行的线程5:"+ Thread.currentThread().name)
}
}
2.通过非阻塞式挂起和恢复来实现用同步代码的方式编写异步代码
// 注意:在真实开发过程中,MainScope作用域用的非常常用
MainScope().launch(){ // 注意:此协程块默认是在UI线程中启动协程
// 下面的代码看起来会以同步的方式一行行执行(异步代码同步获取结果)
val token = apiService.getToken() // 网络请求:IO线程,获取用户token
val user = apiService.getUser(token)// 网络请求:IO线程,获取用户信息
nameTv.text = user.name // 更新 UI:主线程,展示用户名
val articleList = apiService.getArticleList(user.id)// 网络请求:IO线程,根据用户id获取用户的文章集合哦
articleTv.text = "用户${user.name}的文章页数是:${articleList.size}页" // 更新 UI:主线程
}
3.传统并发处理方式
如果一个页面需要同时并发请求多个接口,当所有的接口都请求完成需要做一些合并处理,然后更新UI,如何并发处理呢?
-
方法一:为每个接口设置一个boolean值,每当接口请求成功,boolean值设置为true,当最后一个接口请求成功后,在更新UI,这样就达到了并发的目的 【管理多个boolean值,不够优雅,太累了】
-
方法二:RXjava的zip操作符【达到发射一次,将结果合并处理的目的】
fun testRxjavaZip(){
println("-------------")
val observable1: Observable<HttpResult<List<Banner>>> = HttpRetrofit.apiService.getBanners().subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread())
val observable2:Observable<HttpResult<ArrayList<HomeData.DatasBean>>> =HttpRetrofit.apiService.getTopArticles().subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread())
val observable3: Observable<HttpResult<List<KnowledgeData>>> = HttpRetrofit.apiService.getKnowledgeTree().subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread())
var result1: HttpResult<List<Banner>>? =null
var result2: HttpResult<ArrayList<HomeData.DatasBean>>? =null
var result3: HttpResult<List<KnowledgeData>>? =null
Observable.zip(observable1, observable2, observable3,
object: Function3<
HttpResult<List<Banner>>,
HttpResult<ArrayList<HomeData.DatasBean>>,
HttpResult<List<KnowledgeData>>,
Boolean>{
override fun apply(t1: HttpResult<List<Banner>>, t2: HttpResult<ArrayList<HomeData.DatasBean>>, t3: HttpResult<List<KnowledgeData>>): Boolean {
result1=t1
result2=t2
result3=t3
return t1!=null&&t2!=null&&t3!=null
}
}).subscribe(object:Observer<Boolean>{
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
override fun onNext(t: Boolean) {
if(t){//对结果进行处理
println("成功获取结果");
println(Gson().toJson(result1))
println(Gson().toJson(result2))
println(Gson().toJson(result3))
}
}
});
}