携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情
登录拦截系列:
前言
关于App实现登录的拦截再执行逻辑,前文我们讲到过线程池的方案,有同学迫不及待的就提问了,线程都可以协程的怎么不行,这不,协程的方案来了。
我们回顾一下线程的思路,
- 一个就是无限轮询,加入Flag判断,太耗费资源。
- 另一个就是线程的暂停与恢复,其中就有两种具体实现方案。
那么协程的思路和线程有区别吗?思路类似,但是有区别。
- 思路一:都已经协程了,就无需轮询了,直接挂起即可。
- 思路二:和线程类似的思路,协程的暂停与恢复
两种思路我们分为三种具体方案来实现,话不多说,直接看代码:
一、协程的通知
我们通过 Channel 来实现协程的通知,使用 channel.receive() 让一个协程挂起,我们再通过另一个协程 channel.send()来发送消息,让之前的协程恢复,从而到达继续执行的效果。
如果对Channel不了解的可以看看我之前的文章,协程的通信。
我们使用一个工具类统一管理:
class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {
companion object {
private var instance: LoginInterceptCoroutinesManager? = null
get() {
if (field == null) {
field = LoginInterceptCoroutinesManager()
}
return field
}
fun get(): LoginInterceptCoroutinesManager {
return instance!!
}
}
private val channel = Channel<Boolean>()
fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {
launch {
if (LoginManager.isLogin()) {
nextAction()
return@launch
}
loginAction()
val isLogin = channel.receive()
YYLogUtils.w("收到消息:" + isLogin)
if (isLogin) {
nextAction()
}
}
}
fun loginFinished() {
launch {
async {
YYLogUtils.w("发送消息:" + LoginManager.isLogin())
channel.send(LoginManager.isLogin())
}
}
}
override fun onDestroy(owner: LifecycleOwner) {
cancel()
}
}
使用起来也是很简单,和线程的使用方式类似:
//协程的方式
mBtnProfile2.click {
LoginInterceptCoroutinesManager.get().checkLogin(loginAction = {
gotoLoginPage()
}, nextAction = {
gotoProfilePage()
})
}
然后我们需要在登录页面设置登录完成的调用
fun doLogin() {
showStateLoading()
CommUtils.getHandler().postDelayed({
showStateSuccess()
SP().putString(Constants.KEY_TOKEN, "abc")
LoginInterceptCoroutinesManager.get().loginFinished()
finish()
}, 500)
}
实现的效果:
这里扩展一下:之前我们使用 Channel 的原生方式来实现的,其实我们的逻辑是先注册监听,再发送数据,我们就可以使用 actor{} 这种快捷方式实现。
关于 actor 的使用参考我之前的文章 协程的并发安全。
我们修改前面的工具类:
class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {
companion object {
private var instance: LoginInterceptCoroutinesManager? = null
get() {
if (field == null) {
field = LoginInterceptCoroutinesManager()
}
return field
}
fun get(): LoginInterceptCoroutinesManager {
return instance!!
}
}
lateinit var sendChannel: SendChannel<Boolean>
fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {
launch {
if (LoginManager.isLogin()) {
nextAction()
return@launch
}
loginAction()
sendChannel = actor {
val isLogin = receive()
YYLogUtils.w("收到消息:" + isLogin)
if (isLogin) {
nextAction()
}
}
}
}
fun loginFinished() {
launch {
if (this@LoginInterceptCoroutinesManager::sendChannel.isInitialized){
YYLogUtils.w("发送消息:" + LoginManager.isLogin())
sendChannel.send(LoginManager.isLogin())
}
}
}
override fun onDestroy(owner: LifecycleOwner) {
cancel()
}
}
实现的效果和 Channel 是一样的。由于两者的实现思路是一致的,就放到一起讲了。下面看看协程中其他的方式还有哪些。
二、协程的广播
如果说协程的 Channel 通信是一对一的,那么协程的 Broadcast 是多对多的。我们可以像 Channel 一样的方式通过 Broadcast 来订阅消息。
我们改造上面的工具类:
class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {
companion object {
private var instance: LoginInterceptCoroutinesManager? = null
get() {
if (field == null) {
field = LoginInterceptCoroutinesManager()
}
return field
}
fun get(): LoginInterceptCoroutinesManager {
return instance!!
}
}
private val broadcastChannel = BroadcastChannel<Boolean>(Channel.BUFFERED)
fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {
launch {
if (LoginManager.isLogin()) {
nextAction()
return@launch
}
loginAction()
val receiveChannel = broadcastChannel.openSubscription()
val isLogin = receiveChannel.receive()
YYLogUtils.w("收到消息:" + isLogin)
if (isLogin) {
nextAction()
}
}
}
fun loginFinished() {
launch {
YYLogUtils.w("发送消息:" + LoginManager.isLogin())
broadcastChannel.send(LoginManager.isLogin())
}
}
override fun onDestroy(owner: LifecycleOwner) {
broadcastChannel.cancel()
cancel()
}
}
使用的方式没变化:
//协程的方式
mBtnProfile2.click {
LoginInterceptCoroutinesManager.get().checkLogin(loginAction = {
gotoLoginPage()
}, nextAction = {
gotoProfilePage()
})
}
private fun gotoLoginPage() {
gotoActivity<LoginDemoActivity>()
}
private fun gotoProfilePage() {
gotoActivity<ProfileDemoActivity>()
}
效果也是和上面的方式一致。
三、协程的恢复
通过 suspendCancellableCoroutine 我们可以自由的控制协程的暂停与恢复,关于如何使用可以参考我之前的文章 Kotlin协程-协程的暂停与恢复。
我们修改我们的工具类:
class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {
companion object {
private var instance: LoginInterceptCoroutinesManager? = null
get() {
if (field == null) {
field = LoginInterceptCoroutinesManager()
}
return field
}
fun get(): LoginInterceptCoroutinesManager {
return instance!!
}
}
private lateinit var mCancellableContinuation: CancellableContinuation<Boolean>
fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {
launch {
if (LoginManager.isLogin()) {
nextAction()
return@launch
}
loginAction()
val isLogin = suspendCancellableCoroutine<Boolean> {
mCancellableContinuation = it
YYLogUtils.w("暂停协程,等待唤醒")
}
YYLogUtils.w("已经恢复协程,继续执行")
if (isLogin) {
nextAction()
}
}
}
fun loginFinished() {
if (!this@LoginInterceptCoroutinesManager::mCancellableContinuation.isInitialized) return
if (mCancellableContinuation.isCancelled) return
mCancellableContinuation.resume(LoginManager.isLogin(), null)
}
override fun onDestroy(owner: LifecycleOwner) {
YYLogUtils.w("LoginInterceptCoroutinesManager - onDestroy")
mCancellableContinuation.cancel()
cancel()
}
}
运行的Log如下:
具体运行效果和上面一致,这里不过多展示
总结
不愧是Kotlin语法糖,可以看到实现的过程比线程要简单,和线程的思路相比,它们有部分是相同,又不完全相同,Kotlin的实现可以利用到一些协程的特点,更加的方便。
当然了如果我们项目并没有使用Kotlin构建,那么协程并不能用,就可以参考实现线程的方案了,毕竟还有很多老项目是Java语言发开的。
我虽然输出了一些 Kotlin 和协程的一些文章,但其实我对 Kotlin 和协程也只是入门水平,如果大家对协程实现拦截有更好的方案,还望不吝赐教,可以在评论区一起交流学习。
好了,我本人如有讲解不到位或错漏的地方,也希望同学们可以指出交流。
如果感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。