Android登录拦截的场景-基于协程的实现

2,182 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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)

        }

实现的效果:

login_04.gif

这里扩展一下:之前我们使用 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如下:

image.png

具体运行效果和上面一致,这里不过多展示

总结

不愧是Kotlin语法糖,可以看到实现的过程比线程要简单,和线程的思路相比,它们有部分是相同,又不完全相同,Kotlin的实现可以利用到一些协程的特点,更加的方便。

当然了如果我们项目并没有使用Kotlin构建,那么协程并不能用,就可以参考实现线程的方案了,毕竟还有很多老项目是Java语言发开的。

我虽然输出了一些 Kotlin 和协程的一些文章,但其实我对 Kotlin 和协程也只是入门水平,如果大家对协程实现拦截有更好的方案,还望不吝赐教,可以在评论区一起交流学习。

好了,我本人如有讲解不到位或错漏的地方,也希望同学们可以指出交流。

如果感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。

Ok,这一期就此完结。