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的拦截方案实现,这些基于一些框架实现的我并没有讲,第一是因为实现方式都是类似拦截器的方式,第二是很多同学在开发中并不会使用这些框架。