如何更好地進行 Android 元件化開發(四)登入攔截篇

語言: CN / TW / HK

theme: devui-blue highlight: hybrid


本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

前言

路由框架基本都會有路由攔截器,能實現 AOP 的功能,在跳轉前、跳轉後做一些自定義的邏輯處理。還有一個比較經典的應用場景,就是在跳轉過程中處理登入事件,這樣就不需要在目標頁重複做登入校驗。

封裝一套登入攔截功能可以提高一定的開發效率,不過如果嘗試過用路由的攔截器去封裝,會發現有一些地方不太好處理,封裝起來可能並不是那麼順利。下面個人會分享一些思路來幫助大家使用路由框架來封裝登入攔截功能。

實現登入攔截

先了解一下路由的攔截器怎麼使用,本文使用的路由框架是 ARouter,其它路由框架的用法也是大同小異。

ARouter 需要建立一個類實現 IInterceptor 介面,並新增 @Interceptor 註解宣告優先順序。實現登入攔截要判斷一下 postcard.path 是不是需要校驗登入的 path,是的話就中斷原本的路由,然後跳到登入頁面。

```kotlin @Interceptor(priority = 8, name = "登入攔截器") class SignInInterceptor : IInterceptor {

override fun process(postcard: Postcard, callback: InterceptorCallback) { if (isInterceptPath(postcard.path)) { ARouter.getInstance().build("/account/sign_in") .with(postcard.extras) // 傳遞引數 .withString(KEY_NEXT_ROUTE_PATH, postcard.path) // 傳遞目標頁面的 path .withTransition(postcard.enterAnim, postcard.exitAnim) .navigation() callback.onInterrupt(RuntimeException("The route path of '${postcard.path}' requires sign in.")) } else { callback.onContinue(postcard) } }

override fun init(context: Context) = Unit } ```

重寫的 process() 函式有兩個引數,其中的 callback 引數是用於決定是否進行攔截,呼叫了 callback.onInterrupt(exception) 中斷路由流程,而呼叫 callback.onContinue(postcard) 會交還控制權。至少需要呼叫其中一個,否則不會繼續路由。

上面的攔截器僅僅實現了攔截頁面跳轉到登入頁面,這當然還不夠,因為我們登入完還需要繼續跳轉到目標頁面。那麼可以在登入之後查一下 intent 有沒下一個頁面的 path,有的話就進行路由跳轉,沒有的話就關閉頁面。我們新增一個 Activity 的擴充套件:

kotlin fun Activity.signInSuccess(enterAnim: Int = -1, exitAnim: Int = -1) { val path = intent.getStringExtra(SignInInterceptor.KEY_NEXT_ROUTE_PATH) if (path != null) { intent.removeExtra(SignInInterceptor.KEY_NEXT_ROUTE_PATH) ARouter.getInstance().build(path) .with(intent.extras) .withTransition(enterAnim, exitAnim) .navigation() } else { setResult(Activity.RESULT_OK) if (enterAnim != -1 && exitAnim != -1) { overridePendingTransition(enterAnim, exitAnim) } } finish() }

在登入成功之後需要呼叫一下 signInSuccess() 擴充套件,這樣才會繼續之前的路由跳轉。

前面的 SignInInterceptor 再優化一下,通過一些配置去決定是否進行登入攔截。首先需要有登入頁面的 path 和需要校驗登入的 path,並且還要有個方法能驗證 App 有沒登入。我們給 SignInInterceptor 新增一個靜態的初始化函式配置這些資訊,這樣就更加通用一點了。

```kotlin @Interceptor(priority = Int.MAX_VALUE) class SignInInterceptor : IInterceptor {

override fun process(postcard: Postcard, callback: InterceptorCallback) { if (isInterceptPath(postcard.path)) { ARouter.getInstance().build("/account/sign_in") .with(postcard.extras) .withString(KEY_NEXT_ROUTE_PATH, postcard.path) .withTransition(postcard.enterAnim, postcard.exitAnim) .navigation() callback.onInterrupt(RuntimeException("The route path of '${postcard.path}' requires sign in.")) } else { callback.onContinue(postcard) } }

override fun init(context: Context) = Unit

companion object { const val KEY_NEXT_ROUTE_PATH = "next_route_path" internal var signInActivityPath: String? = null internal var requireSignInPaths = arrayListOf() internal var checkSignIn: () -> Boolean = { true }

fun init(signInActivityPath: String, requireSignInPaths: List<String>, block: () -> Boolean) {
  this.signInActivityPath = signInActivityPath
  this.requireSignInPaths.addAll(requireSignInPaths)
  checkSignIn = block
}

fun isInterceptPath(path: String) =
  signInActivityPath != null && path != signInActivityPath && requireSignInPaths.contains(path) && !checkSignIn()

} } ```

