探索 ARouter 原理

首圖.png

1. ARouter 原理概述

ARouter 是阿里開(kāi)源的一款幫助 Android APP 進(jìn)行組件化改造的路由框架,可以實(shí)現(xiàn)在同一個(gè)項(xiàng)目中互不依賴的的模塊的 Activity 之間跳轉(zhuǎn)。

ARouter 的路由參數(shù)攔截器都是用注解來(lái)標(biāo)注的。注解在 Retrofit、Dagger 和 EventBus 中都有使用,注解分為運(yùn)行時(shí)注解編譯時(shí)注解

ARouter 的跳轉(zhuǎn)是基于路由表 RouterMap 實(shí)現(xiàn)的,負(fù)責(zé)生成路由表的是 RouteProcessor ,負(fù)責(zé)加載路由表的是 LogisticsCenterRegisterTransform

RouteProcessor 是一個(gè)注解處理器,是 AbstractProcessor 的子類。編譯時(shí)注解是依賴注解處理工具 APT(Annotation Processing Tool)實(shí)現(xiàn)的,用于在編譯時(shí)掃描和處理注解,通過(guò) APT 我們能少寫很多模板代碼。在編譯時(shí),編譯器會(huì)檢查 AbstractProcessor 的子類,并調(diào)用 AbstractProcessor 的子類的 process() 方法,然后把添加了注解的元素都傳到 process() 方法中,這樣我們就可以在 process() 函數(shù)中生成新的 Java 類文件。

在 RouteProcessor 的 process() 方法中,會(huì)調(diào)用 parseRoutes() 方法,parseRoutes() 方法會(huì)用 JavaPoet API 來(lái)生成 Java 代碼,具體的代碼就是 Activity 等類的 Class 信息。除了 RouteProcessor ,ARouter 中還有參數(shù)注解處理器 AutowiredProcessor 和攔截器注解處理器 InterceptorProcessor ,它們的原理和 RouteProcessor 是一樣的。

在 _ARoute 的 navigation() 方法中,首先會(huì)處理預(yù)處理服務(wù),然后會(huì)讓 LogisticsCenter 填充 Postcard 中的信息,如果 LogisticsCenter 沒(méi)有找到對(duì)應(yīng)的路由信息的話,就會(huì)走降級(jí)策略的邏輯,如果 LogisticsCenter 找到對(duì)應(yīng)的路由信息的話,就會(huì)判斷是不是走綠色通道,如果不走綠色通道的話就由攔截器鏈決定要不要跳轉(zhuǎn)。如果走綠色通道的話,就直接按 Fragment 和 Activity 等不同的類型進(jìn)行跳轉(zhuǎn)。

預(yù)處理服務(wù)具體就是一個(gè) PretreatmentService 接口,只要定義一個(gè)實(shí)現(xiàn)了這個(gè)接口的類,并給這個(gè)類加一個(gè) @Route 注解就可以使用了,預(yù)處理服務(wù)的作用,是做一些跳轉(zhuǎn)的時(shí)候,在加載路由表前的判斷。

降級(jí)策略的作用是跳轉(zhuǎn)路由的信息缺失的時(shí)候,要做的事情,比如說(shuō)給用戶彈一個(gè)錯(cuò)誤提示或記錄錯(cuò)誤日志等,降級(jí)策略對(duì)應(yīng)的是一個(gè) DegradeService 接口,定義一個(gè)實(shí)現(xiàn)這個(gè)接口的類,并添加上 @Route 注解就可以使用降級(jí)策略了。

綠色通道的作用就是判斷要不要走攔截器鏈,比如說(shuō)我們定義了一個(gè)登陸攔截器,但是某個(gè)頁(yè)面不需要做這個(gè)判斷,就可以走綠色通道,走綠色通道只要在調(diào)用 build() 方法后調(diào)用 greenChannel() 方法就可以了。

攔截器具體就是一個(gè)添加了 @Interceptor 注解并實(shí)現(xiàn)了 IInterceptor 接口的類,通過(guò)攔截器我們能做一些類似登錄態(tài)判斷等邏輯。

負(fù)責(zé)生成路由表的是 LogisticsCenterRegisterTransform ,LogisticsCenter 注冊(cè)路由表的方式是在運(yùn)行時(shí)通過(guò) ClassUtils 加載 dex 文件中的內(nèi)容,然后再通過(guò)反射初始化這些類的信息,并保存到倉(cāng)庫(kù) Warehouse 中。Dex 文件是 Android 平臺(tái)的可執(zhí)行文件,類似于 Windows 中的 exec 文件,每個(gè) APK 安裝包中都有 dex 文件,dex 文件中包含了 app 的所有源碼,反編譯后能看到對(duì)應(yīng)分 java 源碼。

LogisticCenter 中有一個(gè) loadRouterMap() 方法,這個(gè)方法中默認(rèn)只有一行代碼,這行代碼就是把 registerByPlugin 字段的值改為 false 。初始化 ARouter 時(shí),會(huì)間接調(diào)用到 LogisticsCenter 的 init() 方法,在 LogisticsCenter 的 init() 方法中,會(huì)判斷是否由插件加載路由表,如果不是的話,就在運(yùn)行時(shí),通過(guò) ClassUtils 從 Dex 文件中加載路由表的信息。如果是由 Gradle 插件來(lái)注冊(cè)路由表的話,那么就由 RegisterTransform 從 Jar 文件中讀取路由表的信息。 RegisterTransform 繼承了 Transform 類,Transform 是 Android 官方提供的用來(lái)修改 class 文件的 API ,每個(gè) Transform 都是一個(gè) Gradle 任務(wù),能讀取和處理 jar、aar 和 resource 等資源,用戶自定義的 Transform 會(huì)插在 Transform 隊(duì)列的最前面。

