kotlin 协程基础,阿里P8架构师的Android大厂面试题总结

CoroutineScope.async与CoroutineScope.launch的区别在于,async方式返回Deferred,可以获取协程的执行结果。

3.5 coroutineScope

在其他协程或挂起函数内,可通过coroutineScope来创建一个CoroutineScope,并且会立即执行coroutineScope{}内新建的协程,如下所示:

GlobalScope.launch {
Log.d(“CoroutineTag”, “GlobalScope”)
coroutineScope {
// 协程内的this即CoroutineScope
Log.d(“CoroutineTag”, “coroutineScope”)
}
}

coroutineScope是一个挂起挂起函数,所以只能在协程或挂起函数内调用:

public suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}

3.6 runBlocking

在线程、协程、挂起函数内,可通过runBlocking创建一个CoroutineScope,并且会立即执行runBlocking{}内新建的协程,如下所示:

GlobalScope.launch {
Log.d(“CoroutineTag”, “GlobalScope”)
runBlocking {
// 协程内的this即CoroutineScope
Log.d(“CoroutineTag”, “coroutineScope”)
}
}

与coroutineScope不同的是,runBlocking会阻塞当前线程;runBlocking是一个普通方法,所以runBlocking可以在线程中调用:

/**

  • @param block 挂起函数,返回结果T就是协程的返回结果
    */
    public fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
    contract {
    callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
    // create or use private event loop if no dispatcher is specified
    eventLoop = ThreadLocalEventLoop.eventLoop
    newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
    // See if context’s interceptor is an event loop that we shall use (to support TestContext)
    // or take an existing thread-local event loop if present to avoid blocking it (but don’t create one)
    eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
    ?: ThreadLocalEventLoop.currentOrNull()
    newContext = GlobalScope.newCoroutineContext(context)
    }
    val coroutine = BlockingCoroutine(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
    }

3.7 MVVM的CoroutineScope

参考:developer.android.com/topic/libra…

对于MVVM架构,KTX提供了LifecycleOwner、LiveData、ViewModel对应的CoroutineScope;使用方法如下:
(1)添加依赖

dependencies {
// ViewModelScope
implementation “androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0”
// LifecycleScope
implementation “androidx.lifecycle:lifecycle-runtime-ktx:2.2.0”
// liveData
implementation “androidx.lifecycle:lifecycle-livedata-ktx:2.2.0”
}

(2)用法示例

// 1.viewModelScope
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}

// 2.lifecycleScope
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}

四、协程启动方式

coroutine builder协程的启动方式有几种:

4.1 CoroutineScope.launch

启动一个协程但不会阻塞调用线程,必须在协程作用域CoroutineScope中才能调用,返回Job表示该新建的协程。

public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}

4.2 CoroutineScope.async

启动一个协程但不会阻塞调用线程,必须在协程作用域CoroutineScope中才能调用。返回Deferred表示该新建的协程。Deferred是Job的子接口。

public fun CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}

CoroutineScope.async与CoroutineScope.launch的区别在于,async方式返回Deferred,可以获取协程的执行结果。

4.3 withContext

指定一个CoroutineContext,并启动一个协程,使用方式如下:

GlobalScope.launch {
withContext(Dispatchers.Default) {
}

withContext是一个挂起函数,只能在其他协程或挂起函数内调用:

public suspend fun withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
val oldContext = uCont.context
val newContext = oldContext + context
// always check for cancellation of new context
newContext.checkCompletion()
// FAST PATH #1 – new context is the same as the old one
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
// FAST PATH #2 – the new dispatcher is the same as the old one (something else changed)
// equals is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
val coroutine = UndispatchedCoroutine(newContext, uCont)
// There are changes in the context, so this thread needs to be updated
withCoroutineContext(newContext, null) {
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
// SLOW PATH – use new dispatcher
val coroutine = DispatchedCoroutine(newContext, uCont)
coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
}

4.4 coroutineScope{}

如3.2所示,coroutineScope{}会创建一个CoroutineScope{},并执行{}内新建的协程,不会阻塞当前线程。

4.5 runBlocking{}

启动一个新的协程并阻塞调用它的线程,会把挂起函数的结果作为协程的结果返回,常用来main方法和测试(所以Android开发中不常用)。

4.6 produce<> { }

启动一个新的协程,并生产一系列数据到channel中,这个协程返回ReceiveChannel,其他协程可从ReceiveChannel中接受数据。

五、CoroutineContext

5.1 定义

CoroutineContext是一系列元素的集合,主要的元素是代表协程的Job,此外还有协程的dispatcher等(Job、Dispatchers与CoroutineName都实现了Element接口)。 CoroutineScope封装了CoroutineContext:

public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}

5.2 继承性

当通过CoroutineScope.launch启动一个新的协程时,新的CoroutineScope继承了外面的CoroutineContext,并且新的协程Job成为了父协程的子Job,这样当父Job取消时会递归取消子Job。

public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}

以下两种情况不会取消Job除外: (1)GlobalScope.launch启动的协程; (2)launch中传递了特定的父Job;

5.3 组合CoroutineContext

一些情况下,我们需要自定义CoroutineContext中的元素,可以使用+来组合CoroutineContext中的各个元素:

