前言:
本人通过对kotlin由浅到深使用了一年半,并且多个项目已基本达到纯kotlin开发。越来越觉得kotlin对于开发Android来说,不仅仅是多了一门开发语言,更是提升开发效率和优化程序设计的利器!值得Android开发者放心学习和语言转换,有Google做靠山,不用担心kotlin的前景问题。用了kotlin,你会发现其他所有语言在开发Android时,都显得那么臃肿和啰嗦。
今天我们主要简单讲一下kotlin的基础配置和异步处理框架-协程(coroutines)的配置。之所以将kotlin的基础配置和协程放在一起,是因为协程的使用只能在kotlin语言环境里,不能在Java里使用,况且我们任何一个App的开发,肯定都会涉及到异步耗时任务的执行至于为什么要特意讲一下协程,你在网上搜一下就知道,这个框架是多么强大和神奇!
不仅是我们处理异步任务时的简单高效的工具,更是结合了原来
Thread+Handler+Runable+RxAndroid这几大切换器和控件的使用场景,是我们处理异步任务的不二选择!
下面我们直接通过代码看看我们需要干的几件事:
1、项目根目录gradle中:
buildscript {
ext.kotlin_version = '1.2.71'
......
dependencies {
.....
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
.....
}
}
2、app/gradle中:
.....
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
......
kotlin {
experimental {
coroutines 'enable'
}
}
dependencies {
....
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//coroutines的配置重点是这里
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.5'
.....
}
通过以上两步,基本完成了对kotlin和coroutines的基础配置,那么代码里我们可以直接使用了:
fun getData(){
//启动线程
val job = launch {
//加载数据任务一
val load = loadData()
//刷新界面
launch(UI) {
Toast.makeText(this@MainActivity, load, Toast.LENGTH_SHORT).show()
Log.d("launch", load)
}
//加载数据任务二
val select = selectData()
launch(UI) {
Toast.makeText(this@MainActivity, select, Toast.LENGTH_SHORT).show()
Log.d("launch", select)
}
}
}
//封装的界面刷新方法,不用每次都调用launch(UI)了
fun launchUi(start: CoroutineStart = CoroutineStart.DEFAULT,
parent: Job? = null,
block: suspend CoroutineScope.() -> Unit) = launch(UI, start, parent, block)
如果你想让 getData()的执行进行加锁,直接这样就可以:
fun getData()= runBlocking{
......
}
如果你想在界面关闭时停止任务:
if (job.isActive)
job.cancelAndJoin()
如果要在任务里delay一会:
val launch = launch {
...
//等待执行,相当于Thread.sleep(1000)
delay(1000)
...
}
原理
1、构造参数:
既然coroutines这么好用,那么我们来看看它内部到底怎么实现的:
打开launch:
public actual fun launch(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
parent: Job? = null,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context, parent)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
/** @suppress **Deprecated**: Binary compatibility */
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
public fun launch(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job =
launch(context, start, block = block)
/**
* @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter
*/
@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter",
replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)"))
public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job =
launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block)
我们会发现它有如上三种调用方式,默认我们会使用第一种方式。不管哪种方式都有一个DefaultDispatcher的参数,这个参数就是我们启动异步的上下文,继续进入DefaultDispatcher:
@Suppress("PropertyName")
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool
到这貌似看出一点端倪了,继续进入CommonPool:
object CommonPool : CoroutineDispatcher() {
private var usePrivatePool = false
@Volatile
private var _pool: Executor? = null
private inline fun Try(block: () -> T) = try { block() } catch (e: Throwable) { null }
private fun createPool(): ExecutorService {
if (System.getSecurityManager() != null) return createPlainPool()
val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
?: return createPlainPool()
if (!usePrivatePool) {
Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
?.let { return it }
}
Try { fjpClass.getConstructor(Int::class.java).newInstance(defaultParallelism()) as? ExecutorService }
?. let { return it }
return createPlainPool()
}
private fun createPlainPool(): ExecutorService {
val threadId = AtomicInteger()
return Executors.newFixedThreadPool(defaultParallelism()) {
Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
}
}
private fun defaultParallelism() = (Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1)
@Synchronized
private fun getOrCreatePoolSync(): Executor =
_pool ?: createPool().also { _pool = it }
override fun dispatch(context: CoroutineContext, block: Runnable) =
try { (_pool ?: getOrCreatePoolSync()).execute(timeSource.trackTask(block)) }
catch (e: RejectedExecutionException) {
timeSource.unTrackTask()
DefaultExecutor.execute(block)
}
// used for tests
@Synchronized
internal fun usePrivatePool() {
shutdown(0)
usePrivatePool = true
_pool = null
}
// used for tests
@Synchronized
internal fun shutdown(timeout: Long) {
(_pool as? ExecutorService)?.apply {
shutdown()
if (timeout > 0)
awaitTermination(timeout, TimeUnit.MILLISECONDS)
shutdownNow().forEach { DefaultExecutor.execute(it) }
}
_pool = Executor { throw RejectedExecutionException("CommonPool was shutdown") }
}
// used for tests
@Synchronized
internal fun restore() {
shutdown(0)
usePrivatePool = false
_pool = null
}
override fun toString(): String = "CommonPool"
}
到这已经非常清楚了,这是一个由ExecutorService构建的线程池,由上面的defaultParallelism()完成。
除此之外,我们看到了DefaultDispatcher继承了一个CoroutineDispatcher的类:
public expect abstract class CoroutineDispatcher constructor() : AbstractCoroutineContextElement, ContinuationInterceptor {
public open fun isDispatchNeeded(context: CoroutineContext): Boolean
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
public override fun interceptContinuation(continuation: Continuation): Continuation
}
public expect interface Runnable {
public fun run()
}
@Suppress("PropertyName")
public expect val DefaultDispatcher: CoroutineDispatcher
打开这个类我们发现就是一些要实现的抽象方法,没啥别的了。
回到上面开始讲的launch的构造方式中,除了CoroutineContext,还有第二个参数start,这个我们通过源码可以看出它是一个枚举类,它的意思是选择协程启动的模式:DEFAULT、LAZY、ATOMIC、UNDISPATCHED:
public operator fun invoke(block: suspend () -> T, completion: Continuation) =
when (this) {
CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion)
CoroutineStart.ATOMIC -> block.startCoroutine(completion)
CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
CoroutineStart.LAZY -> Unit // will start lazily
}
参数三Job就是我们要构造的返回对象,这里也就是任务的主要执行处。参数四CoroutineScope作用时任务执行外界可回调的状态、上下文对象。
2、任务的执行
构造参数的父类中通过接收子类传递的参数再次讲参数封装完整后,最后调用start()方式启动:
val newContext = newCoroutineContext(context, parent)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
我们进入start里:
public abstract class AbstractCoroutine{
........
public fun start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
initParentJob()
start(block, receiver, this)
}
........
}
继续进入initParentJob:
public actual interface Job : CoroutineContext.Element {
......
internal actual fun initParentJobInternal(parent: Job?) {
check(parentHandle == null)
if (parent == null) {
parentHandle = NonDisposableHandle
return
}
parent.start() // make sure the parent is started
@Suppress("DEPRECATION")
val handle = parent.attachChild(this)
parentHandle = handle
// now check our state _after_ registering (see updateState order of actions)
if (isCompleted) {
handle.dispose()
parentHandle = NonDisposableHandle // release it just in case, to aid GC
}
}
......
}
到这里算是我们能跟踪的都跟完了。以上是coroutine的整个执行流程,源码大概不算太复杂,内部的维护也算很周全,例如加锁、取消、delay、多任务并发它都做了处理,所以我们不妨在开发中尝试一下。