Transform API 可以做很多的事情,比如在所有的 class 文件中插樁,做 UI 、內(nèi)存和網(wǎng)絡(luò)方面的性能監(jiān)控。還可以修改某個(gè)第三方庫(kù)的 class 文件,修改它的邏輯。還可以在 Log 中插入當(dāng)前代碼行數(shù),這樣更容易定位問(wèn)題。還可以對(duì)任何類進(jìn)行動(dòng)態(tài)代理。還可以實(shí)現(xiàn)打印出某個(gè)方法的入?yún)⒑统鰠⒌拇a。

2. ARouter 簡(jiǎn)介

ARouter 是阿里開(kāi)源的一款幫助 Android App 進(jìn)行組件化改造的路由框架,是 Android 平臺(tái)中對(duì)頁(yè)面和服務(wù)提供路由功能的中間件,可以實(shí)現(xiàn)在不同模塊的 Activity 之間跳轉(zhuǎn)。ARouter 的特點(diǎn)是靈活性強(qiáng)還能幫助項(xiàng)目解耦

靈活性強(qiáng)指的是在一些復(fù)雜的業(yè)務(wù)場(chǎng)景下,很多功能都是運(yùn)營(yíng)人員動(dòng)態(tài)配置的。比如電商系統(tǒng)需要下發(fā)一個(gè)活動(dòng)頁(yè)面,App 事先不知道該活動(dòng)具體的目標(biāo)頁(yè)面,但如果提前做好了頁(yè)面映射,就可以自由配置了。

幫助解耦指的是,隨著業(yè)務(wù)量增長(zhǎng),我們項(xiàng)目代碼會(huì)越來(lái)越多,開(kāi)發(fā)人員之間的協(xié)作也會(huì)變得越來(lái)越復(fù)雜,而解決這個(gè)問(wèn)題的常見(jiàn)方案是插件化和組件化。插件化和組件化的前提是代碼解耦,解耦后還要保持頁(yè)面之間的依賴關(guān)系,這時(shí)就要一套路由機(jī)制了。

ARouter 支持直接解析標(biāo)準(zhǔn) URL 跳轉(zhuǎn)多模塊工程使用添加多個(gè)攔截器單獨(dú)作為依賴注入框架配置轉(zhuǎn)場(chǎng)動(dòng)畫生成路由文檔等多個(gè)特性。

3. ARouter 架構(gòu)概覽

ARouter 架構(gòu)-路由表.png

ARouter 項(xiàng)目中包含了 API編譯器 Compiler插件 Gradle Plugin注解 Annotation 4 個(gè)模塊。

API 模塊由launchercoreexceptionthreadfacedeutilsbase子模塊組成。

launcher,包含了啟動(dòng)器 ARouter,core 包含物流中心 LogsticsCenter倉(cāng)庫(kù) Warehouse 等類。exception 包含了一些異常類。thread 包中包含了CancellableCountDownLatch,ARRouter 的攔截器鏈是放在子線程中執(zhí)行的,就用到了它。facede 模塊包含了導(dǎo)航回調(diào) NavigationCallbackIInterceptor 等接口。utils 包含了 ARouter 自定義的日志打印器等工具類。base 包下只有一個(gè)用于保存攔截器的 UnitqueKeyTreeMap

ARouter 的 Compiler 模塊包含用于生成路由表的類,@Autowired@Interceptor@Route 注解對(duì)應(yīng)的注解處理器分別是 AutowiredProcessor、InterceptorProcessor 以及 RouteProcessor 等注解處理器都在 Compiler 包中。

Register Plugin 模塊包含了注冊(cè)代碼生成器 RegisterCodeGenerator 和 RegisterTransform,如果我們使用了 ARouter 的路由表加載插件,那這個(gè)路由表就會(huì)由 Register 插件加載。

Annotation 模塊只包含了一些 @Autowired 等注解和路由類型 RouteType 等枚舉類。

4. ARouter 基本用法

接下來(lái)的代碼會(huì)統(tǒng)一用 Kotlin 來(lái)演示。

1. 添加依賴與配置
添加依賴.png
2. 聲明路徑

使用 ARouter 要用 @Route 注解聲明跳轉(zhuǎn)目標(biāo)的路徑,在這里要注意,最前面的斜杠是不能少的,而且路徑至少有兩級(jí)。

group 是可選的,ARouter 內(nèi)部會(huì)對(duì) path 進(jìn)行分組,以下面這段代碼為例,如果不傳 group 的話,那 ARouter 會(huì)把 goods 作為該路徑的 group ,否則 taobao 就是該路徑所屬的 group 。

聲明路徑.png
3. 初始化 ARouter

打印日志和開(kāi)啟調(diào)試模式必須寫 在init() 之前,否則這些配置在初始化過(guò)程中將無(wú)效。

initARouter.png
4. 跳轉(zhuǎn)
跳轉(zhuǎn)1.png

在跳轉(zhuǎn)時(shí),我們也可以傳 group,比如這個(gè)例子中傳了 taobao,那就會(huì)跳轉(zhuǎn)到 taobao 分組下的商品詳情頁(yè),不傳的話,就會(huì)跳到 goods 分組下的詳情頁(yè)。

5. 明信片 Postcard

在講 ARouter 的路由表生成原理前,我們先來(lái)看下在路由表生成和加載過(guò)程中非常重要的 RouteMetaPostcard 。當(dāng)我們調(diào)用 ARouter.getinstance().build() 時(shí),其實(shí)是在創(chuàng)建一個(gè)明信片 Postcard 對(duì)象,withXXX() 和 navigation() 等方法就是它的方法,Postcard 的 navigation() 方法最終調(diào)用的是 _ARouter 的 navigation() 方法,關(guān)于 _ARouter 的實(shí)現(xiàn)后面會(huì)講。

