Android登录拦截的场景-基于拦截器模式实现

前言

前面的文章讲了一些APP登录拦截再执行的功能实现的几种方案,登录拦截,登录拦截,唉?我们能不能用拦截器模式实现?

关于拦截的实战应用,之前讲过两篇不同的类型,一种是值传递的方式 拦截实现Log的打印与保存。另一种是非值传递的方式 拦截弹窗的展示。不了解的可以阅读一下我之前文章。

如何实现拦截器拦截登录逻辑?我们整理一下思路:

  • 我们拦截登录的场景是通过是否已经登录的状态来判断是否拦截,是外部变量的判断,我们无需值传递的方式。
  • 我们需要添加固定一个拦截器判断是否登录,是否允许通行。
  • 我们需要动态的添加一个拦截器,用于执行跳转个人中心的逻辑,并且在执行完毕之后移除拦截器。
  • 我们在定义一个管理类,定义统一的入口,添加拦截器,移除拦截,继续登录拦截等操作。

下面我们就开始吧,尝试拦截器的定义与实现。

一、默认拦截器

这是一套固定的代码,我们先把拦截器基类拿过来定义。

interface Interceptor {
    fun intercept(chain: InterceptChain)
}
abstract class BaseInterceptImpl : Interceptor {

    protected var mChain: InterceptChain? = null

    @CallSuper
    override fun intercept(chain: InterceptChain) {
        mChain = chain
    }

}
class InterceptChain private constructor(
    // 弹窗的时候可能需要Activity/Fragment环境。
    val activity: FragmentActivity? = null,
    val fragment: Fragment? = null,
    private var interceptors: MutableList<Interceptor>?
) {
    companion object {

        @JvmStatic
        fun create(count: Int = 0): Builder {
            return Builder(count)
        }
    }

    private var index: Int = 0

    // 执行拦截器。
    fun process() {
        interceptors ?: return
        when (index) {
            in interceptors!!.indices -> {
                val interceptor = interceptors!![index]
                index++
                interceptor.intercept(this)
            }

            interceptors!!.size -> {
                clearAllInterceptors()
            }
        }
    }

    private fun clearAllInterceptors() {
        interceptors?.clear()
        interceptors = null
    }

    // 构建者模式。
    open class Builder(private val count: Int = 0) {
        private val interceptors by lazy(LazyThreadSafetyMode.NONE) {
            ArrayList<Interceptor>(count)
        }
        private var activity: FragmentActivity? = null
        private var fragment: Fragment? = null

        // 添加一个拦截器。
        fun addInterceptor(interceptor: Interceptor): Builder {
            if (!interceptors.contains(interceptor)) {
                interceptors.add(interceptor)
            }
            return this
        }

        // 关联Fragment。
        fun attach(fragment: Fragment): Builder {
            this.fragment = fragment
            return this
        }

        // 关联Activity。
        fun attach(activity: FragmentActivity): Builder {
            this.activity = activity
            return this
        }


        fun build(): InterceptChain {
            return InterceptChain(activity, fragment, interceptors)
        }
    }
}

我们把之前的代码拷贝过来修改一下,由于我们不是弹窗逻辑,不依赖于Activity/Fragment,所以我们可以更加的精简代码,修改如下:

interface Interceptor {
    fun intercept(chain: LoginInterceptChain)
}
abstract class BaseLoginInterceptImpl : Interceptor {

    protected var mChain: LoginInterceptChain? = null

    @CallSuper
    override fun intercept(chain: LoginInterceptChain) {
        mChain = chain
    }

}

二、拦截器管理类与默认拦截器

由于没有那么多的参数,我们就无需使用构建者模式,直接设置管理类定义为单例对象,直接修改即可。

object LoginInterceptChain {

    private var index: Int = 0

    private val interceptors by lazy(LazyThreadSafetyMode.NONE) {
        ArrayList<Interceptor>(2)
    }

    //默认初始化Login的拦截器
    private val loginIntercept = LoginInterceptor()


    // 执行拦截器。
    fun process() {

        if (interceptors.isEmpty()) return

        when (index) {
            in interceptors.indices -> {
                val interceptor = interceptors[index]
                index++
                interceptor.intercept(this)
            }

            interceptors.size -> {
                clearAllInterceptors()
            }
        }
    }

    // 添加一个拦截器。
    fun addInterceptor(interceptor: Interceptor): LoginInterceptChain {
        //默认添加Login判断的拦截器
        if (!interceptors.contains(loginIntercept)) {
            interceptors.add(loginIntercept)
        }

        if (!interceptors.contains(interceptor)) {
            interceptors.add(interceptor)
        }

        return this
    }


    //放行登录判断拦截器
    fun loginFinished() {
        if (interceptors.contains(loginIntercept) && interceptors.size > 1) {
            loginIntercept.loginfinished()
        }
    }

