組件化構(gòu)想以及ARouter的使用分析

組件化

模塊化绰垂、組件化與插件化

在項(xiàng)目發(fā)展到一定程度,隨著人員的增多火焰,代碼越來越臃腫劲装,這時(shí)候就必須進(jìn)行模塊化的拆分。在我看來荐健,模塊化是一種指導(dǎo)理念酱畅,其核心思想就是分而治之、降低耦合江场。而在Android工程中如何實(shí)施纺酸,目前有兩種途徑,也是兩大流派址否,一個(gè)是組件化餐蔬,一個(gè)是插件化。

既然組件化和插件化都是為了模塊化而生的佑附,那么他們有什么區(qū)別樊诺,我覺得最大的區(qū)別應(yīng)該就是動(dòng)態(tài)修改的能力,這里的動(dòng)態(tài)修改指的是運(yùn)行期的動(dòng)態(tài)修改音同,插件化顯然是可以支持的词爬,但是組件化卻不行,它只允許編譯期的動(dòng)態(tài)修改权均。

所以為什么要做的是組件化而不是插件化顿膨,作為RD我覺得理由大概如下吧

插件化有很多坑要躺——插件化框架本身的不穩(wěn)定讓開發(fā)者前赴后繼的躺坑

發(fā)不完的版本——插件化可以運(yùn)行時(shí)修改,PM表示非常完美叽赊,RD變身成真業(yè)務(wù)搬磚工

組件化沒有黑科技恋沃,穩(wěn)定——原生能力支持這種靈活配置的方式

組件化工作

代碼解耦

一個(gè)比較理想的解耦狀態(tài)應(yīng)當(dāng)是使用AndroidStudio提供的multiple module能力將主項(xiàng)目中的已有模塊進(jìn)行拆分,這里的module我們分為兩種

一種是基礎(chǔ)庫library必指,這些代碼可以直接被其他模塊直接引用囊咏,比如網(wǎng)絡(luò)庫,我們稱之為library。另一種是一個(gè)完整的功能模塊梅割,比如會(huì)員中心霜第,我們稱之為Component。拆分后的結(jié)果應(yīng)該是類似于如下樣式

模塊化

那么解耦到什么樣的效果户辞,才是我們需要的呢庶诡,顯然主模塊以及各個(gè)Component之間不允許有直接的引用,我們解耦的主要目標(biāo)就是要做到完全隔離的效果咆课,不能直接使用其他Component內(nèi)的類并且最好不了解其中的實(shí)現(xiàn)細(xì)節(jié)。

組件的單獨(dú)調(diào)試

其實(shí)單獨(dú)調(diào)試比較簡單扯俱,只需要把a(bǔ)pply plugin: ‘com.android.library’切換成apply plugin: ‘com.android.application’就可以书蚪,但是我們還需要修改一下AndroidManifest文件,因?yàn)橐粋€(gè)單獨(dú)調(diào)試需要有一個(gè)入口的Actiivity迅栅。具體如下

在gradle.properities配置中放入如下參數(shù)

##### 是否單獨(dú)調(diào)試A模塊 #####
DEBUG_MODULE_A=false
##### 主模塊是否需要引入A模塊 #####
NEED_MODULE_A=true

在業(yè)務(wù)module的build.gradle中添加如下代碼

if (DEBUG_MODULE_A.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    // ...
    
    sourceSets {
        main {
            if (DEBUG_MODULE_A.toBoolean()) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

}

在主module的build.gradle中添加如下代碼

// a模塊非debug且需要打包a模塊能力時(shí)殊校,才包含a模塊
if (!DEBUG_MODULE_A.toBoolean() && NEED_MODULE_A.toBoolean()) {
    implementation project(':module-a')
}

在業(yè)務(wù)module的src文件夾下添加對(duì)應(yīng)的debug時(shí)需要使用的AndroidMainifest文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.baidu.input.module_a" >

    <application>
        <activity android:name="com.baidu.input.module_a.ModuleAMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name="com.baidu.input.module_a.ModuleATestActivity"></activity>
    </application>

</manifest>

在業(yè)務(wù)module中添加入口Activity類

// 虛擬Activity,用于測試業(yè)務(wù)內(nèi)功能
public class ModuleAMainActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.modulea_activity_main);
    }

    public void testActivity(View v) {
        // ...
    }

    public void testService(View v) {
        // ...
    }
}

通過上面的步驟读存,其實(shí)我們已經(jīng)給組件搭了一個(gè)測試的環(huán)境为流,從而讓組件的代碼能夠在單獨(dú)的環(huán)境里運(yùn)行,結(jié)構(gòu)如下圖所示

組件化

目前這種做法的缺點(diǎn)在于需要手動(dòng)配置让簿,同時(shí)對(duì)于manifest文件維護(hù)兩份的成本也比較大敬察,后期希望能夠通過插件的方式自動(dòng)進(jìn)行配置,對(duì)于manifest則采用Android Studio支持的多flavor的manifest自動(dòng)合并來去做尔当。

組件的通信

上面說到解耦的時(shí)候提到了莲祸,主項(xiàng)目與各組件之間不允許直接進(jìn)行引用,那么要實(shí)現(xiàn)跨模塊的功能椭迎,就必然涉及到了通信的過程锐帜,這個(gè)過程應(yīng)該如何進(jìn)行呢。