按 Postcard 的注釋來(lái)說(shuō),它是路線圖的容器,我們可以把它看做是一張包含了收件人信息以及特定內(nèi)容的明信片。

Postcard 繼承了 RouteMeta,RouteMeta 是路由表的內(nèi)容,而 Postcard 則包含了在跳轉(zhuǎn)時(shí)的傳參和動(dòng)畫等信息,我們先來(lái)看下 RouteMeta。

5.1 RouteMate

RouteMeta 包含了跳轉(zhuǎn)路線的基本信息,從名字上來(lái)看就是路線的元信息,元信息也就是關(guān)于信息的信息,RouteMeta 包含了以下字段。

  • 路線類型 type
  • 路線原始類型 rawType
  • 終點(diǎn) destination
  • 路徑 path
  • 路線組 group
  • 優(yōu)先級(jí) int
  • 標(biāo)志 extra
  • 參數(shù)類型 paramsType
  • 路線名 name
  • 注入配置 injectConfig
1. 路線類型

路線類型 RouteType 是一個(gè)枚舉類,有下面幾種類型。

  • Activity

  • Service

  • Provider

    也就是自定義服務(wù) IProvider;

  • ContentProvider

  • Fragment

  • Broadcast

  • Method

  • Unknown

    未知路線;

其中 Service、Broadcast 和 Method 是未實(shí)現(xiàn)的。

2. 路線原始類型

路由原始類型 rawType 的類型為 Element,關(guān)于 Element ,在后面會(huì)進(jìn)一步講解,這里只需要知道 Element 中包含了跳轉(zhuǎn)目標(biāo)的 Class 信息,是由路由處理器 RouteProcessor 設(shè)定的。

3. 終點(diǎn)

終點(diǎn) destination 就是聲明了 @Route 的跳轉(zhuǎn)目標(biāo)的 Class ,比如目標(biāo) Activity 和 Fragment 的 Class,這個(gè)信息也是由 RouteProcessor 設(shè)定的。

4. 路徑和路線組

如果我們?cè)?@Route 中只設(shè)定了路徑,比如 path = /goods/details ,那么 goods 就是 group ,details 就是路徑 path 。

如果我們?cè)O(shè)置了 @Route 的 group 的值,比如 group = taobao ,那么 path 的 group 就是 taobao。

5. 優(yōu)先級(jí)

優(yōu)先級(jí)在 @Route 中無(wú)法設(shè)定,是給攔截器用的,priority 的值越小,攔截器的優(yōu)先級(jí)就越高。

攔截器鏈的實(shí)現(xiàn)類為 InterceptorServiceImpl,它在調(diào)用攔截器的 onInteceptor() 方法時(shí),就是按照 priority 的順序來(lái)獲取攔截器,然后逐個(gè)調(diào)用的。

6. 標(biāo)志

這個(gè) extra 是路由文檔 RouteDoc 的標(biāo)志,文檔中包含了路由和服務(wù)相關(guān)的信息 ,默認(rèn)不會(huì)生成對(duì)應(yīng)的文件,如果想生成的話,可以在注解處理參數(shù)中設(shè)置生成文檔。

  • 添加參數(shù)

    如果我們想查看路由文檔,可以 AROUTER_MODULE_NAME 后面添加:

    AROUTER_GENERATE_DOC: "enable"

  • 文檔路徑

    build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json

路由文檔的大致內(nèi)容如下。

路由文檔.png
7. 參數(shù)類型

paramsType 也就是參數(shù)類型,對(duì)于我們跳轉(zhuǎn)時(shí)設(shè)定的參數(shù),ARouter 會(huì)根據(jù)不同的類型給它們一個(gè)枚舉值,然后取值時(shí),再根據(jù)不同的類型調(diào)用 Intent 的 getXXXExtra() 等方法。

5.2 Postcard

Postcard.png

Postcard 中包含如下字段。

  • 統(tǒng)一資源標(biāo)識(shí)符 uri
  • 標(biāo)簽 tag
  • 傳參 mBundle
  • 標(biāo)志 flags
  • 超時(shí)時(shí)間 timeout
  • 自定義服務(wù) provider
  • 是否走綠色通道 greenChannel
  • 序列化服務(wù) serializationService
  • 轉(zhuǎn)場(chǎng)動(dòng)畫 optionsCompat
  • 進(jìn)入與退出動(dòng)畫 enterAnim/exitAnim

下面我們來(lái)看下這些字段的作用。

1. uri

在 ARouter 中,我們可以用 URI 作為路徑跳轉(zhuǎn),ARouter 會(huì)用路徑替換服務(wù) PathReplaceService 替換路徑,這個(gè)服務(wù)沒(méi)有默認(rèn)實(shí)現(xiàn),如果我們需要為不同的協(xié)議和主機(jī)替換不同的路徑時(shí),就要自己實(shí)現(xiàn)這個(gè)服務(wù)。

uri 跳轉(zhuǎn).png

如果我們沒(méi)有實(shí)現(xiàn)這個(gè)服務(wù),那 scheme 和 主機(jī)地址等信息是沒(méi)有意義的,ARouter 默認(rèn)情況下只會(huì)對(duì)路徑和參數(shù)進(jìn)行解析,在上面的例子中,路徑就是 moduleA/second ,參數(shù)為 name=老王。

2. tag
使用 tag.png

