組件化
模塊化、組件化與插件化
在項目發(fā)展到一定程度,隨著人員的增多,代碼越來越臃腫,這時候就必須進行模塊化的拆分。在我看來,模塊化是一種指導(dǎo)理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何實施,目前有兩種途徑,也是兩大流派,一個是組件化,一個是插件化。
既然組件化和插件化都是為了模塊化而生的,那么他們有什么區(qū)別,我覺得最大的區(qū)別應(yīng)該就是動態(tài)修改的能力,這里的動態(tài)修改指的是運行期的動態(tài)修改,插件化顯然是可以支持的,但是組件化卻不行,它只允許編譯期的動態(tài)修改。
所以為什么要做的是組件化而不是插件化,作為RD我覺得理由大概如下吧
插件化有很多坑要躺——插件化框架本身的不穩(wěn)定讓開發(fā)者前赴后繼的躺坑
發(fā)不完的版本——插件化可以運行時修改,PM表示非常完美,RD變身成真業(yè)務(wù)搬磚工
組件化沒有黑科技,穩(wěn)定——原生能力支持這種靈活配置的方式
組件化工作
代碼解耦
一個比較理想的解耦狀態(tài)應(yīng)當是使用AndroidStudio提供的multiple module能力將主項目中的已有模塊進行拆分,這里的module我們分為兩種
一種是基礎(chǔ)庫library,這些代碼可以直接被其他模塊直接引用,比如網(wǎng)絡(luò)庫,我們稱之為library。另一種是一個完整的功能模塊,比如會員中心,我們稱之為Component。拆分后的結(jié)果應(yīng)該是類似于如下樣式

那么解耦到什么樣的效果,才是我們需要的呢,顯然主模塊以及各個Component之間不允許有直接的引用,我們解耦的主要目標就是要做到完全隔離的效果,不能直接使用其他Component內(nèi)的類并且最好不了解其中的實現(xiàn)細節(jié)。
組件的單獨調(diào)試
其實單獨調(diào)試比較簡單,只需要把apply plugin: ‘com.android.library’切換成apply plugin: ‘com.android.application’就可以,但是我們還需要修改一下AndroidManifest文件,因為一個單獨調(diào)試需要有一個入口的Actiivity。具體如下
在gradle.properities配置中放入如下參數(shù)
##### 是否單獨調(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模塊能力時,才包含a模塊
if (!DEBUG_MODULE_A.toBoolean() && NEED_MODULE_A.toBoolean()) {
implementation project(':module-a')
}
在業(yè)務(wù)module的src文件夾下添加對應(yīng)的debug時需要使用的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) {
// ...
}
}
通過上面的步驟,其實我們已經(jīng)給組件搭了一個測試的環(huán)境,從而讓組件的代碼能夠在單獨的環(huán)境里運行,結(jié)構(gòu)如下圖所示