我們初始化該攔截器就會啟動登入攔截功能。

kotlin SignInInterceptor.init("/account/sign_in", listOf("/app/main", ...)) { AccountRepository.isSignIn }

完善轉場動畫

路由的攔截功能通常是個非同步操作,所以想跳轉某個頁面並關閉當前頁面會有點麻煩。按照我們平時的習慣會這麼來寫程式碼:

kotlin ARouter.getInstance().build("/xx/xxx").navigation() finish()

執行起來會發現有白屏的轉場動畫,這是因為 navigation() 函式內呼叫了這一段攔截程式碼:

image.png

這裡判斷了是不是綠色通道(Activity 型別預設為 false),不是的話就走攔截的流程,如果全部攔截器都不攔截,那才會回撥到 onContinue() 函式繼續跳轉。我們再找一下 onContinue() 函式執行的地方:

image.png

可以很清楚地看到是這是一個非同步回撥,那麼即使我們的程式碼是先呼叫路由跳轉再調 finish(),但是最終的邏輯是先呼叫 finish() 再非同步呼叫 startActivity(),導致轉場動畫會白屏。

要解決這個問題有兩種方式,一種是遮蔽路由攔截功能,配置為綠色通道,這樣路由跳轉時就立即執行 startActivity()。

kotlin ARouter.getInstance().build("/xx/xxx").greenChannel().navigation() finish()

這會忽略所有攔截器,一般不建議呼叫,除非真有這樣的需求。路由框架一般還能監聽導航的回撥,其中會有個回撥函式是在 startActivity() 之後才執行,那在該函式回撥時才 finish() 就沒有問題。我們在 navigation() 函式加多一個 NavCallback 引數:

kotlin ARouter.getInstance().build("/xx/xxx").navigation(this, object : NavCallback() { override fun onArrival(postcard: Postcard?) { finish() } })

我們可以看下原始碼確認一下執行順序,callback.onArrival() 確實在 startActivity() 之後呼叫。

image.png

我們優化一下前面的 signInSuccess(),從登入頁面用路由跳轉到目標頁面要在 onArrival() 的時候才 finish()。

kotlin fun Activity.signInSuccess(enterAnim: Int = -1, exitAnim: Int = -1) { val path = intent.getStringExtra(SignInInterceptor.KEY_NEXT_ROUTE_PATH) if (path != null) { intent.removeExtra(SignInInterceptor.KEY_NEXT_ROUTE_PATH) ARouter.getInstance().build(path) .with(intent.extras) .withTransition(enterAnim, exitAnim) .navigation(this, object : NavCallback() { override fun onArrival(postcard: Postcard?) { finish() } }) } else { setResult(Activity.RESULT_OK) if (enterAnim != -1 && exitAnim != -1) { overridePendingTransition(enterAnim, exitAnim) } finish() } }

現在路由跳轉後關閉當前頁面是沒問題了,但是如果被攔截跳到登入頁面,是會中斷原來要執行的 startActivity() 操作,那後續的 callback.onArrival() 也不會回撥,本來該執行 finish() 也中斷了。也就是說正常路由跳轉會關閉當前頁面,被攔截後不會關閉當前頁面了。

想在攔截後仍然能保留 onArrival() 回撥並不好處理,因為在攔截器是沒法回撥 callback.onArrival() 的。個人做些了嘗試後想到了個思路,在登入的攔截器裡不做跳轉的操作,只調用 callback.onInterrupt(exception) 中斷路由。

```kotlin @Interceptor(priority = Int.MAX_VALUE) class SignInInterceptor : IInterceptor {

override fun process(postcard: Postcard, callback: InterceptorCallback) { if (isInterceptPath(postcard.path)) { callback.onInterrupt(RuntimeException("The route path of '${postcard.path}' requires sign in.")) } else { callback.onContinue(postcard) } }

// ... } ```