tag 用于在 NavigationCallback 的 interrupt() 方法中獲取異常信息。

在攔截器鏈 InterceptorServiceImpl 中,當(dāng)我們?cè)谧远x的攔截器中調(diào)用 onInterrupt() 方法時(shí),InterceptorServiceImpl 會(huì)創(chuàng)建一個(gè) HandlerException ,并把它作為 Postcard 的 tag ,然后監(jiān)聽(tīng)跳轉(zhuǎn)結(jié)果的地方,就能在 onInterrupt() 方法中通過(guò)獲取 Postcard 的 tag 信息來(lái)獲取異常信息。

我們?cè)跀r截器中傳入的 exception 為空的話,那么 HandlerException() 的 message 默認(rèn)為 "No Message." 。

3. mBundle

我們調(diào)用 withString() 等方法設(shè)置要傳遞給跳轉(zhuǎn)目標(biāo)的數(shù)據(jù)時(shí),這個(gè)數(shù)據(jù)就是放在 mBundle 中的。

4. flags

我們調(diào)用 withFlag() 設(shè)定 Activity 的啟動(dòng)標(biāo)志時(shí),這個(gè)標(biāo)志就會(huì)賦值給 flags 字段。

5. timeout

攔截器鏈處理跳轉(zhuǎn)事件是放在 CountDownLatch 中執(zhí)行的,超時(shí)時(shí)間默認(rèn)為 300 秒,也就是 5 分鐘,所以我們?cè)跀r截器中不要進(jìn)行時(shí)間太長(zhǎng)的耗時(shí)操作。

6. provider

當(dāng)我們實(shí)現(xiàn)了自定義服務(wù)時(shí),參數(shù)注解處理器 AutowiredProcessor 會(huì)為各個(gè)路徑創(chuàng)建一個(gè)實(shí)現(xiàn)注射器 ISyringe 接口的類,在這個(gè)類的 inject() 方法中,調(diào)用了 ARouter.getInstance().navigation(XXXService.class) ,當(dāng) LogisticsCenter 發(fā)現(xiàn)這是一個(gè) Provider 時(shí),就會(huì)通過(guò)反射創(chuàng)建一個(gè) Provider 實(shí)例,然后設(shè)置給 Postcard ,再進(jìn)行跳轉(zhuǎn)。

7. greenChannel

所謂綠色通道,就是不會(huì)被攔截器鏈處理的通道,自定義服務(wù) IProvider 和 Fragment 就是走的綠色通道。

如果我們想讓某個(gè)跳轉(zhuǎn)操作跳過(guò)攔截器,可以在 navigation() 前調(diào)用 greenChannel() 方法。

8. serializationService

當(dāng)我們調(diào)用 withObject() 方法時(shí),ARouter 就會(huì)獲取我們自己自定義的序列化服務(wù) SerializationService,然后調(diào)用該服務(wù)的 object2Json() 方法,再把數(shù)據(jù)轉(zhuǎn)化為 String 放入 bundle 中。

6. ARouter 路由表生成原理

看完了 RouteMeta 和 Postcard,接下來(lái)我們來(lái)看下 RouteProcessor 的路由表生成流程。

6.1 注解處理流程

注解處理流程.png

關(guān)于 Android Gradle 的 和 Javac 這里不會(huì)展開(kāi)講,在這里我們只需要理解在對(duì) @Route 等注解處理前,是由 Android Gradle 的 AndroidJavaCompiler 來(lái)發(fā)起編譯操作,然后讓 Javac 去調(diào)用 RouteProcessor 等注解處理器對(duì)注解進(jìn)行處理的。

6.2 路由解析流程

RouterProcessor.process().png

RouteProcessor 對(duì)于聲明了 @Route 注解的類的處理大致可分為下面 4 個(gè)步驟

  1. 獲取路由元素
  2. 創(chuàng)建路由元信息
  3. 把路由元信息進(jìn)行分組
  4. 生成路由文件
1. 獲取路由元素

這里的元素指的是 javax 包中的 Element ,Element 表示 Java 語(yǔ)言元素,比如字段、包、方法、類以及接口。

process() 方法會(huì)接收到 annotations 和 roundEnv 兩個(gè)參數(shù),annotations 就是當(dāng)前處理器要處理的注解,roundEnv 就是運(yùn)行環(huán)境 JavacRoundEnvironment。

JavacRoundEnvironment 有一個(gè) getElementsAnnotatedWith() 方法,RouteProcessor 在處理注解時(shí)首先會(huì)用這個(gè)方法獲取元素。

2. 創(chuàng)建路由元信息
按類型創(chuàng)建路由元信息.png

這里說(shuō)的路由元信息指的是 RouteMeta,RouteProcessor 會(huì)把聲明了 @Route 注解的的 Activity、Provider、Service 或 Fragment 和一個(gè) RouteMeta 關(guān)聯(lián)起來(lái)。

當(dāng)元素類型為 Activity 時(shí),RouteProcessor 會(huì)遍歷獲取 Activity 的子元素,也就是 Activity 的成員變量,把它們放入注入配置 RouteMeta 的 injectConfig 中,當(dāng)我們配置了要生成路由文檔時(shí),RouteProcessor 就會(huì)把 injectConfig 寫入到文檔中,然后就對(duì)元信息進(jìn)行分組。

3. 把路由元信息進(jìn)行分組

在 RouteProcessor 中有一個(gè) groupMap,在 RouteMeta 創(chuàng)建好后,RouteProcessor 會(huì)把不同的 RouteMeta 進(jìn)行分組,放入到 groupMap 中。