比較通用的方式是使用路由來進(jìn)行這部分的工作畜号。具體后面會(huì)以ARouter的使用為例來進(jìn)行說明缴阎。

集成調(diào)試

在組件的單獨(dú)調(diào)試環(huán)節(jié)我們?cè)黾恿讼旅娴呐渲?/p>

##### 是否單獨(dú)調(diào)試A模塊 #####
DEBUG_MODULE_A=false
##### 主模塊是否需要引入A模塊 #####
NEED_MODULE_A=true

其中NEED_MODULE_A的配置就是用于后期集成調(diào)試準(zhǔn)備的付枫,當(dāng)A模塊的功能完成時(shí)箱蝠,該配置應(yīng)當(dāng)被值為true,便于主模塊對(duì)A模塊進(jìn)行依賴并將A模塊的功能打包到整體APK中晃财。

實(shí)際上替饿,比較合適的做法是语泽,在整個(gè)開發(fā)階段(debug),主模塊中都不應(yīng)當(dāng)包含類似于下面的配置

implementation project(':module-a')

這種依賴方式帶來的缺點(diǎn)是主模塊的開發(fā)人員會(huì)有意無意的直接引用到A模塊中的類视卢,這對(duì)于解耦工作來說是一個(gè)退化過程踱卵,因此可能也需要一個(gè)整體的開關(guān)用于控制開發(fā)階段的依賴問題,比如

if (!DEBUG_MODULE_A.toBoolean() && NEED_MODULE_A.toBoolean() && !DEBUG.toBoolean()) {
    implementation project(':module-a')
}

但是正如單獨(dú)調(diào)試中提到的,這種手動(dòng)修改的方式畢竟非常的不友好惋砂,而且對(duì)于我們目前的項(xiàng)目而言不易于操作妒挎,因此考慮使用自定義插件的方式來進(jìn)行,目前對(duì)Gradle插件還不是很熟悉西饵,所以沒有具體去嘗試酝掩,大致的想法應(yīng)該是希望能夠判斷當(dāng)前build的類型并且根據(jù)配置文件的參數(shù)來決定是否
implementation對(duì)應(yīng)的模塊。

組件化規(guī)劃

實(shí)際上組件化是一個(gè)比較長期而且耗時(shí)的過程眷柔,特別是將一個(gè)大工程進(jìn)行組件化期虾,要考慮的內(nèi)容可以說是非常多的,具體體現(xiàn)在下面幾點(diǎn)

路由庫選擇

正如前面所說的驯嘱,目前組件化的工作需要進(jìn)行組件間的通信镶苞,因此必須要有一個(gè)負(fù)責(zé)這部分工作的路由模塊,這個(gè)模塊應(yīng)該如何選擇鞠评,是自行實(shí)現(xiàn)還是選擇第三方等等茂蚓。

調(diào)試環(huán)境

目前調(diào)試環(huán)境的切分方式還不夠自動(dòng)化,可能需要開發(fā)額外的插件

組件化拆分

對(duì)于組件化工作而言剃幌,大部分時(shí)間可能都是消耗在拆分工作上聋涨,現(xiàn)在讓我去想這個(gè)過程我都可以感覺到是很麻煩,但是細(xì)細(xì)考慮這部分的工作负乡,還是有法可循的

  • 從產(chǎn)品需求到開發(fā)階段到運(yùn)營階段都有清晰邊界的功能開始拆分
  • 拆分過程中依賴項(xiàng)目的模塊繼續(xù)進(jìn)行拆分牍白,比如埋點(diǎn)、網(wǎng)絡(luò)
  • 最終將主模塊變成空殼抖棘,僅僅包含一些簡單的拼接邏輯

路由

這里的路由就是根據(jù)路由表將請(qǐng)求發(fā)送到制定的位置淹朋,可以是一個(gè)頁面也可以是一個(gè)服務(wù)抑或是其他形式的內(nèi)容。目前Android平臺(tái)的路由庫還是比較豐富的钉答,那么為什么要有這么一個(gè)路由組件主要有下面幾點(diǎn)原因

開發(fā)與協(xié)作

根據(jù)我們對(duì)路由的定義础芍,Android原生的路由方案一般是通過顯式intent和隱式intent兩種方式實(shí)現(xiàn)的,而在顯式intent的情況下数尿,因?yàn)闀?huì)存在直接的類依賴的問題仑性,導(dǎo)致耦合非常嚴(yán)重;而在隱式intent情況下右蹦,則會(huì)出現(xiàn)規(guī)則集中式管理诊杆,導(dǎo)致協(xié)作變得非常困難。

組件化

