两年前,到微信面试的时候,人家问我懂不懂协程,知不知道里面原理,我当时懵B。一年前,去另一家公司面试的时候,人家也是这样问我kotlin协程会用吗?我也是没法回答。 若是没有实践过,估计也没法说出个因此来,由于压根不知道究竟他怎么用,使用的时候须要注意什么。 终于近来有一个节点,本身能够去接触协程了,须要写出对接口使用协程的方式扩展。java
###1.协程 不少人都会讲进程,线程,协程来讨论。 其实就简单的说一下我所理解的吧 进程能够有多个线程,线程能够有多个协程,使用协程其实仍是线程切换。 使用协程一定要有做用域(Scope),有一个全局的做用域GlobalScope是供App内全局使用的。 而后须要一个标识上下文的context 说一下协程的优点 1.无需系统内核的上下文切换,减少开销; 2.无需原子操做锁定及同步的开销,不用担忧资源共享的问题; 3.单线程便可实现高并发,单核 CPU 即使支持上万的协程都不是问题,因此很适合用于高并发处理,尤为是在应用在网络爬虫中;web
须要注意的地方: GlobalScope调用withContext时没法使用Dispatch.Default,由于默认是一个EmptyCoroutineContext,协程不会运行,而且不会有任何报错。面试
###2.请求 Continuation 你们能够考虑一个场景,当网络协议发送后,经过非堵塞的机制来等待协议结果返回,在不改动原有的协议的使用状况下,加入协程去改造协议处理。 初学者,其实很容易会想到使用一个协程去完成发送,而后使用另一个协程来完成接收,这样作就能够简单完成操做。 那么有没更优美的编写呢,先给你们看一个简单的代码 安全
###3.suspend 泛型 内联 须要注意的是,协程域里面,所有都须要声明为suspend fun的形式,提示是协程的方法,程序执行挂起的时候估计是须要特殊的标记。 协程编写泛型的形式和java差距不是很大,可是须要注意的是,使用了协程包含了泛型对象,使用is判断,会提示你,须要使用内联。 bash
###4.广播 channel received 标记 协议也并不是只有请求接收,特别若是是使用socket,那么你确定是能有接收广播的状况。而上面使用协程continuation只能模拟出请求和接收的状况,那是否有办法接收些成广播呢? 这里可使用微信
private val channel = Channel<Ent>()
fun send() {
async {
withContext(coroutineContext) {
val obj = ChildEnt()
obj.name = "协程广播"
obj.count = 4
channel.send(obj)
// channel.close()
}
}
}
fun <T : Ent> register(callback: CoroutinesCallback<T>): Job {
return async {
withContext(coroutineContext) {
for (ent in channel) {
callback.block.invoke(ent as T)
}
}
}
}
复制代码
这里须要使用channel,使用一个协程来作发送,另一个协程须要来接收。 若是你使用channel.receive()只能接收到一条数据,这里使用,in channel的方式能够一直监听到channel.send的数据。 固然若是肯定通道不可用,要使用channel.close关闭通道。网络
###5.java调用协程 若是你使用java的代码,你会发现没法使用协程,没法使用域声明。 那怎么怎么才能调用协程? java中仍是能声明域对象以及CoroutineContext上下文对象的,那么只能传输做用域,context,以及使用的回调的方法来作处理。并发
class CoroutinesCallback<T : IEnt>(
var scope: CoroutineScope,
var context: CoroutineContext,
var block: (suspend (T) -> Unit),
var error: (suspend (Exception) -> Unit)? = null
)
override fun <T : IEnt> sendAsCoroutineAsync(
rspClass: Class<T>,
scope: CoroutineScope,
context: CoroutineContext,
s: (T) -> Unit,
e: ((Exception) -> Unit)?
): Deferred<Unit?>? {
return sendAsCoroutineAsync(rspClass,
CoroutinesCallback(scope, context, {
s.invoke(it)
}, {
e?.invoke(it)
}))
}
复制代码
java传输这些能够声明的对象,再经过kotlin转包一层。那为什么不让外层直接传入一个CoroutinesCallback回调对象就能够呢? java是没法办法初始化suspend的初始方法的,这就很是尴尬了。折中的方法,只能使用suspend block的再包一层普通block的方法,而普通block s: (T) -> Unit能够对应java中的Function1<T, Unit> s的方法。socket
###6.协程的回收 固然是须要考虑协程的回收的,特别在外Activity生命周期结束后,才到达协程结果返回,若是你只是封装消息外抛或者不在主线程还好,否则就颇有可能形成崩溃了。 协程域使用async的方法会传回一个Deffered的对象,和Job相似,能够经过这个对象cancel的方法能够完成释放,本身挑选时机就好。async
想要更加智能,参照rxjava的处理,是须要绑定lifecycle,改造的时候也是这样作的。新版本的lifecycle加入了对协程的支持,直接是有lifecycle CoroutineScope,执行的时候,直接使用这个域就很是安全了。旧版的lifecycle并无,那这时候绑定释放就只能本身编写了。
class LifecycleCoroutineListener(
private val job: Job, private val cancelEvent: Lifecycle.Event =
Lifecycle.Event.ON_DESTROY
) : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun pause() = handleEvent(Lifecycle.Event.ON_PAUSE)
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() = handleEvent(Lifecycle.Event.ON_STOP)
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() = handleEvent(Lifecycle.Event.ON_DESTROY)
private fun handleEvent(e: Lifecycle.Event) {
if (e == cancelEvent && !job.isCancelled){
job.cancel()
}
}
}
//使用的时候参数传入lifecycle,而后完成绑定
lifecycle?.addObserver(LifecycleCoroutineListener(j))
复制代码
这里还有优化的地方,协程域上下文CoroutineContext是带有isActive的方法的。经过封装extendsion的方法,来对Continuation回调时先对存活判断
private fun <T> Continuation<T>.resumeIfActive(value: T) {
if (this.context.isActive) {
resume(value)
}
}
复制代码
###7.协程的异常处理 上面介绍了continuation的对象,使用resume能够返回结果到挂起的等待的地方,若是失败了的状况,能够放回resumeWithException的方法来返回Exception内容到接收处,可是这里须要try catch来得到Exception。
###8.线程池问题 协程自身也是会开通线程池的,若是原本就有rxjava的一套代码,无疑会增长线程数量的。有没很好的方法规避呢,能够选择和rxjava公用线程池。
object XXDispatchers {
/**
* 后台任务分发器, 使用的线程池与 Schedulers.computation() 同样
*/
@JvmStatic
val Default: CoroutineDispatcher = Schedulers.computation().asCoroutineDispatcher()
/**
* 主线程
*/
@JvmStatic
val Main: CoroutineDispatcher = Dispatchers.Main
/**
* 协程挂起后恢复回到的线程, 与最后挂起函数运行时所在的线程相同. 即与 Dispatchers.Unconfined 相同
*/
@JvmStatic
val Unconfined: CoroutineDispatcher = Dispatchers.Unconfined
/**
* IO任务分发器, 使用的线程池与 Schedulers.io() 同样
*/
@JvmStatic
val IO: CoroutineDispatcher = Schedulers.io().asCoroutineDispatcher()
}
复制代码
最后的提醒,使用协程必定是须要做用域和上下文的,而且要考虑释放等问题。暂时并无像rxjava同样链式调用那么方便 若是有更优化的方案,能够再评论区评论,我会认真跟进。
两个群号均可以加入,群2群号763094035,我在这里期待大家的加入!!!
群1号是316556016。