拿路徑 /goods/details 來(lái)說(shuō),如果我們?cè)?@Route 中沒(méi)有設(shè)置 group 的值,那么 RouteProcessor 就會(huì)把 goods 作為 RouteMeta 的 group 。

4. 生成路由表
生成路由文件.png

當(dāng) RouteProcessor 把 RouteMeta 分組好后,就會(huì)用 JavaPoet 生成 Group、Provider 和 Root 路由文件,路由表就是由這些文件組成的,JavaPoet 是 Square 開(kāi)源的代碼生成框架。

生成路由文件后,物流中心 LogisticsCenter 需要用這些文件來(lái)填充倉(cāng)庫(kù) Warehouse 中的 routes 和 providerIndex 等索引,然后在跳轉(zhuǎn)時(shí)根據(jù) routes 和索引來(lái)跳轉(zhuǎn)。

關(guān)于 LogisticsCenter 和 Warehouse 在后面會(huì)講,下面我們來(lái)看下路由文件的內(nèi)容。

RouteProcessor 生成的路由文件位于build/generated/source/kapt/(debug/release)/com/alibaba/android/arouter/routes。

以 ARouter 示例項(xiàng)目中的一個(gè)路由文件為例,RouteProcessor 創(chuàng)建的 RouteMeta 會(huì)轉(zhuǎn)化為下面這樣的文件。

ARouterGroupTest.png

其他的路由文件也是大同小異的,所有內(nèi)容都是由 RouteProcessor 填充的。

7. ARouter 跳轉(zhuǎn)原理

看完了路由表生成流程,下面我們來(lái)看下 ARouter 的跳轉(zhuǎn)原理。

當(dāng)我們調(diào)用 Postcard 的 navigation() 方法時(shí),Postcard 會(huì)調(diào)用 _ARouter 的 navigation() 方法,然后 _ARouter 才會(huì)去加載路由表,下面看下 navigation() 的處理流程。

_ARouter 的 navigation() 方法有下面兩種重載。

navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback)
  
navigation(Class<? extends T> service)

7.1 navigation()

第一種重載是進(jìn)行跳轉(zhuǎn)的,第二種重載是用來(lái)創(chuàng)建服務(wù)的,下面我們來(lái)看第一種重載的實(shí)現(xiàn)。

_ARouter.navigation().png

_ARouter 的 navigation() 方法的大致處理流程如下。

_ARouter 的 navigation() 首先會(huì)根據(jù)我們實(shí)現(xiàn)的預(yù)處理服務(wù),判斷是否繼續(xù)往下處理,不往下處理則中斷跳轉(zhuǎn)流程。

如果預(yù)處理服務(wù)返回的是 true ,那么 navigation() 方法就會(huì)加載路由表,把 RouteMeta 的信息填充到 Postcard 中,比如終點(diǎn) destination 等信息,這個(gè)操作是由物流中心 LogisticsCenter 做的。

假如 LogisticsCenter 在完善明信片的過(guò)程中遇到了異常,比如找不到路徑對(duì)應(yīng)的目標(biāo),那么就會(huì)調(diào)用降級(jí)策略,我們可以在降級(jí)策略中顯示錯(cuò)誤提示等信息。

  1. 攔截器鏈

    在明信片完善信息后,navigation() 就會(huì)把跳轉(zhuǎn)事件交給攔截器鏈處理;

  2. 按類型跳轉(zhuǎn)

    在攔截器鏈處理完成,并且沒(méi)有中斷跳轉(zhuǎn)時(shí),navigation() 就會(huì)按照路徑類型跳轉(zhuǎn)到不同的頁(yè)面或調(diào)用自定義服務(wù);

1. 實(shí)現(xiàn)預(yù)處理服務(wù)

預(yù)處理服務(wù)可以讓我們?cè)?ARouter 進(jìn)行跳轉(zhuǎn)前,根據(jù) PostCard 的內(nèi)容判斷是否要獨(dú)立地對(duì)這次跳轉(zhuǎn)進(jìn)行處理,是的話則在 onPretreatment() 中返回 false 即可。

預(yù)處理服務(wù).png
2. 預(yù)處理服務(wù)處理流程

在 navigation() 中,首先會(huì)調(diào)用預(yù)處理服務(wù)的 onPretreamtn() 方法,判斷是否要繼續(xù)往下處理,如果返回結(jié)果為 false ,則不再往下處理,也就是不會(huì)進(jìn)行跳轉(zhuǎn)等操作。

7.2 完善明信片

調(diào)用完預(yù)處理服務(wù)后,_ARouter 就會(huì)用物流中心 LogisticsCenter 來(lái)加載路由表,路由表也就是 RouteProcessor 生成的路由文件。

1. 獲取路由元信息

在 _ARouter 初始化時(shí),會(huì)把 LogisticsCenter 也進(jìn)行初始化,而 LogisticsCenter 的初始化方法中,會(huì)讀取 RouteProcessor 創(chuàng)建好的路由表,然后放到對(duì)應(yīng)的索引 index 中。

有了索引,當(dāng) _ARouter 調(diào)用 LogisticsCenter 的 completion() 方法時(shí),就可以用索引從 Warehouse 的 routes 中獲取路由元信息。

如果 LogisticsCenter 根據(jù)索引查找不到對(duì)應(yīng)的 RouteMeta,那就說(shuō)明 routes 還沒(méi)有被填充,這時(shí) LogisticsCenter 就會(huì)獲取 group 的 RouteMeta,然后把 group 下的路徑填充到 routes 中,然后再調(diào)用一次 completion() ,這時(shí)就可以取填充明信片的信息了。

索引是在 LogsticsCenter 初始化的時(shí)候加載的,后面在路由表加載原理的時(shí)候會(huì)講到。