如果路由操作被攔截器中斷後,會回撥 NavCallback 的 onInterrupt(postcard) 函式,我們在這裡才用路由跳轉到登入頁面,這樣在 NavCallback 內可以呼叫原本 onArrival(postcard) 函式。

```kotlin open class SignInNavCallback : NavCallback() {

override fun onArrival(postcard: Postcard) = Unit

override fun onInterrupt(postcard: Postcard) { if (SignInInterceptor.isInterceptPath(postcard.path)) { ARouter.getInstance().build(SignInInterceptor.signInActivityPath!!) .with(postcard.extras) .withString(SignInInterceptor.KEY_NEXT_ROUTE_PATH, postcard.path) .withTransition(postcard.enterAnim, postcard.exitAnim) .navigation(postcard.context, object : NavCallback() { override fun onArrival(postcard: Postcard) { [email protected](postcard) } }) } else { onOtherInterrupt(postcard) } }

open fun onOtherInterrupt(postcard: Postcard) = Unit } ```

最開始路由跳轉的 onArrival() 被中斷了不會回撥,那我們自己想辦法在後面登入頁面的路由跳轉執行回原本 onArrival() 函式。

把 NavCallback 改成 SignInNavCallback,這樣即使被登入攔截了也能關閉當前頁面。

kotlin ARouter.getInstance().build("/xx/xxx").navigation(this, object : SignInNavCallback() { override fun onArrival(postcard: Postcard?) { finish() } })

不過還有個問題,在不需要 onArrival() 的時候也得傳入 SignInNavCallback,否則跳轉不過登入頁面。

kotlin ARouter.getInstance().build("/xx/xxx").navigation(this, SignInNavCallback())

這麼用的話就有點奇怪了,所以個人建議再封裝一個 startActivity() 函式的擴充套件把路由跳轉的程式碼給隱藏了,內部會固定傳一個 SignInNavCallback。

```kotlin fun Context.startActivity( routePath: String, vararg pairs: Pair, enterAnim: Int = -1, exitAnim: Int = -1, onArrival: ((Postcard) -> Unit)? = null, onInterrupt: ((Postcard) -> Unit)? = null, onFound: ((Postcard) -> Unit)? = null, onLost: ((Postcard) -> Unit)? = null, ) { ARouter.getInstance().build(routePath) .with(bundleOf(*pairs)) .withTransition(enterAnim, exitAnim) .navigation(this, object : SignInNavCallback() { override fun onArrival(postcard: Postcard) { onArrival?.invoke(postcard) }

  override fun onOtherInterrupt(postcard: Postcard) {
    onInterrupt?.invoke(postcard)
  }

  override fun onFound(postcard: Postcard) {
    onFound?.invoke(postcard)
  }

  override fun onLost(postcard: Postcard) {
    onLost?.invoke(postcard)
  }
})

} ```

這樣路由跳轉的用法就更類似於原生的跳轉,並且內部的實現保證了登入攔截的邏輯是完整的。

kotlin startActivity("/xx/xxx")

如果需要傳參或路由跳轉後關閉當前頁面,可以這麼寫:

kotlin startActivity("/xx/xxx", "email" to email, onArrival = { finish() })

使用註解配置 path

我們回顧一下前面登入攔截器初始化的時候是直接傳入了登入頁面的 path 和其它需要驗證登入的 path。

kotlin SignInInterceptor.init("/account/sign_in", listOf("/message/message_list", ...)) { AccountRepository.isSignIn }

這個攔截器的初始化通常會在使用者元件裡執行,那麼就會有個問題,使用者元件怎麼知道哪些 path 需要校驗登入狀態呢?有的人可能會說把全部需要登入攔截的 path 都寫上不就好了,但是每當其它元件新增了 path 需要新增登入攔截,就會讓負責使用者元件的同事改一下程式碼,這樣協作開發的方式並不好。

所以最好是哪個元件有 path 需要登入攔截都可以自己配置,這可以參考路由框架用註解來實現。比如我們給一個 path 常量新增 @RequireSignInPath 註解,在路由跳轉時就會先驗證一下有沒登入。

kotlin @RequireSignInPath const val PATH_MESSAGE_LIST = "/message/message_list"

這種效果要怎麼實現呢?這會到 APT (Annotation Processing Tool) 註解處理器,APT 可以對原始碼檔案進行檢測找出其中的註解,並對註解進行額外的處理,通常是生成一些我們所需的模板類。