組件化是開發(fā)和協(xié)作中作為開發(fā)者所需要面對(duì)的問題何陆,而一旦一款A(yù)PP達(dá)到一定體量的時(shí)候晨汹,業(yè)務(wù)就會(huì)膨脹得比較嚴(yán)重,而開發(fā)團(tuán)隊(duì)的規(guī)模也會(huì)越來越大贷盲,這時(shí)候一般都會(huì)提出組件化的概念淘这。組件化就是將APP按照一定的功能和業(yè)務(wù)拆分成多個(gè)小組件剥扣,不同的組件由不同的開發(fā)小組來負(fù)責(zé),這樣就可以解決大型APP開發(fā)過程中的開發(fā)與協(xié)作的問題铝穷,將這些問題分散到小的APP中钠怯。目前而言組件化已經(jīng)有非常多比較成熟的方案了,而自定義路由框架也可以非常好地解決整個(gè)APP完成組件化之后模塊之間沒有耦合的問題曙聂,因?yàn)闆]有耦合時(shí)使用原生的路由方案肯定是不可以的晦炊。

Native和H5問題

Native與H5的問題主要是由于現(xiàn)在的APP很少是純Native或者純H5的,一般是將兩者進(jìn)行結(jié)合宁脊,那么他們之間需要一個(gè)統(tǒng)一負(fù)責(zé)處理頁面跳轉(zhuǎn)的管理模塊断国,使用路由模塊實(shí)現(xiàn)的中間跳轉(zhuǎn)頁就非常適合處理這種問題。
根據(jù)之前組件化的工作中的描述榆苞,路由是必須使用的一項(xiàng)工具并思,這里我使用ARouter庫來介紹。

ARouter介紹

ARouter是阿里開源的一個(gè)Android平臺(tái)中對(duì)頁面及服務(wù)提供路由功能的中間件语稠,

他有如下特點(diǎn)

  1. 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn),并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
  2. 支持多模塊工程使用
  3. 支持添加多個(gè)攔截器弄砍,自定義攔截順序
  4. 支持依賴注入仙畦,可單獨(dú)作為依賴注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射關(guān)系按組分類、多級(jí)管理音婶,按需初始化
  8. 支持用戶指定全局降級(jí)與局部降級(jí)策略
  9. 頁面慨畸、攔截器、服務(wù)等組件均自動(dòng)注冊(cè)到框架
  10. 支持多種方式配置轉(zhuǎn)場動(dòng)畫
  11. 支持獲取Fragment
  12. 完全支持Kotlin以及混編

簡單的說ARouter的原理就是在編譯階段根據(jù)注解解釋器對(duì)路由注解衣式,攔截器注解以及自動(dòng)裝配注解注解進(jìn)行解釋并生成輔助代碼寸士,待運(yùn)行期與API接口一起提供給宿主APP使用,其中

路由注解——@Route

路由注解生成的路由表碴卧,是核心路由功能弱卡,之所以使用注解來實(shí)現(xiàn)主要考慮的是大型項(xiàng)目中的界面數(shù)量非常多,如果進(jìn)行手動(dòng)注冊(cè)映射關(guān)系會(huì)非常麻煩住册,需要寫很多重復(fù)冗余的代碼婶博,并且需要調(diào)用很多接口,因此ARouter使用了注解的方式進(jìn)行幫我們自動(dòng)注冊(cè)荧飞。

攔截器注解——@Interceptor

攔截器注解用于對(duì)路由過程進(jìn)行攔截凡人,主要考慮的是原生路由能力無法在頁面跳轉(zhuǎn)的過程中添加自定義邏輯,而這一能力有時(shí)候有非常有必要可以避免許多重復(fù)邏輯的實(shí)現(xiàn)叹阔。ARouter中的攔截器也是通過注解的方式自動(dòng)注冊(cè)的挠轴。

自動(dòng)裝配——@Autowired

編譯期對(duì)Autowired注解的字段進(jìn)行掃描并注冊(cè)到映射文件中,如果需要路由的目標(biāo)界面調(diào)用了ARouter.inject(this)耳幢,那么待運(yùn)行時(shí)ARouter會(huì)查找到編譯期為調(diào)用方生成的輔助類進(jìn)行參數(shù)注入岸晦。

ARouter使用

以一個(gè)實(shí)際的例子來描述ARouter的使用,項(xiàng)目希望的簡要結(jié)構(gòu)如下
image.png

配置

路由跳轉(zhuǎn)各個(gè)模塊都需要使用,因此在ModuleRouter模塊中引入ARouter所需要的庫委煤,上面一個(gè)是api接口堂油,下面一個(gè)是注解解釋器

dependencies {
    // 替換成最新版本, 需要注意的是api
    // 要與compiler匹配使用,均使用最新版可以保證兼容
    // 最新版本參考github的鏈接
    api "com.alibaba:arouter-api:${AROUTER_API}"
    annotationProcessor "com.alibaba:arouter-compiler:${AROUTER_COMPILER}"
    ...
}

由于ModuleRouter模塊可能會(huì)使用到一些ARouter的注解碧绞,因此還需要添加下面的配置代碼

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ moduleName : project.getName() ]
            }
        }
    }
}

ModuleA府框、ModuleB以及APP模塊都需要依賴ModuleRouter,而且需要使用到注解讥邻,因此配置如下

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ moduleName : project.getName() ]
            }
        }
    }
}

// ...
dependencies {
    annotationProcessor "com.alibaba:arouter-compiler:${AROUTER_COMPILER}"
    implementation project(':modulerouter')
    // ...
}

初始化

ARouter初始化工作推薦盡早進(jìn)行迫靖,因此放在Application的onCreate中