2. 填充明信片信息

我們?cè)谡{(diào)用 ARouter.getInstance().build() 方法時(shí),就是創(chuàng)建了一張 Postcard,但是這張 Postcard 的信息是不完整的。

比如 Postcard 中有一個(gè) destination 字段,destination 表示跳轉(zhuǎn)目標(biāo)的 Class 信息,跳轉(zhuǎn) Activity 要用 Intent,destination 就是 Intent 的構(gòu)造函數(shù)的第二個(gè)參數(shù)。

而 LogisticsCenter 就要負(fù)責(zé)把從路由表獲取到的 destination 信息填充到這張明信片中,有了一張信息完整的明信片,才能進(jìn)行后續(xù)的跳轉(zhuǎn)操作。

3. 初始化 Provider

填充完了 Postcard 的信息后,LogisticsCenter 會(huì)根據(jù) Postcard 的類型來(lái)做不同的操作,如果是 Provider 的話,就會(huì)調(diào)用 Provider 的初始化方法,并且把 Postcard 設(shè)為綠色通道。

如果是 Fragment 的話,那就只把 Postcard 設(shè)為綠色通道,如果是其他類型,則不設(shè)為綠色通道,這里說(shuō)的綠色通道,其實(shí)就是說(shuō) Provider 和 Fragment 是跳過(guò)攔截器鏈的。

2.1.3 降級(jí)策略

所謂的降級(jí)策略,其實(shí)就是跳轉(zhuǎn)失敗時(shí),我們能夠跳轉(zhuǎn)到別的頁(yè)面,比如一個(gè)跳轉(zhuǎn)失敗提示頁(yè)。

假如 ARouter 在完善明信片信息的過(guò)程中遇到了異常,

1. 降級(jí)策略處理流程
降級(jí)策略.png

調(diào)用完預(yù)處理服務(wù)后,navigation() 就會(huì)通過(guò)物流中心 LogisticsCenter 來(lái)填充 PostCard 內(nèi)容,如果在填充過(guò)程中遇到了異常,就會(huì)調(diào)用降級(jí)服務(wù),關(guān)于 LogisticsCenter 后面會(huì)講。

2. 自定義降級(jí)策略

在自定義降級(jí)策略時(shí),要注意 context 可能會(huì)空,要使用 Context? 。

7.3 攔截器

攔截器可以用來(lái)在跳轉(zhuǎn)過(guò)程中處理事件,比如做登陸檢查,攔截器會(huì)在跳轉(zhuǎn)之間執(zhí)行,多個(gè)攔截器會(huì)按優(yōu)先級(jí)順序依次執(zhí)行。

1. 實(shí)現(xiàn)攔截器

實(shí)現(xiàn)攔截器時(shí),我們要調(diào)用 onContinue() 或 onInterrupt() 方法,至少需要調(diào)用其中一種方法,否則不會(huì)繼續(xù)路由。

onContinue() 這個(gè)方法表示處理完成,交換控制權(quán)給 ARouter;

如果不想繼續(xù)跳轉(zhuǎn),可以用 onInterrupt() 方法,傳一個(gè)異常,以中斷路由流程;

onInterrupt().png
2. 攔截器處理流程

navigation() 在完善 Postcard 信息后,就會(huì)判斷該 Postcard 是否通過(guò)綠色通道來(lái)處理,綠色通道就是不經(jīng)過(guò)攔截器鏈的通道,關(guān)于攔截器鏈的實(shí)現(xiàn)在后面會(huì)講。

在 LogisticsCenter 的 completion() 中,中會(huì)把路由類型為 Provider 和 Fragment 的路線設(shè)為綠色通道,如果我們想讓目標(biāo)頁(yè)面跳過(guò)攔截器鏈,就可以在 navigation() 方法前調(diào)用 greenChannel() 方法。

7.4 按類型跳轉(zhuǎn)

_navigation().png

當(dāng)處理完攔截器后,navigation() 中就會(huì)調(diào)用 _navigation() 方法,這也是具體進(jìn)行跳轉(zhuǎn)的方法。

在這個(gè)方法中,會(huì)根據(jù) Postcard 的路由類型 RouteType 來(lái)判斷怎么跳轉(zhuǎn),

1. Activity

當(dāng) RouteType 為 Activity 時(shí),啟動(dòng) Activity 的流程和我們平時(shí)啟動(dòng) Activity 的流程是一樣的,創(chuàng)建 Intent、傳入 destination、設(shè)置 fragment 和重寫動(dòng)畫等,最終調(diào)用 startActivity() 啟動(dòng)目標(biāo)頁(yè)面。

2. Fragment / Broadcast / ContentProvider

當(dāng)跳轉(zhuǎn)目標(biāo)為 Fragment、Broadcast 或 ContentProvider 時(shí),會(huì)通過(guò) destination 用反射創(chuàng)建實(shí)例,如果是 Framgent ARouter 還會(huì)為它設(shè)置要傳遞給目標(biāo) Fragment 的參數(shù),然后返回實(shí)例。

3. Provider

如果跳轉(zhuǎn)目標(biāo)為 Provider,也就是自定義服務(wù)的話,就對(duì)應(yīng)了后面講 ARouter 自定義服務(wù)時(shí)講的“通過(guò)依賴查找發(fā)現(xiàn)服務(wù)”。

7.5 跳轉(zhuǎn)回調(diào)

跳轉(zhuǎn)事件觸發(fā)時(shí)機(jī).png

在調(diào)用 navigation() 跳轉(zhuǎn)時(shí),我們可以在 navigation() 中傳入 NavigationCallback 監(jiān)聽(tīng)跳轉(zhuǎn)過(guò)程中發(fā)生的事件。

  • onLost()

    無(wú)法查找到跳轉(zhuǎn)目標(biāo);

  • onFound()

    找到了跳轉(zhuǎn)目標(biāo);

  • onInterrupt()

    攔截器中斷了跳轉(zhuǎn);

  • onArrival()

    已打開(kāi)跳轉(zhuǎn)目標(biāo);

NavigationCallback.png

除了 NavigationCallback ,我們也可以用 NavCallback 監(jiān)聽(tīng)跳轉(zhuǎn)中發(fā)生的事件,NavCallback 是 ARouter 中實(shí)現(xiàn)了 NavigationCallback 的一個(gè)抽象類,使用 NavCallback 不會(huì)強(qiáng)制要求我們重寫所有方法,只要求重寫 onArrival() 方法。

NavCallback.png

8. ARouter 路由表加載原理

所謂的加載路由表,其實(shí)就是加載 RouteProcessor 生成的類文件

在我們調(diào)用 ARouter 的 init() 方法時(shí),ARouter 會(huì)調(diào)用 LogisticsCenter 的 init() 方法,在 LogisticsCenter 的 init() 方法中,會(huì)判斷當(dāng)前路由表加載方式是否為插件,不是的話則從 Dex 中加載路由表,是的話則由插件從 Jar 中加載路由表

下面我們來(lái)看下怎么通過(guò) Dex 加載路由表。

8.1 從 Dex 中加載路由表

通過(guò) Dex 加載路由表.png

通過(guò)類加載路由表的流程大致可分為讀取 Dex 文件從 Dex 文件中讀取路由表把路由表保存到本地以及把路由信息保存到索引這 4 步。

讀取 Dex 文件,指的是當(dāng) LogisticsCenter 發(fā)現(xiàn)沒(méi)有用插件加載路由表時(shí),就會(huì)用 ClassUtils 讀取路由表。這里說(shuō)的路由表,其實(shí)就是 RouteProcessor 生成好的類文件類名,而讀取 Dex 文件的方式,就是從源碼目錄(applicationInfo.sourceDir) 中讀取 base apk 的路徑,然后用這個(gè)路徑構(gòu)建一個(gè) DexFile(path) 。

從 Dex 文件中讀取路由表指的是 ClassUtils 會(huì)用 DexFile 讀取 apk 中的類信息,然后判斷類的包名是否為 com.alibaba.android.arouter.routes,是的話說(shuō)明這是 ARouter 的注解處理器生成的路由文件,把匹配上的類加入列表中,然后把列表返回給 LogisticsCenter 。

把路由表保存到本地指的是當(dāng) LogisticsCenter 從 ClassUtils 中獲取到注解處理器生成的類名時(shí),就會(huì)把這些類名保存 SharedPreferences 中,下次就根據(jù) App 版本判斷,如果不是新版本,就從本地中加載類名,否則就用 ClassUtils 讀取類名。

把路由信息保存到索引指的是當(dāng) LogisticsCenter 把路由表保存到 SharedPreferences 后,就會(huì)根據(jù)類名的后綴判斷類是 IRouteRoot 、IInterceptorGroup 還是 IProviderGroup ,然后根據(jù)不同的類把類文件的內(nèi)容加載到索引中。

8.2 從 Jar 中加載路由表

如果我們想縮短 ARouter 的初始化時(shí)間,可以用 ARouter 的 Gradle 插件,這個(gè)插件能自動(dòng)加載路由表,這樣 ARouter 初始化的時(shí)候就不需要讀取類的信息,從而縮短初始化時(shí)間。

應(yīng)用插件.png

Register 插件從 Jar 文件加載路由表的流程如下。

插件加載路由表流程.png

當(dāng)我們運(yùn)行 App 時(shí),Gradle 就會(huì)調(diào)用我們依賴的 ARouter Register 插件,這個(gè)插件的執(zhí)行的起點(diǎn)就在 PluginLaunch 的 apply() 中,PluginLaunch 判斷了只有在運(yùn)行的項(xiàng)目為 Applicaiton 時(shí),才會(huì)加載路由表信息。

Android Gradle 插件包含了一個(gè) Transform API,這個(gè) API 允許第三方插件在編譯后的類文件轉(zhuǎn)換為 dex 文件前做處理操作,而 ARouter 的 Register 插件就實(shí)現(xiàn)了一個(gè) RegisterTransform

當(dāng) Gradle 的任務(wù)管理器 TaskManager 執(zhí)行 TransformTask 時(shí),就會(huì)執(zhí)行 RegisterTransform 的 transform() 方法。在 transform() 方法中,會(huì)接收到一個(gè) TaskManager 傳過(guò)來(lái)的 TransformInput 集合,通過(guò) TransformInput 可以獲取到 Gradle 為我們項(xiàng)目生成的 Jar 和 Class 文件,包括 RouteProcessor 生成的文件

當(dāng) RegisterTransform 獲取到了 Class 文件后,就會(huì)用 ScanUtils 中的 ScanClassVisitor 訪問(wèn) Class 中的字節(jié)碼數(shù)據(jù),包括 Class 的包名和類名。ScanClassVisitor 是 ClassVisitor 的子類,ClassVisitor 可以改變修改字節(jié)碼,當(dāng) ScanClassVisitor 發(fā)現(xiàn)當(dāng)前 Class 實(shí)現(xiàn)了 IRouteRootIIntercetorGroupIProviderGroup 接口時(shí),這三個(gè)接口都是就會(huì)把當(dāng)前 Class 的類名添加到 ScanSetting 的 classList 中。

當(dāng) RegisterTransform 把 ScanSetting 中的 classList 初始化完后,就會(huì)用代碼生成器 RegisterCodeGenerator 插入代碼到 Jar 文件中。

在 RegisterCodeGenerator 的 insertInitCodeIntoJarFile() 方法中,會(huì)把 Jar 文件轉(zhuǎn)化為 JarFile 對(duì)象,然后獲取到 JarFile 中的 JarEntry ,找到 LogisticsCenter,然后把路由表插入到 LogisticsCenter 的 loadRouteMap() 方法中,比如下面這樣。

插入路由表.png
  • Jar 文件路徑

    build/intermediates/transforms/com.alibaba.arouter/debug/41.jar

9. ARouter 進(jìn)階用法與注意事項(xiàng)

1. 傳參
跳轉(zhuǎn)2.png
2. 解析參數(shù)
解析參數(shù).png

在使用 @Autowired 注解時(shí),要注意下面幾點(diǎn)。

這里需要注意的是,如果 Activity 或 Fragment 是用 Kotlin 寫的,那字段就要加上 @JvmField ,以提供 getter()setter() 給 ARouter 使用。

key 不能為空,因?yàn)闀?huì)被取做默認(rèn)值,否則會(huì)出現(xiàn)空指針異常。因?yàn)?ARouter 在用 Intent 的 getLongExtra() 等方法獲取參數(shù)時(shí),這些方法默認(rèn)值不是包裝類型,如 Long ,而是基本類型,如 long 。