首先我們建立一個介面,定義個 loadInto() 函式,引數是字串陣列。

kotlin interface IRoutePaths { fun loadInto(list: ArrayList<String>) }

呼叫 loadInto() 函式就把一些字串新增到一個數組中。我們可以用 APT 生成 IRoutePaths 的實現類,新增 @RequireSignInPath 註解修飾的字串常量,比如生成了以下的類類:

kotlin public class ARouter$$Paths$$message implements IRoutePaths { @Override public void loadInto(ArrayList<String> paths) { paths.add(com.dylanc.componentization.message.constants.ConstantsKt.PATH_MESSAGE_LIST); } }

有這個類後只需反射該類的例項呼叫 loadInto(paths) 函式就完成了登入攔截的 path 配置,ARouter 的路由表也是這樣配置的。接下來我們講一下怎麼用 APT 生成檔案。

使用 APT 生成 Java 檔案

首先得建立一個 Java 模組用來處理註解,這裡我們建立一個 router-compiler 模組,記得要選擇 Java or Kotlin Library

image.png

使用 kapt 的方式依賴該模組,如果是 Java 專案把 kapt 改成 annotationProcessor。

dependencies { kapt project(path: ':router-compiler') }

這樣會訪問 router-compiler 裡一個固定的檔案:

image.png

注意這裡並不是有一個叫 META-INF.services 的資料夾,而是有 META-INF 和 services 兩個資料夾,檔名是 javax.annotation.processing.Processor,這些都是固定的。

檔案的內容就只有註解處理器的全類名,比如:

com.dylanc.componentization.router.compiler.PathsProcessor

那麼在編譯的時候會掃描 javax.annotation.processing.Processor 的內容,我們寫了一個 PathsProcessor 註解處理器,就會根據該註解處理器的邏輯去生成檔案。

在編寫 PathsProcessor 註解處理器的程式碼之前我們還得先有註解,我們定義一個 RequireSignInPath 註解,用來標記需要驗證登入的 path 常量。

kotlin @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.FIELD) annotation class RequireSignInPath

給 router-compiler 模組新增 JavaPoet 依賴,JavaPoet 提供了一些生成 Java 檔案的 API。

dependencies { implementation "com.squareup:javapoet:1.13.0" }

之後就能編寫註解處理器了,需要繼承 AbstractProcessor 類。重寫 getSupportedAnnotationTypes() 宣告會用到哪些註解,然後就能在 process() 函式編寫生成檔案的程式碼了。通過註解能獲取到類或屬性的資訊,再呼叫 JavaPoet 的 API 去生成自己所需的 Java 檔案。API 怎麼用就不介紹了,大家自行了解一下。下面是我們這次需要生成的程式碼,稍微閱讀下其實也很容易理解生成了怎麼樣的 Java 程式碼。

```kotlin class PathsProcessor : AbstractProcessor() { // ...

override fun process(annotations: MutableSet, roundEnv: RoundEnvironment): Boolean { val checkLoginPathElements = roundEnv.getElementsAnnotatedWith(RequireSignInPath::class.java).map { it as VariableElement } val arrayListType = ParameterizedTypeName.get(ClassName.get(ArrayList::class.java), ClassName.get(String::class.java)) val paramSpec = ParameterSpec.builder(arrayListType, "paths").build() val typeIRoutePaths = elementUtils.getTypeElement(IROUTE_PATHS)

val loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto")
  .addAnnotation(Override::class.java)
  .addModifiers(Modifier.PUBLIC)
  .addParameter(paramSpec)

checkLoginPathElements.forEach { element ->
  loadIntoMethodBuilder.addStatement("paths.add(${element.fullClassName}.${element.simpleName})")
}

val typeSpec = TypeSpec.classBuilder(ClassName.get(PACKAGE_NAME, "$PROJECT${SEPARATOR}Paths$SEPARATOR$moduleName"))
  .addSuperinterface(ClassName.get(typeIRoutePaths))
  .addModifiers(Modifier.PUBLIC)
  .addMethod(loadIntoMethodBuilder.build())
  .build()

try {
  JavaFile.builder(PACKAGE_NAME, typeSpec).build().writeTo(filer)
} catch (e: IOException) {
  e.printStackTrace()
}
return false

}

override fun getSupportedAnnotationTypes() = setOf(RequireSignInPath::class.java.canonicalName)

override fun getSupportedSourceVersion() = SourceVersion.RELEASE_8

private val VariableElement.fullClassName: String get() = ClassName.get(enclosingElement.asType()).toString() } ```