if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 開啟調(diào)試模式(如果在InstantRun模式下運(yùn)行兴使,必須開啟調(diào)試模式系宜!線上版本需要關(guān)閉,否則有安全風(fēng)險(xiǎn))
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化

注解

ARouter的路由功能所需要的路由表是在編譯階段根據(jù)注解生成輔助類中包含的发魄,這里路由主要包含了路由界面和路由服務(wù)兩部分盹牧。注解解釋器已經(jīng)在配置階段在相應(yīng)模塊中通過配置添加了,但是想使用路由能力励幼,就需要在特定地方加上注解汰寓。除此之外還有攔截器注解和自動(dòng)裝配注解

路由界面

// 在支持路由的頁面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí),/xx/xx
@Route(path = "/modulea/test")
public class ModuleATestActivity extends Activity {
    ...
}

這里對(duì)@Route這個(gè)注解做個(gè)簡單的解釋苹粟,path這個(gè)字段里最前面的兩個(gè)『/』中間的部分是路由表中『組』的標(biāo)識(shí)有滑,后面的內(nèi)容是具體表示∏断鳎『組』這個(gè)概念用于ARouter的分組加載的管理毛好,避免一次性加載所有節(jié)點(diǎn)導(dǎo)致路由表瞬間增大】溜酰可以使用group字段進(jìn)行自定義分組肌访,其余字段部分可以參考源碼中的注釋。

@Route(path = "/com/test" , group = "wangchen")

一旦主動(dòng)指定分組之后艇劫,應(yīng)用內(nèi)路由需要使用 ARouter.getInstance().build(path, group) 進(jìn)行跳轉(zhuǎn)场靴,手動(dòng)指定分組,否則無法找到

路由服務(wù)

對(duì)于需要路由的服務(wù)港准,需要實(shí)現(xiàn)IProvider接口

@Route(path = "/modulea/service")
public class ModuleAServiceImpl implements IProvider {
    ...
}

特殊服務(wù)

這里提兩個(gè)特殊服務(wù)

對(duì)象解析服務(wù)

ARouter中如果要傳遞自定義對(duì)象旨剥,則需要使用該服務(wù),實(shí)現(xiàn)SerializationService浅缸,并且使用@Route注解

@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
    @Override
    public void init(Context context) {

    }

    @Override
    public <T> T json2Object(String text, Class<T> clazz) {
        return JSON.parseObject(text, clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return JSON.toJSONString(instance);
    }
}

降級(jí)服務(wù)

降級(jí)服務(wù)表示對(duì)路由失敗的情況的處理轨帜,是全局生效的

// 實(shí)現(xiàn)DegradeService接口,并加上一個(gè)Path內(nèi)容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
  @Override
  public void onLost(Context context, Postcard postcard) {
    // do something.
  }

  @Override
  public void init(Context context) {

  }
}

攔截器

攔截器全局生效衩椒,需要實(shí)現(xiàn)IInterceptor接口蚌父,priority表示攔截器優(yōu)先級(jí)哮兰,優(yōu)先級(jí)高的攔截器優(yōu)先執(zhí)行

@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
    ...
}

自動(dòng)裝配

自動(dòng)裝配需要在對(duì)應(yīng)成員變量處加上@Autowired注解

// 為每一個(gè)參數(shù)聲明一個(gè)字段,并使用 @Autowired 標(biāo)注
// URL中不能傳遞Parcelable類型數(shù)據(jù)苟弛,通過ARouter api可以傳遞Parcelable對(duì)象
@Route(path = "/test/activity")
public class Test1Activity extends Activity {
    @Autowired
    public String name;
    @Autowired
    int age;
    @Autowired(name = "girl") // 通過name來映射URL中的不同參數(shù)
    boolean boy;
    @Autowired
    TestObj obj;    // 支持解析自定義對(duì)象喝滞,路由表中需要存在SerializationService服務(wù)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ARouter.getInstance().inject(this);

    // ARouter會(huì)自動(dòng)對(duì)字段進(jìn)行賦值,無需主動(dòng)獲取
    Log.d("param", name + age + boy);
    }
}

注意要盡可能早的調(diào)用

ARouter.getInstance().inject()

如果不需要自動(dòng)裝配膏秫,那么可以不調(diào)用ARouter.getInstance().inject()右遭,但是如果希望可以通過URL跳轉(zhuǎn)的方式進(jìn)入該界面,則依然需要保留@Autowired注解缤削,否則ARouter不知道應(yīng)該如何從URL中提取參數(shù)類型放入Intent內(nèi)窘哈。

發(fā)起路由

路由界面和服務(wù)略有不同

路由界面

針對(duì)上面定義的ModuleATestActivity,我們需要路由該界面時(shí)只需要下面的代碼即可

ARouter.getInstance().build("/modulea/test").navigation();

對(duì)于手動(dòng)指定的分組亭敢,則需要這樣

ARouter.getInstance().build("/modulea/test", "module").navigation();

在實(shí)際項(xiàng)目開發(fā)中滚婉,為了協(xié)調(diào)路路徑的問題,考慮將這部分的靜態(tài)代碼下沉至路由模塊帅刀,因此在路由模塊中添加如下代碼

package com.baidu.input.imerouter;

/**
 * Created by wangchen on 02/03/18.
 */