使用 withObject() 傳遞 List 和 Map 時(shí),接收該對(duì)象的地方不能標(biāo)注具體的實(shí)現(xiàn)類類型,應(yīng)聲明為 List 或 Map,否則會(huì)影響序列化中類型的判斷, 其他類似情況需要同樣處理。

如果你發(fā)現(xiàn)接收不到你想要的參數(shù)時(shí),可以在 AutowiredServiceImplautowired() 方法中打個(gè)斷點(diǎn)看下遇到了什么異常。

如果在使用 ARouter 的過(guò)程中,出現(xiàn)了編譯失敗但是不知道原因時(shí),可以用命令查看詳細(xì)的編譯日志 gradle aseembleDebug --stacktrace

3. 實(shí)現(xiàn)序列化服務(wù)

ARouter 允許我們?cè)O(shè)置序列化服務(wù),如果需要傳遞自定義對(duì)象,那只需要新建一個(gè)實(shí)現(xiàn) SerializationService 的類,并加上 @Route 注解 。我們可以給服務(wù)設(shè)定不同的分組,就像下面的 yourservicegroupname 一樣。

序列化服務(wù).png
4. 自定義服務(wù)

除了序列化等服務(wù)以外,ARouter 還允許我們通過(guò)實(shí)現(xiàn) IProvider 接口實(shí)現(xiàn)自定義服務(wù)。