目前這種做法的缺點在于需要手動配置,同時對于manifest文件維護兩份的成本也比較大,后期希望能夠通過插件的方式自動進行配置,對于manifest則采用Android Studio支持的多flavor的manifest自動合并來去做。
組件的通信
上面說到解耦的時候提到了,主項目與各組件之間不允許直接進行引用,那么要實現(xiàn)跨模塊的功能,就必然涉及到了通信的過程,這個過程應(yīng)該如何進行呢。
比較通用的方式是使用路由來進行這部分的工作。具體后面會以ARouter的使用為例來進行說明。
集成調(diào)試
在組件的單獨調(diào)試環(huán)節(jié)我們增加了下面的配置
##### 是否單獨調(diào)試A模塊 #####
DEBUG_MODULE_A=false
##### 主模塊是否需要引入A模塊 #####
NEED_MODULE_A=true
其中NEED_MODULE_A的配置就是用于后期集成調(diào)試準備的,當A模塊的功能完成時,該配置應(yīng)當被值為true,便于主模塊對A模塊進行依賴并將A模塊的功能打包到整體APK中。
實際上,比較合適的做法是,在整個開發(fā)階段(debug),主模塊中都不應(yīng)當包含類似于下面的配置
implementation project(':module-a')
這種依賴方式帶來的缺點是主模塊的開發(fā)人員會有意無意的直接引用到A模塊中的類,這對于解耦工作來說是一個退化過程,因此可能也需要一個整體的開關(guān)用于控制開發(fā)階段的依賴問題,比如
if (!DEBUG_MODULE_A.toBoolean() && NEED_MODULE_A.toBoolean() && !DEBUG.toBoolean()) {
implementation project(':module-a')
}
但是正如單獨調(diào)試中提到的,這種手動修改的方式畢竟非常的不友好,而且對于我們目前的項目而言不易于操作,因此考慮使用自定義插件的方式來進行,目前對Gradle插件還不是很熟悉,所以沒有具體去嘗試,大致的想法應(yīng)該是希望能夠判斷當前build的類型并且根據(jù)配置文件的參數(shù)來決定是否
implementation對應(yīng)的模塊。
組件化規(guī)劃
實際上組件化是一個比較長期而且耗時的過程,特別是將一個大工程進行組件化,要考慮的內(nèi)容可以說是非常多的,具體體現(xiàn)在下面幾點
路由庫選擇
正如前面所說的,目前組件化的工作需要進行組件間的通信,因此必須要有一個負責這部分工作的路由模塊,這個模塊應(yīng)該如何選擇,是自行實現(xiàn)還是選擇第三方等等。
調(diào)試環(huán)境
目前調(diào)試環(huán)境的切分方式還不夠自動化,可能需要開發(fā)額外的插件
組件化拆分
對于組件化工作而言,大部分時間可能都是消耗在拆分工作上,現(xiàn)在讓我去想這個過程我都可以感覺到是很麻煩,但是細細考慮這部分的工作,還是有法可循的
- 從產(chǎn)品需求到開發(fā)階段到運營階段都有清晰邊界的功能開始拆分
- 拆分過程中依賴項目的模塊繼續(xù)進行拆分,比如埋點、網(wǎng)絡(luò)
- 最終將主模塊變成空殼,僅僅包含一些簡單的拼接邏輯
路由
這里的路由就是根據(jù)路由表將請求發(fā)送到制定的位置,可以是一個頁面也可以是一個服務(wù)抑或是其他形式的內(nèi)容。目前Android平臺的路由庫還是比較豐富的,那么為什么要有這么一個路由組件主要有下面幾點原因
開發(fā)與協(xié)作
根據(jù)我們對路由的定義,Android原生的路由方案一般是通過顯式intent和隱式intent兩種方式實現(xiàn)的,而在顯式intent的情況下,因為會存在直接的類依賴的問題,導(dǎo)致耦合非常嚴重;而在隱式intent情況下,則會出現(xiàn)規(guī)則集中式管理,導(dǎo)致協(xié)作變得非常困難。
組件化
組件化是開發(fā)和協(xié)作中作為開發(fā)者所需要面對的問題,而一旦一款A(yù)PP達到一定體量的時候,業(yè)務(wù)就會膨脹得比較嚴重,而開發(fā)團隊的規(guī)模也會越來越大,這時候一般都會提出組件化的概念。組件化就是將APP按照一定的功能和業(yè)務(wù)拆分成多個小組件,不同的組件由不同的開發(fā)小組來負責,這樣就可以解決大型APP開發(fā)過程中的開發(fā)與協(xié)作的問題,將這些問題分散到小的APP中。目前而言組件化已經(jīng)有非常多比較成熟的方案了,而自定義路由框架也可以非常好地解決整個APP完成組件化之后模塊之間沒有耦合的問題,因為沒有耦合時使用原生的路由方案肯定是不可以的。
Native和H5問題
Native與H5的問題主要是由于現(xiàn)在的APP很少是純Native或者純H5的,一般是將兩者進行結(jié)合,那么他們之間需要一個統(tǒng)一負責處理頁面跳轉(zhuǎn)的管理模塊,使用路由模塊實現(xiàn)的中間跳轉(zhuǎn)頁就非常適合處理這種問題。
根據(jù)之前組件化的工作中的描述,路由是必須使用的一項工具,這里我使用ARouter庫來介紹。
ARouter介紹
ARouter是阿里開源的一個Android平臺中對頁面及服務(wù)提供路由功能的中間件,
他有如下特點
- 支持直接解析標準URL進行跳轉(zhuǎn),并自動注入?yún)?shù)到目標頁面中
- 支持多模塊工程使用
- 支持添加多個攔截器,自定義攔截順序
- 支持依賴注入,可單獨作為依賴注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射關(guān)系按組分類、多級管理,按需初始化
- 支持用戶指定全局降級與局部降級策略
- 頁面、攔截器、服務(wù)等組件均自動注冊到框架
- 支持多種方式配置轉(zhuǎn)場動畫
- 支持獲取Fragment
- 完全支持Kotlin以及混編
簡單的說ARouter的原理就是在編譯階段根據(jù)注解解釋器對路由注解,攔截器注解以及自動裝配注解注解進行解釋并生成輔助代碼,待運行期與API接口一起提供給宿主APP使用,其中
路由注解——@Route
路由注解生成的路由表,是核心路由功能,之所以使用注解來實現(xiàn)主要考慮的是大型項目中的界面數(shù)量非常多,如果進行手動注冊映射關(guān)系會非常麻煩,需要寫很多重復(fù)冗余的代碼,并且需要調(diào)用很多接口,因此ARouter使用了注解的方式進行幫我們自動注冊。
攔截器注解——@Interceptor
攔截器注解用于對路由過程進行攔截,主要考慮的是原生路由能力無法在頁面跳轉(zhuǎn)的過程中添加自定義邏輯,而這一能力有時候有非常有必要可以避免許多重復(fù)邏輯的實現(xiàn)。ARouter中的攔截器也是通過注解的方式自動注冊的。
自動裝配——@Autowired
編譯期對Autowired注解的字段進行掃描并注冊到映射文件中,如果需要路由的目標界面調(diào)用了ARouter.inject(this),那么待運行時ARouter會查找到編譯期為調(diào)用方生成的輔助類進行參數(shù)注入。
ARouter使用
以一個實際的例子來描述ARouter的使用,項目希望的簡要結(jié)構(gòu)如下
配置
路由跳轉(zhuǎn)各個模塊都需要使用,因此在ModuleRouter模塊中引入ARouter所需要的庫,上面一個是api接口,下面一個是注解解釋器
dependencies {
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用,均使用最新版可以保證兼容
// 最新版本參考github的鏈接
api "com.alibaba:arouter-api:${AROUTER_API}"
annotationProcessor "com.alibaba:arouter-compiler:${AROUTER_COMPILER}"
...
}
由于ModuleRouter模塊可能會使用到一些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初始化工作推薦盡早進行,因此放在Application的onCreate中
if (isDebug()) { // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 開啟調(diào)試模式(如果在InstantRun模式下運行,必須開啟調(diào)試模式!線上版本需要關(guān)閉,否則有安全風險)
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化
注解
ARouter的路由功能所需要的路由表是在編譯階段根據(jù)注解生成輔助類中包含的,這里路由主要包含了路由界面和路由服務(wù)兩部分。注解解釋器已經(jīng)在配置階段在相應(yīng)模塊中通過配置添加了,但是想使用路由能力,就需要在特定地方加上注解。除此之外還有攔截器注解和自動裝配注解
路由界面
// 在支持路由的頁面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級,/xx/xx
@Route(path = "/modulea/test")
public class ModuleATestActivity extends Activity {
...
}
這里對@Route這個注解做個簡單的解釋,path這個字段里最前面的兩個『/』中間的部分是路由表中『組』的標識,后面的內(nèi)容是具體表示?!航M』這個概念用于ARouter的分組加載的管理,避免一次性加載所有節(jié)點導(dǎo)致路由表瞬間增大??梢允褂胓roup字段進行自定義分組,其余字段部分可以參考源碼中的注釋。
@Route(path = "/com/test" , group = "wangchen")
一旦主動指定分組之后,應(yīng)用內(nèi)路由需要使用 ARouter.getInstance().build(path, group) 進行跳轉(zhuǎn),手動指定分組,否則無法找到
路由服務(wù)
對于需要路由的服務(wù),需要實現(xiàn)IProvider接口
@Route(path = "/modulea/service")
public class ModuleAServiceImpl implements IProvider {
...
}
特殊服務(wù)
這里提兩個特殊服務(wù)
對象解析服務(wù)
ARouter中如果要傳遞自定義對象,則需要使用該服務(wù),實現(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);
}
}
降級服務(wù)
降級服務(wù)表示對路由失敗的情況的處理,是全局生效的
// 實現(xiàn)DegradeService接口,并加上一個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) {
}
}
攔截器
攔截器全局生效,需要實現(xiàn)IInterceptor接口,priority表示攔截器優(yōu)先級,優(yōu)先級高的攔截器優(yōu)先執(zhí)行
@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
...
}
自動裝配
自動裝配需要在對應(yīng)成員變量處加上@Autowired注解
// 為每一個參數(shù)聲明一個字段,并使用 @Autowired 標注
// URL中不能傳遞Parcelable類型數(shù)據(jù),通過ARouter api可以傳遞Parcelable對象
@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; // 支持解析自定義對象,路由表中需要存在SerializationService服務(wù)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
// ARouter會自動對字段進行賦值,無需主動獲取
Log.d("param", name + age + boy);
}
}
注意要盡可能早的調(diào)用
ARouter.getInstance().inject()
如果不需要自動裝配,那么可以不調(diào)用ARouter.getInstance().inject(),但是如果希望可以通過URL跳轉(zhuǎn)的方式進入該界面,則依然需要保留@Autowired注解,否則ARouter不知道應(yīng)該如何從URL中提取參數(shù)類型放入Intent內(nèi)。
發(fā)起路由
路由界面和服務(wù)略有不同
路由界面
針對上面定義的ModuleATestActivity,我們需要路由該界面時只需要下面的代碼即可
ARouter.getInstance().build("/modulea/test").navigation();
對于手動指定的分組,則需要這樣
ARouter.getInstance().build("/modulea/test", "module").navigation();
在實際項目開發(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官方給的最佳實踐中描述了這一點,對于所有界面跳轉(zhuǎn)都建議使用ARouter的方式來進行統(tǒng)一管理,但是對于模塊內(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);
}
}
這樣對于所有可以直接引用ModuleATestActivity這個類的地方(本模塊)就可以以一個非ARouter的方式進行界面跳轉(zhuǎn)。
路由服務(wù)
首先要說的是,相對于上面的路由服務(wù)注冊的代碼,實際上ARouter建議以如下的方式進行路由服務(wù)的聲明
定義服務(wù)接口
public interface ModuleAService extends IProvider {
String callModuleAService(String msg);
}
實現(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) {
}
}
這樣做的好處是分離了接口和實現(xiàn),對于后面的模塊解耦有比較大的幫助。
回過頭來看路由服務(wù)的使用,ARouter中提供了兩種路由服務(wù)的方式——byType和byName,這跟它的路由表實現(xiàn)有關(guān),兩種方式都可以在路由表中找到對應(yīng)的服務(wù)。
byType
ARouter.getInstance().navigation(ModuleAService.class).callModuleAService("msg");
byName
((ModuleAService) ARouter.getInstance().build(RouterPath.MODULE_A_SERVICE).navigation()).callModuleAService("msg");
這兩種方式在ModuleAService接口只有一個實現(xiàn)的時候沒有問題,但是如果出現(xiàn)多實現(xiàn)時會有問題,由于路由表加載是一個map,因此此時實際使用的服務(wù)接口實現(xiàn)是自動生成的路由表加載代碼中順序靠后的一個,這種情況建議使用byName的方式來規(guī)避沖突。
再來看看為什么要進行接口和實現(xiàn)分離,很多時候我們需要跨模塊進行服務(wù)調(diào)用,如果不進行分離直接使用實現(xiàn)類,那么根據(jù)上面兩種方式,在發(fā)起路由的模塊會產(chǎn)生一個對服務(wù)提供模塊的直接依賴,這回對模塊解耦產(chǎn)生影響。
但是換成接口和實現(xiàn)分離的形式來做的話,依然會有一個接口類的依賴,為了避免這種直接依賴問題,我們需要將接口類下沉到基礎(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);
}
實現(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) {
}
}
值得注意的是,希望一個模塊最多對外提供一個服務(wù)接口,以確保路由模塊的接口數(shù)量不會膨脹,同時該服務(wù)接口必須滿足開閉原則。
特殊服務(wù)
因為之前討論的byType和byName問題,ARouter的實現(xiàn)對于特殊服務(wù)都是使用byType的形式來處理的,因此如果出現(xiàn)多服務(wù)實現(xiàn)可能會出現(xiàn)問題。此時建議全局僅使用一個自定義對象加載服務(wù)和全局降級服務(wù),這兩個服務(wù)可以放在路由模塊,便于統(tǒng)一處理。
其他
對于攔截器和自動裝配以及其他路由發(fā)起方式的使用,可以參考官方demo,這里不做更多介紹了。
ARouter分析
ARouter注解
首先我們知道ARouter的自動注冊的實現(xiàn)是利用了編譯期自定義注解的處理來完成的。ARouter定義的注解的部分源碼位于arouter-annotation,具體的實現(xiàn)這里不做源碼分析了,只需要知道編譯期ARouter會通過注解解釋器生成幫助類,比如這樣