public class RouterPath implements IModuleAPath, IModuleBPath {
}

interface IModuleAPath {

    String MODULE_A_TEST = "/modulea/test";

    String MODULE_A_SERVICE = "/modulea/service";

}

interface IModuleBPath {

    String MODULE_B_TEST = "/moduleb/test";
}

因此注解代碼可以修改成

@Route(path = RouterPath.MODULE_A_TEST)
public class ModuleATestActivity extends Activity {
}

@Route(path = RouterPath.MODULE_A_SERVICE)
public class ModuleAServiceImpl implements ModuleAService {
}

發(fā)起路由代碼可以修改成

ARouter.getInstance().build(RouterPath.MODULE_A_TEST).navigation();

在ARouter官方給的最佳實(shí)踐中描述了這一點(diǎn)扣溺,對(duì)于所有界面跳轉(zhuǎn)都建議使用ARouter的方式來進(jìn)行統(tǒng)一管理哈恰,但是對(duì)于模塊內(nèi)的跳轉(zhuǎn)似乎這么寫又有些難受,因此給出如下的代碼建議

@Route(path = RouterPath.MODULE_A_TEST)
public class ModuleATestActivity extends Activity {

    public static void launch(Activity c) {
        ARouter.getInstance().build(RouterPath.MODULE_A_TEST).navigation(c);
    }
}

這樣對(duì)于所有可以直接引用ModuleATestActivity這個(gè)類的地方(本模塊)就可以以一個(gè)非ARouter的方式進(jìn)行界面跳轉(zhuǎn)荠医。

路由服務(wù)

首先要說的是攻冷,相對(duì)于上面的路由服務(wù)注冊(cè)的代碼,實(shí)際上ARouter建議以如下的方式進(jìn)行路由服務(wù)的聲明

定義服務(wù)接口

public interface ModuleAService extends IProvider {

    String callModuleAService(String msg);
}

實(shí)現(xiàn)服務(wù)接口

@Route(path = RouterPath.MODULE_A_SERVICE)
public class ModuleAServiceImpl implements ModuleAService {

    @Override
    public String callModuleAService(String msg) {
        Log.i("ModuleA", msg);
        return "ModuleA receive " + msg;
    }

    @Override
    public void init(Context context) {

    }
}

這樣做的好處是分離了接口和實(shí)現(xiàn)胁黑,對(duì)于后面的模塊解耦有比較大的幫助。

回過頭來看路由服務(wù)的使用颈畸,ARouter中提供了兩種路由服務(wù)的方式——byType和byName嘁信,這跟它的路由表實(shí)現(xiàn)有關(guān),兩種方式都可以在路由表中找到對(duì)應(yīng)的服務(wù)。

byType

ARouter.getInstance().navigation(ModuleAService.class).callModuleAService("msg");

byName

((ModuleAService) ARouter.getInstance().build(RouterPath.MODULE_A_SERVICE).navigation()).callModuleAService("msg");

這兩種方式在ModuleAService接口只有一個(gè)實(shí)現(xiàn)的時(shí)候沒有問題捷雕,但是如果出現(xiàn)多實(shí)現(xiàn)時(shí)會(huì)有問題,由于路由表加載是一個(gè)map壹甥,因此此時(shí)實(shí)際使用的服務(wù)接口實(shí)現(xiàn)是自動(dòng)生成的路由表加載代碼中順序靠后的一個(gè)救巷,這種情況建議使用byName的方式來規(guī)避沖突。

再來看看為什么要進(jìn)行接口和實(shí)現(xiàn)分離句柠,很多時(shí)候我們需要跨模塊進(jìn)行服務(wù)調(diào)用浦译,如果不進(jìn)行分離直接使用實(shí)現(xiàn)類,那么根據(jù)上面兩種方式溯职,在發(fā)起路由的模塊會(huì)產(chǎn)生一個(gè)對(duì)服務(wù)提供模塊的直接依賴精盅,這回對(duì)模塊解耦產(chǎn)生影響。

但是換成接口和實(shí)現(xiàn)分離的形式來做的話谜酒,依然會(huì)有一個(gè)接口類的依賴叹俏,為了避免這種直接依賴問題,我們需要將接口類下沉到基礎(chǔ)模塊中僻族,這里就是ModuleRouter粘驰,注意下面的package

接口位于路由模塊

package com.baidu.input.imerouter;

import com.alibaba.android.arouter.facade.template.IProvider;

/**
 * Created by wangchen on 02/03/18.
 */
public interface ModuleAService extends IProvider {

    String callModuleAService(String msg);
}

實(shí)現(xiàn)位于業(yè)務(wù)模塊

package com.baidu.input.module_a;

import android.content.Context;
import android.util.Log;

import com.alibaba.android.arouter.facade.annotation.Route;
import com.baidu.input.imerouter.ModuleAService;
import com.baidu.input.imerouter.RouterPath;

/**
 * Created by wangchen on 02/03/18.
 */
@Route(path = RouterPath.MODULE_A_SERVICE)
public class ModuleAServiceImpl implements ModuleAService {

    @Override
    public String callModuleAService(String msg) {
        Log.i("ModuleA", msg);
        return "ModuleA receive " + msg;
    }

    @Override
    public void init(Context context) {

    }
}