上面其實還少了獲取當前模組名的邏輯,如果多個模組生成的都是同名類就會有問題。

那要怎麼獲取模組名呢?個人之前也不是很清楚,閱讀了 ARouter 原始碼找到了以下邏輯:

```kotlin class PathsProcessor : AbstractProcessor() { private lateinit var filer: Filer private lateinit var elementUtils: Elements private var moduleName: String? = null

override fun init(processingEnv: ProcessingEnvironment) { super.init(processingEnv) filer = processingEnv.filer elementUtils = processingEnv.elementUtils

val options = processingEnv.options
if (options.isNotEmpty()) {
  moduleName = options["AROUTER_MODULE_NAME"]
}
if (!moduleName.isNullOrEmpty()) {
  moduleName = moduleName!!.replace("[^0-9a-zA-Z_]+".toRegex(), "")
  print("The user has configuration the module name, it was [$moduleName]")
} else {
  throw RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.")
}

}

override fun getSupportedOptions() = setOf("AROUTER_MODULE_NAME")

// ... } ```

可以看到 moduleName 是通過 processingEnv.options 獲取的,key 值是 AROUTER_MODULE_NAME。用過 ARouter 肯定對這個 key 不陌生,因為要在 build.gradle 新增下面的程式碼:

kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } }

我們在 build.gradle 給 AROUTER_MODULE_NAME 設定了 project.getName(),也就是說配置了模組名。那麼在註解處理器的 getSupportedOptions() 函式返回 AROUTER_MODULE_NAME 字串,就聲明瞭需要用到這個 key 的引數,之後能通過 processingEnv.options 獲取到對應值,從而得到模組名。

這樣通過 PathsProcessor 就能生成我們所需的 Java 檔案:

image.png

例項化 Java 類配置 path

生成檔案的類名是 ARouter$$Paths$$modulename,這是參考了 ARouter 的 APT 命名規則,並且包名也保持了一致。我們找一下編譯生成的程式碼,就能看到在一堆 ARouter 生成的 Java 檔案中混進去了我們自己生成的檔案。

image.png

為什麼要這麼做呢?因為 ARouter 會遍歷 com.alibaba.android.arouter.routes 包下的檔案,那麼我們偽裝成 ARouter 生成的檔案,讓 ARouter 順便把我們生成的類找出來,這就能省去遍歷找類的步驟了。

我們可以用 App Startup 實現自動初始化,對 App Startup 不熟悉的小夥伴可以看看前面的文章

通過閱讀 ARouter 的原始碼能發現 ARouter 在遍歷檔案後,會將遍歷到的類名儲存到 SharedPreferences 中。那麼我們在初始化 ARouter 後,就能用 SharedPreferences 獲取 ARouter 找過的類名,如果類名是我們定義的 ARouter$$Paths$$ 開頭,就使用反射例項化物件,並強轉成 IRoutePaths 介面呼叫 loadInto() 函式。

```kotlin class RouterInitializer : Initializer {

@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") override fun create(context: Context) { val applicationInfo = context.packageManager.getApplicationInfo(context.packageName, 0) val isDebug = applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0 if (isDebug) { ARouter.openLog() ARouter.openDebug() } ARouter.init(context as Application)

try {
  val sharedPreferences = context.getSharedPreferences(Consts.AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
  val routerMap = HashSet(sharedPreferences.getStringSet(Consts.AROUTER_SP_KEY_MAP, HashSet()))
  for (className in routerMap) {
    if(className.startsWith("${Consts.ROUTE_ROOT_PAKCAGE}.ARouter$$Paths$$")){
      (Class.forName(className).newInstance() as? IRoutePaths)?.loadInto(SignInInterceptor.requireSignInPaths)
    }
  }

  ARouter.logger.debug("Paths", SignInInterceptor.requireSignInPaths.toString())
} catch (_: NoSuchFieldException) {
} catch (e: Exception) {
  e.printStackTrace()
}

}

override fun dependencies() = emptyList<Class<Initializer<*>>>() } ```

在 manifests 宣告 Provider 就能自動初始化了。

xml <application> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="com.dylanc.componentization.router.RouterInitializer" android:value="androidx.startup" /> </provider> </application>

