一、 引言
在安卓開發(fā)中,隨著項(xiàng)目的復(fù)雜度增加,模塊化(或組件化)開發(fā)方式越來(lái)越受到開發(fā)者的青睞。模塊化開發(fā)可以將一個(gè)大型項(xiàng)目拆分成多個(gè)相對(duì)獨(dú)立的模塊,每個(gè)模塊負(fù)責(zé)一個(gè)功能或業(yè)務(wù)場(chǎng)景,從而提高代碼的可讀性、可維護(hù)性和可復(fù)用性。但是模塊化開發(fā)也帶來(lái)了一些挑戰(zhàn),其中之一就是如何實(shí)現(xiàn)模塊間的解耦和通信。
傳統(tǒng)的方式是通過Intent進(jìn)行頁(yè)面跳轉(zhuǎn)和數(shù)據(jù)傳遞,但這種方式存在以下缺點(diǎn):
- 需要顯式地指定目標(biāo)頁(yè)面的類名或Action,導(dǎo)致強(qiáng)依賴關(guān)系;
- 需要在AndroidManifest.xml中注冊(cè)每個(gè)頁(yè)面,并配置相應(yīng)的過濾器;
- 需要手動(dòng)處理數(shù)據(jù)序列化和反序列化;
- 需要在跨進(jìn)程通信時(shí)使用Bundle或AIDL等機(jī)制;
- 難以適應(yīng)動(dòng)態(tài)加載和插件化等需求。
為了解決這些問題,許多開源框架提出了基于路由(Router)的方案。路由是一種將URL映射到具體頁(yè)面或服務(wù)的機(jī)制,可以用于將Intent頁(yè)面跳轉(zhuǎn)的強(qiáng)依賴關(guān)系解耦,同時(shí)減少跨團(tuán)隊(duì)開發(fā)的互相依賴問題。
二、 基本概念
2.1 路由
路由(Router)是一種將URL映射到具體頁(yè)面或服務(wù)的機(jī)制。URL是一種統(tǒng)一資源定位符(Uniform Resource Locator),用于標(biāo)識(shí)一個(gè)資源或目標(biāo)。URL通常包含以下幾個(gè)部分:
- 協(xié)議(Scheme):表示資源訪問所采用的協(xié)議類型,如http、https等;
- 主機(jī)名(Host):表示資源所在服務(wù)器域名或IP地址;
- 端口號(hào)(Port):表示資源所在服務(wù)器端口號(hào),默認(rèn)為80;
- 路徑(Path):表示資源在服務(wù)器上具體位置;
- 查詢參數(shù)(Query):表示請(qǐng)求資源時(shí)附加給服務(wù)器端程序處理數(shù)據(jù);
- 片段標(biāo)識(shí)符(Fragment):表示請(qǐng)求資源時(shí)定位到某個(gè)子部分。
例如:
http://m.itdecent.cn/u/1d9c468843cf/code?userId=aschnloih8ajkbkjb
這個(gè)URL包含以下部分:
- 協(xié)議:https
- 主機(jī)名:m.itdecent.cn
- 端口號(hào):默認(rèn)為80
- 路徑:/u/1d9c468843cf
- 查詢參數(shù):code?userId=aschnloih8ajkbkjb
片段標(biāo)識(shí)符:無(wú)
在基于路由的module解耦方案中,URL通常用于表示一個(gè)頁(yè)面或服務(wù),而不是一個(gè)網(wǎng)絡(luò)資源。因此,協(xié)議部分可以自定義,例如:
router://moduleA/pageA?name=Tom&age=18
這個(gè)URL表示跳轉(zhuǎn)到moduleA模塊中的pageA頁(yè)面,并傳遞name和age兩個(gè)參數(shù)。
2.2 路由表
路由表(Router Table)是一種存儲(chǔ)URL和頁(yè)面或服務(wù)之間映射關(guān)系的數(shù)據(jù)結(jié)構(gòu)。路由表可以是靜態(tài)的或動(dòng)態(tài)的,也可以是本地的或遠(yuǎn)程的。靜態(tài)路由表是在編譯期生成的,動(dòng)態(tài)路由表是在運(yùn)行期生成的。本地路由表是存儲(chǔ)在客戶端內(nèi)存或文件中的,遠(yuǎn)程路由表是存儲(chǔ)在服務(wù)器端數(shù)據(jù)庫(kù)或配置文件中的。
路由表通常包含以下幾個(gè)字段:
- URL:表示一個(gè)頁(yè)面或服務(wù)的唯一標(biāo)識(shí)符;
- 類名:表示對(duì)應(yīng)頁(yè)面或服務(wù)所屬類名;
- 類型:表示對(duì)應(yīng)頁(yè)面或服務(wù)所屬類型,如Activity、Fragment、Service等;
- 攔截器:表示對(duì)應(yīng)頁(yè)面或服務(wù)是否需要經(jīng)過攔截器處理;
- 優(yōu)先級(jí):表示對(duì)應(yīng)頁(yè)面或服務(wù)在多個(gè)匹配結(jié)果中的優(yōu)先級(jí);
- 其他屬性:表示對(duì)應(yīng)頁(yè)面或服務(wù)所需的其他屬性,如進(jìn)程名、啟動(dòng)模式等。
例如:
| URL | 類名 | 類型 | 攔截器 | 優(yōu)先級(jí) | 其他屬性 |
|---|---|---|---|---|---|
| router://moduleA/pageA | com.example.modulea.PageAActivity | Activity | true | 1 | process=“:moduleA” |
| router://moduleB/pageB/:id | com.example.moduleb.PageBFragment | Fragment | false | 0 |
這個(gè)路由表包含兩條記錄,分別表示moduleA模塊中的pageA頁(yè)面和moduleB模塊中的pageB頁(yè)面。其中pageB頁(yè)面支持路徑參數(shù)id,用于傳遞動(dòng)態(tài)數(shù)據(jù)。
2.3 路由器
路由器(Router)是一種負(fù)責(zé)處理URL請(qǐng)求和跳轉(zhuǎn)到相應(yīng)頁(yè)面或服務(wù)的組件。路由器通常包含以下幾個(gè)功能:
- 初始化:負(fù)責(zé)初始化路由表和攔截器等配置信息;
- 解析:負(fù)責(zé)解析URL請(qǐng)求,并根據(jù)路由表查找匹配結(jié)果;
- 攔截:負(fù)責(zé)根據(jù)攔截器規(guī)則判斷是否需要攔截當(dāng)前請(qǐng)求,并執(zhí)行相應(yīng)操作;
- 跳轉(zhuǎn):負(fù)責(zé)根據(jù)匹配結(jié)果創(chuàng)建相應(yīng)類型的實(shí)例,并執(zhí)行跳轉(zhuǎn)邏輯;
- 回調(diào):負(fù)責(zé)提供回調(diào)接口給調(diào)用者獲取跳轉(zhuǎn)結(jié)果和數(shù)據(jù);
例如:
// 初始化
Router.init(context);
// 解析
Postcard postcard = Router.parse("router://moduleA/pageA?name=Tom&age=18");
// 攔截
boolean intercepted = Router.intercept(postcard);
// 跳轉(zhuǎn)
Router.navigate(postcard);
// 回調(diào)
Router.setCallback(new Callback() {
@Override
public void onSuccess(Postcard postcard) {
// 跳轉(zhuǎn)成功
}
@Override
public void onFail(Postcard postcard) {
// 跳轉(zhuǎn)失敗
}
});
這段代碼演示了使用Router組件進(jìn)行URL請(qǐng)求處理和跳轉(zhuǎn)到pageA頁(yè)面的過程。
2.4 攔截器
攔截器(Interceptor)是一種負(fù)責(zé)對(duì)URL請(qǐng)求進(jìn)行攔截和處理的組件。攔截器通常用于實(shí)現(xiàn)以下功能:
- 權(quán)限檢查:判斷當(dāng)前用戶是否有權(quán)限訪問目標(biāo)頁(yè)面或服務(wù);
- 登錄檢查:判斷當(dāng)前用戶是否已經(jīng)登錄,如果沒有則跳轉(zhuǎn)到登錄頁(yè)面;
- 參數(shù)校驗(yàn):判斷當(dāng)前請(qǐng)求是否攜帶了合法的參數(shù),如果沒有則提示錯(cuò)誤信息;
- 數(shù)據(jù)預(yù)處理:對(duì)當(dāng)前請(qǐng)求攜帶的數(shù)據(jù)進(jìn)行預(yù)處理,如加密、解密、壓縮、解壓等;
- 業(yè)務(wù)邏輯:根據(jù)業(yè)務(wù)需求執(zhí)行一些特定的邏輯,如埋點(diǎn)、統(tǒng)計(jì)、廣告等;
例如:
public class LoginInterceptor implements Interceptor {
@Override
public boolean intercept(Postcard postcard) {
// 判斷當(dāng)前用戶是否已經(jīng)登錄
if (!UserManager.isLogin()) {
// 跳轉(zhuǎn)到登錄頁(yè)面
Router.navigate("router://moduleA/login");
return true;
}
return false;
}
}
這個(gè)攔截器實(shí)現(xiàn)了登錄檢查的功能,如果當(dāng)前用戶沒有登錄,則跳轉(zhuǎn)到登錄頁(yè)面,并攔截原始請(qǐng)求。
三、使用實(shí)例
本節(jié)將以一個(gè)簡(jiǎn)單的示例來(lái)演示如何使用基于路由的module解耦方案。假設(shè)我們有一個(gè)電商項(xiàng)目,包含以下幾個(gè)模塊:
- app模塊:主模塊,負(fù)責(zé)啟動(dòng)應(yīng)用和管理其他模塊;
- home模塊:首頁(yè)模塊,負(fù)責(zé)展示商品列表和輪播圖等內(nèi)容;
- detail模塊:詳情模塊,負(fù)責(zé)展示商品詳情和評(píng)論等內(nèi)容;
- cart模塊:購(gòu)物車模塊,負(fù)責(zé)展示用戶已添加的商品和結(jié)算等內(nèi)容;
- user模塊:用戶模塊,負(fù)責(zé)展示用戶信息和訂單等內(nèi)容;
為了實(shí)現(xiàn)這些模塊之間的解耦和通信,我們可以使用一個(gè)開源框架ARouter來(lái)實(shí)現(xiàn)基于路由的方案。ARouter是一個(gè)輕量級(jí)且功能強(qiáng)大的路由框架,支持靜態(tài)路由表生成、自動(dòng)參數(shù)注入、多種類型跳轉(zhuǎn)、多種攔截器配置等功能。
- ARouter框架是一個(gè)用于幫助Android App進(jìn)行組件化改造的框架,支持模塊間的路由、通信、解耦
- ARouter框架使用靜態(tài)注解處理,為適應(yīng)多模塊,使用moduleName后綴生成了一組統(tǒng)一規(guī)則的注冊(cè)類
- ARouter框架在GitHub上有完整的文檔和示例代碼,可以方便地查看和學(xué)習(xí)(https://github.com/alibaba/ARouter)
- ARouter框架的基本使用包括初始化、注冊(cè)頁(yè)面和服務(wù)、跳轉(zhuǎn)頁(yè)面和調(diào)用服務(wù)、配置攔截器等步驟
3.1 配置依賴
首先,在項(xiàng)目根目錄下的build.gradle文件中添加ARouter插件依賴:
buildscript {
dependencies {
classpath "com.alibaba:arouter-register:1.0.2"
}
}
然后,在每個(gè)子模塊下的build.gradle文件中添加ARouter庫(kù)依賴,并應(yīng)用ARouter插件:
apply plugin: 'com.alibaba.arouter'
dependencies {
implementation "com.alibaba:arouter-api:1.5.2"
annotationProcessor "com.alibaba:arouter-compiler:1.5.2"
}
最后,在app模塊下的build.gradle文件中添加以下配置:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
這樣就完成了ARouter框架在項(xiàng)目中的配置。
3.2 初始化路由器
接下來(lái),在app模塊中創(chuàng)建一個(gè)Application類,并在onCreate方法中初始化路由器:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化路由器
ARouter.init(this);
}
}
同時(shí),在AndroidManifest.xml文件中注冊(cè)該Application類,并添加meta-data標(biāo)簽指定路由表生成路徑:
<application
android:name=".MyApplication">
<meta-data
android:name="ROUTER_MODULE_APP"
android:value="app/src/main/java/com/example/app/router" /> </application>
這樣就完成了路由器在app模塊中的初始化。
3.3 注冊(cè)頁(yè)面和服務(wù)
然后,在每個(gè)子模塊中,使用@Route注解來(lái)注冊(cè)頁(yè)面和服務(wù),并指定對(duì)應(yīng)的URL和其他屬性:
// home模塊中的HomePageActivity
@Route(path = "router://home/homePage")
public class HomePageActivity extends AppCompatActivity {
// ...
}
// detail模塊中的DetailPageActivity
@Route(path = "router://detail/detailPage", extras = 1)
public class DetailPageActivity extends AppCompatActivity {
// ...
}
// cart模塊中的CartService實(shí)現(xiàn)類
@Route(path = "router://cart/cartService")
public class CartServiceImpl implements CartService {
// ...
}
// user模塊中的UserService實(shí)現(xiàn)類
@Route(path = "router://user/userService")
public class UserServiceImpl implements UserService {
// ...
}
這樣就完成了頁(yè)面和服務(wù)在各個(gè)子模塊中的注冊(cè)。
3.4 跳轉(zhuǎn)頁(yè)面和調(diào)用服務(wù)
接下來(lái),在任意一個(gè)子模塊中,可以使用ARouter.getInstance()方法獲取路由器實(shí)例,并通過build方法構(gòu)建Postcard對(duì)象,然后通過navigation方法進(jìn)行跳轉(zhuǎn)或調(diào)用:
// 從home模塊跳轉(zhuǎn)到detail模塊的DetailPageActivity,并傳遞商品id參數(shù)
ARouter.getInstance().build("router://detail/detailPage").withString("id", "123456").navigation();
// 從detail模塊調(diào)用cart模塊的CartService接口,添加商品到購(gòu)物車
CartService cartService = ARouter.getInstance().build("router://cart/cartService").navigation();
cartService.addProductToCart(product);
// 從cart模塊調(diào)用user模塊的UserService接口,獲取用戶信息
UserService userService = ARouter.getInstance().build("router://user/userService").navigation();
User user = userService.getUserInfo();
這樣就完成了頁(yè)面和服務(wù)在各個(gè)子模塊之間的跳轉(zhuǎn)和調(diào)用。
3.5 配置攔截器
最后,在任意一個(gè)子模塊中,可以使用@Interceptor注解來(lái)配置攔截器,并指定優(yōu)先級(jí)和名稱:
// 配置一個(gè)登錄攔截器,優(yōu)先級(jí)為8,名稱為loginInterceptor
@Interceptor(priority = 8, name = "loginInterceptor")
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard) {
// 判斷當(dāng)前請(qǐng)求是否需要登錄檢查(extras為1表示需要)
if (postcard.getExtras() == 1) {
// 判斷當(dāng)前用戶是否已經(jīng)登錄
if (!UserManager.isLogin()) {
// 跳轉(zhuǎn)到登錄頁(yè)面,并傳遞原始請(qǐng)求信息(requestCode為100表示是從攔截器跳轉(zhuǎn)過來(lái))
ARouter.getInstance().build("router://user/login").with(postcard).withInt("requestCode", 100).navigation();
// 攔截當(dāng)前請(qǐng)求,并設(shè)置結(jié)果碼為失?。≧ESULT_FAILED表示失敗)
postcard.setGreenChannel();
postcard.withInt("resultCode", RESULT_FAILED);
}
}
// 繼續(xù)執(zhí)行下一個(gè)攔截器或目標(biāo)請(qǐng)求(onContinue方法表示繼續(xù))
callback.onContinue(postcard);
}
@Override
public void init(Context context) {
// 初始化攔截器相關(guān)資源(可選)
}
}
這樣就完成了攔截器在某個(gè)子模塊中的配置。
四、優(yōu)缺點(diǎn)分析
1.基于路由的module解耦方案有以下優(yōu)點(diǎn):
- 實(shí)現(xiàn)了模塊之間的完全解耦,不需要依賴任何接口或?qū)崿F(xiàn)類;
- 支持多種類型的跳轉(zhuǎn)和調(diào)用,包括Activity、Fragment、Service等;
- 支持多種方式的參數(shù)傳遞和注入,包括基本類型、對(duì)象類型、Bundle等;
- 支持多種攔截器配置和處理,可以實(shí)現(xiàn)權(quán)限檢查、登錄檢查等功能;
- 支持靜態(tài)路由表生成和動(dòng)態(tài)路由注冊(cè),提高了性能和靈活性;
2.基于路由的module解耦方案也有以下缺點(diǎn):
- 需要額外引入一個(gè)路由框架,增加了項(xiàng)目的復(fù)雜度和維護(hù)成本;
- 需要為每個(gè)頁(yè)面和服務(wù)定義一個(gè)唯一的URL,并保證其正確性和一致性;
- 需要注意URL的命名規(guī)范和安全性,避免與其他應(yīng)用或模塊沖突或被惡意調(diào)用;
- 需要注意攔截器的優(yōu)先級(jí)和執(zhí)行順序,避免出現(xiàn)邏輯錯(cuò)誤或死循環(huán);