值得注意的是,希望一個(gè)模塊最多對(duì)外提供一個(gè)服務(wù)接口述么,以確保路由模塊的接口數(shù)量不會(huì)膨脹蝌数,同時(shí)該服務(wù)接口必須滿足開閉原則

特殊服務(wù)

因?yàn)橹坝懻摰腷yType和byName問題度秘,ARouter的實(shí)現(xiàn)對(duì)于特殊服務(wù)都是使用byType的形式來處理的顶伞,因此如果出現(xiàn)多服務(wù)實(shí)現(xiàn)可能會(huì)出現(xiàn)問題。此時(shí)建議全局僅使用一個(gè)自定義對(duì)象加載服務(wù)全局降級(jí)服務(wù)剑梳,這兩個(gè)服務(wù)可以放在路由模塊唆貌,便于統(tǒng)一處理。

其他

對(duì)于攔截器和自動(dòng)裝配以及其他路由發(fā)起方式的使用阻荒,可以參考官方demo挠锥,這里不做更多介紹了众羡。

ARouter分析

ARouter注解

首先我們知道ARouter的自動(dòng)注冊(cè)的實(shí)現(xiàn)是利用了編譯期自定義注解的處理來完成的侨赡。ARouter定義的注解的部分源碼位于arouter-annotation,具體的實(shí)現(xiàn)這里不做源碼分析了粱侣,只需要知道編譯期ARouter會(huì)通過注解解釋器生成幫助類羊壹,比如這樣

APT

Routes

我們看下routes這個(gè)包下的內(nèi)容,這個(gè)包下的類都是用于生成路由表的

先看Root的內(nèi)容齐婴,可以看出這里做的事情是將路由分組放入map中油猫,這里涉及到兩個(gè)分組——service和test

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("service", ARouter$$Group$$service.class);
    routes.put("test", ARouter$$Group$$test.class);
  }
}

以其中一個(gè)分組test為例看代碼,這里做的事情是將這個(gè)test分組內(nèi)的所有具體路由項(xiàng)添加到一個(gè)map中柠偶,路由項(xiàng)的具體信息會(huì)包裝成RouteMeta類的對(duì)象

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("pac", 9); put("ch", 5); put("fl", 6); put("obj", 10); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 10); put("map", 10); put("age", 3); put("url", 8); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

除此之外Interceptor中是攔截器路由項(xiàng)加載的類情妖,而Provider中是服務(wù)路由項(xiàng)加載的類

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(7, Test1Interceptor.class);
  }
}
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, 10, -2147483648));
    providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, TestService.class, "/service/test", "service", null, 50, -2147483648));
  }
}

從Provider這個(gè)自動(dòng)生成類我們還可以發(fā)現(xiàn)一個(gè)問題睬关,對(duì)于服務(wù)而言它可以通過Provider自動(dòng)生成類的loadInto方法加載到路由表中,也可以通過具體所屬組所提供的loadInto方法加載到路由表中毡证,這也是ARouter對(duì)服務(wù)能提供byTypebyName兩種路由方式的原因

Autowired

對(duì)于剩下的自動(dòng)生成的類电爹,都是以Autowired這個(gè)關(guān)鍵詞結(jié)尾的,表明他們是負(fù)責(zé)自動(dòng)裝配的代碼料睛,以其中一個(gè)為例

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class Test1Activity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Test1Activity substitute = (Test1Activity)target;
    substitute.name = substitute.getIntent().getStringExtra("name");
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
    substitute.height = substitute.getIntent().getIntExtra("height", substitute.height);
    substitute.girl = substitute.getIntent().getBooleanExtra("boy", substitute.girl);
    substitute.ch = substitute.getIntent().getCharExtra("ch", substitute.ch);
    substitute.fl = substitute.getIntent().getFloatExtra("fl", substitute.fl);
    substitute.dou = substitute.getIntent().getDoubleExtra("dou", substitute.dou);
    substitute.pac = substitute.getIntent().getParcelableExtra("pac");
    if (null != serializationService) {
      substitute.obj = serializationService.parseObject(substitute.getIntent().getStringExtra("obj"), new com.alibaba.android.arouter.facade.model.TypeWrapper<TestObj>(){}.getType());
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
    }
    if (null != serializationService) {
      substitute.objList = serializationService.parseObject(substitute.getIntent().getStringExtra("objList"), new com.alibaba.android.arouter.facade.model.TypeWrapper<List<TestObj>>(){}.getType());
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'objList' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
    }
    if (null != serializationService) {
      substitute.map = serializationService.parseObject(substitute.getIntent().getStringExtra("map"), new com.alibaba.android.arouter.facade.model.TypeWrapper<Map<String, List<TestObj>>>(){}.getType());
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'map' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
    }
    substitute.url = substitute.getIntent().getStringExtra("url");
    substitute.helloService = ARouter.getInstance().navigation(HelloService.class);
  }
}

對(duì)于這部分的代碼丐箩,大部分比較清晰,總體邏輯是從Activity接收到的intent中提取內(nèi)容并賦值給對(duì)應(yīng)的屬性恤煞。需要注意的是如下幾點(diǎn)

  1. @Autowired修飾的屬性不能為private
  2. @Autowired如果修飾的是自定義對(duì)象屎勘,那么需要有一個(gè)SerializationService服務(wù)實(shí)現(xiàn)
  3. @Autowired可以用來修飾服務(wù),自動(dòng)裝配的時(shí)候會(huì)找到對(duì)應(yīng)的服務(wù)實(shí)現(xiàn)賦值