launch(Dispatchers.Default + CoroutineName(“test”)) {
println(“I’m working in thread ${Thread.currentThread().name}”)
}

六、CoroutineStart

6.1 CoroutineStart.DEFAULT

CoroutineScope.launch()方式创建协程的启动方式默认为CoroutineStart.DEFAULT,即立即执行。

6.2 CoroutineStart.LAZY

通过设置CoroutineStart.LAZY的协程不会立即执行,需要手动调用start()后才开始执行。可通过如下方式设置启动方式为CoroutineStart.LAZY:

val job = launch(start = CoroutineStart.LAZY) {
// …
}
// 手动调用start
job.start()

七、Job

CoroutineScope.launch 函数返回的是一个 Job 对象,代表一个异步的任务。可通过Job的start()、cancle()、join()等方法控制该协程的执行。

7.1 简单示例

val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println(“job: I’m sleeping ${i++} …”)
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println(“main: I’m tired of waiting!”)
job.cancelAndJoin() // cancels the job and waits for its completion

输出结果如下:

Hello
World!
Done

可见,等job执行完成后,才会继续输出"Done"。

7.2 获取Job

一个CoroutineContext中只有一个Job,可通过如下方式获取当前CoroutineContext中的Job:

println(“My job is ${coroutineContext[Job]}”)

输出结果是:

My job is “coroutine#1”:BlockingCoroutine{Active}@6d311334

7.3 Job状态

Job 具有生命周期并且可以取消。 Job 还可以有层级关系,一个Job可以包含多个子Job,当父Job被取消后,所有的子Job也会被自动取消;当子Job被取消或者出现异常后父Job也会被取消。

/**

  • A background job.
  • Job实例化方法

  • The most basic instances of Job interface are created like this:
  • (1)Coroutine job is created with [launch][CoroutineScope.launch] coroutine builder.
  • It runs a specified block of code and completes on completion of this block.
  • (2)CompletableJob is created with a Job() factory function.
  • It is completed by calling [CompletableJob.complete].
  • Job states Job状态

  • A job has the following states:
  • | State | [isActive] | [isCompleted] | [isCancelled] |
  • | -------------------------------- | ---------- | ------------- | ------------- |
  • | New (optional initial state) | false | false | false |
  • | Active (default initial state) | true | false | false |
  • | Completing (transient state) | true | false | false |
  • | Cancelling (transient state) | false | false | true |
  • | Cancelled (final state) | false | true | true |
  • | Completed (final state) | false | true | false |

*/

八、挂起函数

suspend修饰的函数叫挂起函数,只能在协程内部或者另一个挂起函数内调用,如下所示;

suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // pretend we are doing something useful here
return 13
}

suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // pretend we are doing something useful here, too
return 29
}

8.1 顺序执行

挂起函数是顺序执行的,如下所示:

val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println(“The answer is ${one + two}”)
}
println(“Completed in $time ms”)

结果如下:

The answer is 42
Completed in 2017 ms

8.2 异步执行

使用sync来异步执行挂起函数,代码如下:

val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println(“The answer is ${one.await() + two.await()}”)
}
println(“Completed in $time ms”)

结果如下:

The answer is 42
Completed in 1017 ms

8.3 延迟异步执行

val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
// some computation
one.start() // start the first one
two.start() // start the second one
println(“The answer is ${one.await() + two.await()}”)
}
println(“Completed in $time ms”)

结果如下:

The answer is 42
Completed in 1017 ms

九、协程调度

Coroutine使用CoroutineDispatcher来调度协程在哪个线程执行;CoroutineDispatcher实现了CoroutineContext接口,使用方式如下:

GlobalScope.launch {
launch { // context of the parent, main runBlocking coroutine
println(“main runBlocking : I’m working in thread ${Thread.currentThread().name}”)
}
launch(Dispatchers.Unconfined) { // not confined – will work with main thread
println(“Unconfined : I’m working in thread ${Thread.currentThread().name}”)
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println(“Default : I’m working in thread ${Thread.currentThread().name}”)
}
launch(newSingleThreadContext(“MyOwnThread”)) { // will get its own new thread
println(“newSingleThreadContext: I’m working in thread ${Thread.currentThread().name}”)
}

// 其他启动过协程的方式也可以指定Dispatcher
async(Dispatchers.Default) {
}
withContext(Dispatchers.Default) {
}
}

运行结果如下:

Unconfined : I’m working in thread main
Default : I’m working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I’m working in thread MyOwnThread
main runBlocking : I’m working in thread main

下面列举了CoroutineDispatcher常见的几种模式:

1. Dispatchers.Default:

launch或aync启动协程时的默认值,内部使用线程池实现;Dispatchers.Default限制处理器上的内核数(2、4、6、8等);

2. Dispatchers.Main

在主线程中执行协程;

3. Dispatchers.IO:

在IO线程执行协程,一般用于执行网络或者I/O操作,与Dispatchers.Default共享

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

线程池;Dispatchers.IO限制最多64个线程;

4. Dispatchers.Unconfined

Dispatchers.Unconfined不能创建新线程,使用这种调度方式的协程的执行、恢复都在当前线程进行;

十、协程间数据通信

协程间数据通信方式:


版权声明:本文为m0_65511857原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。