module解耦(二)基于路由的解耦

一、 引言

在安卓開發(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);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容