總結(jié)

綜合上面的內(nèi)容居扒,我們可以得出下面的結(jié)論

  1. ARouter 的自動(dòng)注冊(cè)機(jī)制一定是通過這些路由清單類來實(shí)現(xiàn)的
  2. 我們可以通過兩種方式來找到定義的 PROVIDER 類型的路由節(jié)點(diǎn)
  3. 自動(dòng)賦值功能的實(shí)現(xiàn)概漱,一定是在頁面被路由打開時(shí)調(diào)用了生成的幫助類(ISyringe接口的 inject(Object target) 方法)

初始化

LogisticsCenter.init

ARouter的初始化是通過ARouter.init方法來實(shí)現(xiàn)的,這個(gè)方法最終是通過LogisticsCenter.init來實(shí)現(xiàn)具體的邏輯的

    /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            Set<String> routerMap;

            // It will rebuild router map every times when debuggable.
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                // These class was generate by arouter-compiler.
                // 掃描對(duì)應(yīng)包名下(實(shí)際上就是routes包)的所有類
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }

                // 保存當(dāng)前記錄的版本信息
                PackageUtils.updateVersion(context);    // Save new version name when router map update finish.
            } else {
                logger.info(TAG, "Load router map from cache.");
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }

            logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
            startInit = System.currentTimeMillis();

            // 對(duì)所有包下的類苔货,分情況進(jìn)行加載犀概,加載到Warehouse的不同的屬性中
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // This one of root elements, load root.
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

根據(jù)這部分的邏輯,我們可以得出下面的結(jié)論

  1. loadInto這個(gè)方法夜惭,其實(shí)就是調(diào)用了剛剛我們分析的自動(dòng)生成的那些類的loadInto方法姻灶,
  2. 初始化之后的路由表是保存在Warehouse這個(gè)類的一些成員變量里的。
  3. 初始化只加載了Root诈茧,Provider产喉,Interceptor三個(gè)類的路由項(xiàng)

實(shí)際上對(duì)于第三點(diǎn),正是之前說的敢会,ARouter是分組懶加載的曾沈,所以初始化的時(shí)候并未做完全路由加載。

Warehouse

然后可以看看Warehouse里的內(nèi)容

/**
 * Storage of route meta and other data.
 *
 * @author zhilong <a href="mailto:zhilong.lzl@alibaba-inc.com">Contact me.</a>
 * @version 1.0
 * @since 2017/2/23 下午1:39
 */
class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}

這里有6個(gè)成員變量鸥昏,有三個(gè)是在剛剛的init過程中初始化的——groupsIndex塞俱、providersIndex、interceptorsIndex吏垮,另外三個(gè)分別描述如下

  1. routes——這個(gè)用于保存完整的路由表障涯,是在路由表更新的方法中不斷更新的,懶加載過程對(duì)應(yīng)于LogisticsCenter.completion方法

  2. providers——這個(gè)用于緩存服務(wù)實(shí)現(xiàn)具體的類的對(duì)象膳汪,避免重復(fù)創(chuàng)建服務(wù)實(shí)現(xiàn)類的對(duì)象唯蝶,懶加載過程對(duì)應(yīng)于LogisticsCenter.completion方法

  3. interceptors——這個(gè)用于保存攔截器的優(yōu)先級(jí)順序,因?yàn)槲覀償r截器是按照優(yōu)先級(jí)先后來處理的遗嗽,因此必然需要一個(gè)列表來保存這個(gè)優(yōu)先級(jí)粘我,懶加載過程對(duì)應(yīng)于InterceptorServiceImpl.init方法

LogisticsCenter.completion

剛剛提到了LogisticsCenter.completion這個(gè)方法,ARouter的初始化過程嚴(yán)格來說應(yīng)該也包含了這個(gè)『完善路由』的方法

    /**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should completion by this method.
     */
    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }

                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                completion(postcard);   // Reload
            }
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need auto inject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }

            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must be implememt IProvider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

這里的PostCard保存的是路由過程需要的一些全部信息痹换,從上面的代碼里我們也可以看出來下面幾點(diǎn)

  1. 對(duì)于不在Warehouse.routes路由表中的路由項(xiàng)征字,需要加載并放在Warehouse.routes中
  2. 對(duì)于已經(jīng)在路由表中的項(xiàng)都弹,會(huì)將相應(yīng)信息放入PostCard中
  3. 如果路由項(xiàng)是Provider(服務(wù))且不存在于服務(wù)路由表緩存中時(shí),會(huì)實(shí)例化服務(wù)并放入緩存

InterceptorServiceImpl.init