Routes
我們看下routes這個包下的內(nèi)容,這個包下的類都是用于生成路由表的
先看Root的內(nèi)容,可以看出這里做的事情是將路由分組放入map中,這里涉及到兩個分組——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);
}
}
以其中一個分組test為例看代碼,這里做的事情是將這個test分組內(nèi)的所有具體路由項添加到一個map中,路由項的具體信息會包裝成RouteMeta類的對象
/**
* 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中是攔截器路由項加載的類,而Provider中是服務(wù)路由項加載的類
/**
* 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這個自動生成類我們還可以發(fā)現(xiàn)一個問題,對于服務(wù)而言它可以通過Provider自動生成類的loadInto方法加載到路由表中,也可以通過具體所屬組所提供的loadInto方法加載到路由表中,這也是ARouter對服務(wù)能提供byType和byName兩種路由方式的原因
Autowired
對于剩下的自動生成的類,都是以Autowired這個關(guān)鍵詞結(jié)尾的,表明他們是負責自動裝配的代碼,以其中一個為例
/**
* 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);
}
}
對于這部分的代碼,大部分比較清晰,總體邏輯是從Activity接收到的intent中提取內(nèi)容并賦值給對應(yīng)的屬性。需要注意的是如下幾點
- @Autowired修飾的屬性不能為private
- @Autowired如果修飾的是自定義對象,那么需要有一個SerializationService服務(wù)實現(xiàn)
- @Autowired可以用來修飾服務(wù),自動裝配的時候會找到對應(yīng)的服務(wù)實現(xiàn)賦值
總結(jié)
綜合上面的內(nèi)容,我們可以得出下面的結(jié)論
- ARouter 的自動注冊機制一定是通過這些路由清單類來實現(xiàn)的
- 我們可以通過兩種方式來找到定義的 PROVIDER 類型的路由節(jié)點
- 自動賦值功能的實現(xiàn),一定是在頁面被路由打開時調(diào)用了生成的幫助類(ISyringe接口的 inject(Object target) 方法)
初始化
LogisticsCenter.init
ARouter的初始化是通過ARouter.init方法來實現(xiàn)的,這個方法最終是通過LogisticsCenter.init來實現(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.
// 掃描對應(yīng)包名下(實際上就是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();
}
// 保存當前記錄的版本信息
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();
// 對所有包下的類,分情況進行加載,加載到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é)論
- loadInto這個方法,其實就是調(diào)用了剛剛我們分析的自動生成的那些類的loadInto方法,
- 初始化之后的路由表是保存在Warehouse這個類的一些成員變量里的。
- 初始化只加載了Root,Provider,Interceptor三個類的路由項
實際上對于第三點,正是之前說的,ARouter是分組懶加載的,所以初始化的時候并未做完全路由加載。
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個成員變量,有三個是在剛剛的init過程中初始化的——groupsIndex、providersIndex、interceptorsIndex,另外三個分別描述如下
routes——這個用于保存完整的路由表,是在路由表更新的方法中不斷更新的,懶加載過程對應(yīng)于LogisticsCenter.completion方法
providers——這個用于緩存服務(wù)實現(xiàn)具體的類的對象,避免重復(fù)創(chuàng)建服務(wù)實現(xiàn)類的對象,懶加載過程對應(yīng)于LogisticsCenter.completion方法
interceptors——這個用于保存攔截器的優(yōu)先級順序,因為我們攔截器是按照優(yōu)先級先后來處理的,因此必然需要一個列表來保存這個優(yōu)先級,懶加載過程對應(yīng)于InterceptorServiceImpl.init方法
LogisticsCenter.completion
剛剛提到了LogisticsCenter.completion這個方法,ARouter的初始化過程嚴格來說應(yīng)該也包含了這個『完善路由』的方法
/**
* 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保存的是路由過程需要的一些全部信息,從上面的代碼里我們也可以看出來下面幾點
- 對于不在Warehouse.routes路由表中的路由項,需要加載并放在Warehouse.routes中
- 對于已經(jīng)在路由表中的項,會將相應(yīng)信息放入PostCard中
- 如果路由項是Provider(服務(wù))且不存在于服務(wù)路由表緩存中時,會實例化服務(wù)并放入緩存
InterceptorServiceImpl.init
InterceptorServiceImpl實際上也是一個服務(wù)的具體實現(xiàn),用于管理所有的攔截器的初始化,它在ARouter初始化(LogisticsCenter.init)之后執(zhí)行,具體看下InterceptorServiceImpl.init的實現(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)存隊列中的。
綜合上面所有我們可以大概知道ARouter的路由表初始化的整個過程。
發(fā)起路由
這里只分析byType和byName兩種發(fā)起路由的方式,這兩種方式最終會執(zhí)行到_ARouter類內(nèi)的兩個方法
// 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)部實現(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òu)造路由信息PostCard對象,可以看到是從路由表中根據(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對象信息,因為byType只用于路由服務(wù),因此最后將Provider實例對象返回
byName
byName內(nèi)部實現(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對象參數(shù),該參數(shù)是根據(jù)路由項的Group和Path構(gòu)造的,從上面的代碼里看到主要做了幾件事
- 調(diào)用completion方法完善路由表和postcard對象信息
- 回調(diào)告知路由狀態(tài)
- 攔截器工作
- 執(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;
}
上面的代碼主要也是分路由類別做了不同的事情
- Activity——創(chuàng)建intent,塞入flag,extras等內(nèi)容并執(zhí)行界面跳轉(zhuǎn)
- Provider——返回Provider實現(xiàn)對象,供byName調(diào)用者調(diào)用服務(wù)接口
- Fragment——返回Fragment實例
以上就是對ARouter實現(xiàn)的一個簡單的分析,如果有興趣的話可以參考源碼閱讀更多的內(nèi)容