這樣 SignInInterceptor.requireSignInPaths 陣列就會自動新增需要驗證登入的 path 了。

同理登入頁面的 path 也能用註解配置,我們可以再新增一個 @SignInActivityPath 註解實現類似的邏輯。既然 path 都用註解配置了,那麼 SignInInterceptor 的初始化只需傳入一個回撥去判斷是否登入了。

kotlin SignInInterceptor.init { AccountRepository.isSignIn }

優化依賴關係

前面登入攔截的封裝程式碼我們會放到一個 router 模組,用 APT 生成 Java 檔案的程式碼放在 router-compiler 模組,那麼註解類 RequireSignInPath 和 SignInActivityPath 該放在哪個模組呢?

註解類需要在兩個地方使用,首先會在 router-compiler 模組生成 Java 檔案時用到,其次要在各個元件模組修飾需要登入攔截的 path 常量。如果註解類放在 router-compiler 模組,用了 kapt 依賴的模組是沒法訪問到裡面的程式碼的。通常把註解類抽取到一個 router-annotations 中,給 router-compiler 模組和各個元件模組依賴。

這樣我們就有了三個模組:

image.png

那麼我們在元件模組需要新增三個依賴才能使用登入攔截功能。

implementation project(path: ':router') // 用生成的檔案配置 path implementation project(path: ':router-annotations') // 提供註解 kapt project(path: ':router-compiler') // 生成檔案

這樣使用的話就有點麻煩了,可以優化 router-annotations 依賴放到 router 模組中,通過 api 的依賴方式會對上層模組可見。

api project(path: ':router-annotations')

這就只需要新增兩行依賴了。

implementation project(path: ':router') kapt project(path: ':router-compiler')

使用了 APT 的第三方庫通常也是這樣的結構,比如 ButterKnife 也會有個 butterknife-annotations 依賴,但是我們重來沒有新增過,因為放到了 butterknife 依賴中。

完整用法

首先在賬戶元件初始化 SignInInterceptor,告訴攔截器如何判斷是否登入了。

kotlin SignInInterceptor.init { AccountRepository.isSignIn }

給登入頁面的 path 新增 @SignInActivityPath 註解。

kotlin @SignInActivityPath const val PATH_SIGN_IN = "/account/sign_in"

在登入頁面請求登入成功後呼叫了 signInSuccess() 擴充套件函式才能繼續跳轉之前攔截的頁面,這樣準備工作就完成了。

```kotlin @Route(path = PATH_SIGN_IN) class SignInActivity : BaseActivity() { // ...

private fun onRequestSuccess(account: Account) { // ...

signInSuccess()

} } ```

如果有頁面要做登入攔截,就給對應的 path 常量新增 @RequireSignInPath 註解。

kotlin @RequireSignInPath const val PATH_NAIN = "/app/main"

需要路由跳轉的時候要改成更簡潔的 startActivity() 擴充套件進行跳轉,這樣沒有登入才會先跳到登入頁面。

kotlin lifecycleScope.launch { delay(2000) startActivity(PATH_MAIN, onArrival = { finish() }) }

總結

本文介紹路由攔截器的用法,可以在跳轉前、跳轉後做一些自定義的邏輯處理,可以在跳轉過程中處理登入事件。

之後帶著大家用 ARouter 封裝了一套登入攔截的功能,用法比較通用,換成其它路由框架實現也是可以的,整體的封裝的思路和步驟都是類似的:

  1. 使用攔截器中斷攔截一些需要校驗登入的路由,並跳轉到登入頁面;
  2. 在登入頁面登入成功後需要繼續跳轉之前攔截的路由,提供一個 signInSuccess() 的擴充套件在登入成功後呼叫;
  3. 適配路由跳轉後關閉當前頁面的用法,通常監聽路由導航的回撥,可以封裝一個路由的 startActivity() 擴充套件來隱藏回撥的引數;
  4. 結合路由框架的實現原理,使用註解來配置登入頁面的 path 和需要校驗登入的 path,使其可以在各自的元件模組配置登入攔截;

關於我

一個興趣使然的程式“工匠”  。有程式碼潔癖,喜歡封裝,對封裝有一定的個人見解,有不少個人原創的封裝思路。GitHub 有分享一些幫助搭建開發框架的開源庫,有任何使用上的問題或者需求都可以提 issues 或者加我微信直接反饋。