首先聲明接口。

自定義服務(wù).png

然后實(shí)現(xiàn)接口。

ARouter 發(fā)現(xiàn)服務(wù)的方式有兩種,一是通過(guò)依賴注入發(fā)現(xiàn)服務(wù),二是通過(guò)依賴查找發(fā)現(xiàn)服務(wù),推薦通過(guò)依賴注入發(fā)現(xiàn)服務(wù),比如下面這樣。

通過(guò)依賴注入發(fā)現(xiàn)服務(wù).png

helloService 和 helloService2 就是通過(guò)依賴注入的方式發(fā)現(xiàn)服務(wù)的,也就通過(guò)注解標(biāo)注字段即可使用,無(wú)需主動(dòng)獲取。

如果不設(shè)置 name 屬性的話,會(huì)默認(rèn)使用 byType 的方式發(fā)現(xiàn)服務(wù),helloService 就是 byType。

Autowired 注解中標(biāo)注 name 之后,將會(huì)使用 byName 的方式注入對(duì)應(yīng)的字段。

當(dāng)同一接口有多個(gè)實(shí)現(xiàn)的時(shí)候,必須使用byName的方式發(fā)現(xiàn)服務(wù),helloService2 就是 byName。

下面是通過(guò)依賴查找發(fā)現(xiàn)服務(wù)的代碼。主動(dòng)發(fā)現(xiàn)服務(wù)并使用,helloService3 是 byName ,helloService4 是 byType 。

通過(guò)依賴查找發(fā)現(xiàn)服務(wù).png
5. 通過(guò) URL 跳轉(zhuǎn)

如果我們想讓應(yīng)用直接處理外部 URI,只要在清單文件中進(jìn)行配置,再把 url 傳遞給 ARouter 即可。

首先設(shè)定 host,在 Manifest 中聲明 Activity 的 action 和 category ,并且在 data 標(biāo)簽中添加主機(jī) host協(xié)議 scheme

清單.png

然后新建一個(gè) Activit y用于監(jiān)聽(tīng) Scheme 事件,直接把 url 傳遞給 ARouter 即可。

監(jiān)聽(tīng) scheme.png
6. 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫

ARouter 提供了 withTransition() 方法來(lái)設(shè)置跳轉(zhuǎn)的轉(zhuǎn)場(chǎng)動(dòng)畫

轉(zhuǎn)場(chǎng)動(dòng)畫.png
7. 獲取 Fragment

獲取 Fragment 時(shí)要注意,當(dāng)找不到對(duì)應(yīng)路徑的 Fragment 時(shí),會(huì)返回 null,所以這里用空安全的 as? 。

獲取 Fragment.png

10. 其他

如果你想交流 Android 開(kāi)發(fā)相關(guān)的問(wèn)題,歡迎加我的微信 oushaoze2015 一起探討,添加時(shí)請(qǐng)備注“掘金”。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容