Android登入攔截的場景-基於攔截器模式實現

語言: CN / TW / HK

theme: juejin highlight: a11y-dark


攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第27天,點選檢視活動詳情

登入攔截系列: * 登入攔截-AOP的實現 * 登入攔截-方法池和訊息回撥的實現 * 登入攔截-執行緒的實現 * 登入攔截-協程的實現 * 登入攔截-Intent的實現 * 登入攔截-動態代理Hook的實現 * 登入攔截-攔截器模式的實現

前言

前面的文章講了一些APP登入攔截再執行的功能實現的幾種方案,登入攔截,登入攔截,唉?我們能不能用攔截器模式實現?

關於攔截的實戰應用,之前講過兩篇不同的型別,一種是值傳遞的方式 攔截實現Log的列印與儲存。另一種是非值傳遞的方式 攔截彈窗的展示。不瞭解的可以閱讀一下我之前文章。

如何實現攔截器攔截登入邏輯?我們整理一下思路:

  • 我們攔截登入的場景是通過是否已經登入的狀態來判斷是否攔截,是外部變數的判斷,我們無需值傳遞的方式。
  • 我們需要新增固定一個攔截器判斷是否登入,是否允許通行。
  • 我們需要動態的新增一個攔截器,用於執行跳轉個人中心的邏輯,並且在執行完畢之後移除攔截器。
  • 我們在定義一個管理類,定義統一的入口,新增攔截器,移除攔截,繼續登入攔截等操作。

下面我們就開始吧,嘗試攔截器的定義與實現。

一、預設攔截器

這是一套固定的程式碼,我們先把攔截器基類拿過來定義。

kotlin interface Interceptor { fun intercept(chain: InterceptChain) }

```kotlin abstract class BaseInterceptImpl : Interceptor {

protected var mChain: InterceptChain? = null

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

} ```

```kotlin class InterceptChain private constructor( // 彈窗的時候可能需要Activity/Fragment環境。 val activity: FragmentActivity? = null, val fragment: Fragment? = null, private var interceptors: MutableList? ) { 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,所以我們可以更加的精簡程式碼,修改如下:

kotlin interface Interceptor { fun intercept(chain: LoginInterceptChain) }

```kotlin abstract class BaseLoginInterceptImpl : Interceptor {

protected var mChain: LoginInterceptChain? = null

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

} ```

二、攔截器管理類與預設攔截器

由於沒有那麼多的引數,我們就無需使用構建者模式,直接設定管理類定義為單例物件,直接修改即可。

```kotlin 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的攔截器 ```kotlin /* * 判斷是否登入的攔截器 / class LoginInterceptor : BaseLoginInterceptImpl() {

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

    if (LoginManager.isLogin()) {
        //如果已經登入 -> 放行, 轉交給下一個攔截器
        chain.process()
    } else {
        //如果未登入 -> 去登入頁面
        LoginDemoActivity.startInstance()
    }
}


fun loginfinished() {
    //如果登入完成,呼叫方法放行到下一個攔截器
    mChain?.process()
}

} ```

具體的程式碼已經在攔截器管理類中提供了: ```kotlin // 新增一個攔截器。 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()
    }
}

```

檢查登入狀態的攔截器我們需要固定在攔截器鏈的第一個位置,當我們登入完成之後我們通過管理類間接的呼叫這個攔截器放行。

由於我們這個場景是可以多次呼叫的,所以我們每次攔截使用完畢之後我們需要清除全部的攔截器,或者也可以只清除最後一個攔截。 kotlin private fun clearAllInterceptors() { index = 0 interceptors.clear() }

一定要注意,由於是重複使用的攔截器,記得index要歸位,不然無法第二次執行。

二、自定義攔截實現

之前我們定義了檢查登入狀態的攔截器,並固定在攔截器鏈的第一個位置,然後我們需要自定義一個攔截器,用於登入完成之後的繼續執行。

如果是Kotlin語言,我們通過構造方法傳入一個高階函式回撥即可, ```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語言自己實現。 ```kotlin /* * 登入完成下一步的攔截器 / abstract class LoginNextInterceptor() : BaseLoginInterceptImpl() {

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

    if (LoginManager.isLogin()) {
        //如果已經登入執行當前的任務
        runAction()
    }

    mChain?.process()
}

abstract fun runAction()

} ```

那麼我們就能使用了。這裡使用的是Kotlin構造實現高階函式的方式: ```kotlin //攔截器的方式 mBtnProfile.click { checkLogin() }

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

```

我們在登入完成的時候呼叫 loginfinished 方法即可

```kotlin fun doLogin() { showStateLoading()

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

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

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

            finish()

        }, 500)

    }

```

效果:

test_05.gif

總結

攔截器模式真的是非常好用的設計模式,在Android的開發過程中用到的地方很多,可以非常方便的實現各種各樣的效果。

關於登入攔截再執行的這一個場景,我總結了幾種方式,到今天這一篇就算完結了,大家更喜歡哪一種方式呢?

啥?你問我使用的哪一種方案? 其實我個人覺得AOP和Intent太麻煩,動態代理擔心以後會有相容性問題,並且不能覆蓋全部場景,協程和執行緒的實現對記憶體的開銷相對來說會稍微大一點,我個人還是比較喜歡使用簡單,方便整合,記憶體開銷小的一些方式,所以個人比較推薦方法池,通知,攔截器,這三種方案。

其實幾種方案都能實現這個場景,大家按需選擇即可。

當然了,其實還有其他的一些的方法我並沒有窮舉出來,比如有人基於ARouter來實現攔截,有人基於OkHttp的攔截方案實現,這些基於一些框架實現的我並沒有講,第一是因為實現方式都是類似攔截器的方式,第二是很多同學在開發中並不會使用這些框架。

大家自己目前用的又是哪一種方案呢?大家更中意哪一種呢?歡迎評論區討論哦。當然了,我個人能力也有限,如果有其他型別的更多更好的方式,各位高工也可以評論區指出哦。

好了,我本人如有講解不到位或錯漏的地方,希望同學們可以指出交流。

如果感覺本文對你有一點點點的啟發,還望你能點贊支援一下,你的支援是我最大的動力。

Ok,這一期就此完結。

「其他文章」