    //清除全部的拦截器
    private fun clearAllInterceptors() {
        index = 0
        interceptors.clear()
    }

}

主要是要定义一个CheckLogin的拦截器

/**
 * 判断是否登录的拦截器
 */
class LoginInterceptor : BaseLoginInterceptImpl() {

    override fun intercept(chain: LoginInterceptChain) {
        super.intercept(chain)

        if (LoginManager.isLogin()) {
            //如果已经登录 -> 放行, 转交给下一个拦截器
            chain.process()
        } else {
            //如果未登录 -> 去登录页面
            LoginDemoActivity.startInstance()
        }
    }


    fun loginfinished() {
        //如果登录完成,调用方法放行到下一个拦截器
        mChain?.process()
    }
}

具体的代码已经在拦截器管理类中提供了:

    // 添加一个拦截器。
    fun addInterceptor(interceptor: Interceptor): LoginInterceptChain {
        //默认添加Login判断的拦截器
        if (!interceptors.contains(loginIntercept)) {
            interceptors.add(loginIntercept)
        }

        if (!interceptors.contains(interceptor)) {
            interceptors.add(interceptor)
        }

        return this
    }


    //放行登录判断拦截器
    fun loginFinished() {
        if (interceptors.contains(loginIntercept) && interceptors.size > 1) {
            loginIntercept.loginfinished()
        }
    }

检查登录状态的拦截器我们需要固定在拦截器链的第一个位置,当我们登录完成之后我们通过管理类间接的调用这个拦截器放行。

由于我们这个场景是可以多次调用的,所以我们每次拦截使用完毕之后我们需要清除全部的拦截器,或者也可以只清除最后一个拦截。

    private fun clearAllInterceptors() {
        index = 0
        interceptors.clear()
    }

一定要注意,由于是重复使用的拦截器,记得index要归位,不然无法第二次执行。

二、自定义拦截实现

之前我们定义了检查登录状态的拦截器,并固定在拦截器链的第一个位置,然后我们需要自定义一个拦截器,用于登录完成之后的继续执行。

如果是Kotlin语言,我们通过构造方法传入一个高阶函数回调即可,

/**
 * 登录完成下一步的拦截器
 */
class LoginNextInterceptor(private val action: () -> Unit) : BaseLoginInterceptImpl() {

    override fun intercept(chain: LoginInterceptChain) {
        super.intercept(chain)

        if (LoginManager.isLogin()) {
            //如果已经登录执行当前的任务
            action()
        }

        mChain?.process()
    }


}

如果是Java语言开发,我们直接定义为abstract的方法,使用的时候可以重写抽象方法即可使用,有需要的话大家使用Java语言自己实现。

/**
 * 登录完成下一步的拦截器
 */
abstract class LoginNextInterceptor() : BaseLoginInterceptImpl() {

    override fun intercept(chain: LoginInterceptChain) {
        super.intercept(chain)

        if (LoginManager.isLogin()) {
            //如果已经登录执行当前的任务
            runAction()
        }

        mChain?.process()
    }

    abstract fun runAction()

}

那么我们就能使用了。这里使用的是Kotlin构造实现高阶函数的方式:

      //拦截器的方式
        mBtnProfile.click {
            checkLogin()
        }

    private fun checkLogin() {
        LoginInterceptChain.addInterceptor(LoginNextInterceptor {
            gotoProfilePage()
        }).process()
    }

我们在登录完成的时候调用 loginfinished 方法即可

        fun doLogin() {
            showStateLoading()

            CommUtils.getHandler().postDelayed({
                showStateSuccess()

                SP().putString(Constants.KEY_TOKEN, "abc")

                //拦截器放行
                LoginInterceptChain.loginFinished()

                finish()

            }, 500)

        }

总结

拦截器模式真的是非常好用的设计模式,在Android的开发过程中用到的地方很多,可以非常方便的实现各种各样的效果。

关于登录拦截再执行的这一个场景,我总结了几种方式,到今天这一篇就算完结了,大家更喜欢哪一种方式呢?

啥?你问我使用的哪一种方案? 其实我个人觉得AOP和Intent太麻烦,动态代理担心以后会有兼容性问题,并且不能覆盖全部场景,协程和线程的实现对内存的开销相对来说会稍微大一点,我个人还是比较喜欢使用简单,方便集成,内存开销小的一些方式,所以个人比较推荐方法池,通知,拦截器,这三种方案。

其实几种方案都能实现这个场景,大家按需选择即可。

当然了,其实还有其他的一些的方法我并没有穷举出来,比如有人基于ARouter来实现拦截,有人基于OkHttp的拦截方案实现,这些基于一些框架实现的我并没有讲,第一是因为实现方式都是类似拦截器的方式,第二是很多同学在开发中并不会使用这些框架。