InterceptorServiceImpl實(shí)際上也是一個(gè)服務(wù)的具體實(shí)現(xiàn)匙姜,用于管理所有的攔截器的初始化缔杉,它在ARouter初始化(LogisticsCenter.init)之后執(zhí)行,具體看下InterceptorServiceImpl.init的實(shí)現(xiàn)代碼

    public void init(final Context context) {
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class<? extends IInterceptor> interceptorClass = entry.getValue();
                        try {
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            Warehouse.interceptors.add(iInterceptor);
                        } catch (Exception ex) {
                            throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                        }
                    }

                    interceptorHasInit = true;

                    logger.info(TAG, "ARouter interceptors init over.");

                    synchronized (interceptorInitLock) {
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
    }

可以看到攔截器是異步加載的搁料,而且是從interceptorsIndex中提取所有的一次性完全加載到Warehouse管理的內(nèi)存隊(duì)列中的或详。

綜合上面所有我們可以大概知道ARouter的路由表初始化的整個(gè)過程。

發(fā)起路由

這里只分析byType和byName兩種發(fā)起路由的方式郭计,這兩種方式最終會(huì)執(zhí)行到_ARouter類內(nèi)的兩個(gè)方法

// byType
protected <T> T navigation(Class<? extends T> service) {
    // .............
}
// byName
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
    // .............
}

byType

byType內(nèi)部實(shí)現(xiàn)如下

protected <T> T navigation(Class<? extends T> service) {
        try {
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // Compatible 1.0.5 compiler sdk.
            if (null == postcard) { // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }

            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }

第一個(gè)方法用于構(gòu)造路由信息PostCard對(duì)象霸琴,可以看到是從路由表中根據(jù)服務(wù)接口名找到路由基本信息

    /**
     * Build postcard by serviceName
     *
     * @param serviceName interfaceName
     * @return postcard
     */
    public static Postcard buildProvider(String serviceName) {
        RouteMeta meta = Warehouse.providersIndex.get(serviceName);

        if (null == meta) {
            return null;
        } else {
            return new Postcard(meta.getPath(), meta.getGroup());
        }
    }

然后使用上面分析過的completion方法完善PostCard對(duì)象信息,因?yàn)閎yType只用于路由服務(wù)昭伸,因此最后將Provider實(shí)例對(duì)象返回

byName

byName內(nèi)部實(shí)現(xiàn)如下

/**
     * Use router navigation.
     *
     * @param context     Activity or null.
     * @param postcard    Route metas
     * @param requestCode RequestCode
     * @param callback    cb
     */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) { // Show friendly tips for user.
                Toast.makeText(mContext, "There's no route matched!\n" +
                        " Path = [" + postcard.getPath() + "]\n" +
                        " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
            }

            if (null != callback) {
                callback.onLost(postcard);
            } else {    // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

byName的方式梧乘,方法本身包含PostCard對(duì)象參數(shù),該參數(shù)是根據(jù)路由項(xiàng)的Group和Path構(gòu)造的庐杨,從上面的代碼里看到主要做了幾件事

  1. 調(diào)用completion方法完善路由表和postcard對(duì)象信息
  2. 回調(diào)告知路由狀態(tài)
  3. 攔截器工作
  4. 執(zhí)行路由跳轉(zhuǎn)

我們看下最后執(zhí)行路由跳轉(zhuǎn)的方法_navigation(context, postcard, requestCode, callback);

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

上面的代碼主要也是分路由類別做了不同的事情

  1. Activity——?jiǎng)?chuàng)建intent选调,塞入flag,extras等內(nèi)容并執(zhí)行界面跳轉(zhuǎn)
  2. Provider——返回Provider實(shí)現(xiàn)對(duì)象灵份,供byName調(diào)用者調(diào)用服務(wù)接口
  3. Fragment——返回Fragment實(shí)例

以上就是對(duì)ARouter實(shí)現(xiàn)的一個(gè)簡單的分析仁堪,如果有興趣的話可以參考源碼閱讀更多的內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市填渠,隨后出現(xiàn)的幾起案子弦聂,更是在濱河造成了極大的恐慌,老刑警劉巖氛什,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莺葫,死亡現(xiàn)場離奇詭異,居然都是意外死亡枪眉,警方通過查閱死者的電腦和手機(jī)捺檬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贸铜,“玉大人堡纬,你說我怎么就攤上這事∪裕” “怎么了隐轩?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵饺饭,是天一觀的道長渤早。 經(jīng)常有香客問我,道長瘫俊,這世上最難降的妖魔是什么鹊杖? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任悴灵,我火速辦了婚禮,結(jié)果婚禮上骂蓖,老公的妹妹穿的比我還像新娘积瞒。我一直安慰自己,他們只是感情好登下,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布茫孔。 她就那樣靜靜地躺著,像睡著了一般被芳。 火紅的嫁衣襯著肌膚如雪缰贝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天畔濒,我揣著相機(jī)與錄音剩晴,去河邊找鬼。 笑死侵状,一個(gè)胖子當(dāng)著我的面吹牛赞弥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趣兄,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼绽左,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了艇潭?” 一聲冷哼從身側(cè)響起妇菱,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暴区,沒想到半個(gè)月后闯团,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仙粱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年房交,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伐割。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡候味,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隔心,到底是詐尸還是另有隱情白群,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布硬霍,位于F島的核電站帜慢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粱玲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一躬柬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抽减,春花似錦允青、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至史汗,卻和暖如春木柬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背淹办。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工眉枕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怜森。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓速挑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親副硅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姥宝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345