組件化中路由框架學(xué)習(xí)筆記

在組件化之前的一種業(yè)務(wù)業(yè)務(wù)劃分架構(gòu)是一種單一分層的結(jié)構(gòu),整個(gè)APP是一個(gè)Module,不同的業(yè)務(wù)拆分在不同的包下:

  • 不管分包做的多好,隨著項(xiàng)目的增大,項(xiàng)目會(huì)失去層次感,導(dǎo)致接受項(xiàng)目時(shí)會(huì)比較吃力。(臃腫)
  • 包名約束是一種弱約束,一不小心就會(huì)導(dǎo)致不同的包名之間相互引用,導(dǎo)致包名之間耦合度高。(耦合度高)
  • 多人聯(lián)合開發(fā)中,在版本管理中很容易出現(xiàn)代碼沖突和代碼覆蓋的問題。(版本管理困難)

組件化是一個(gè)化繁為簡的過程,將多個(gè)功能模塊進(jìn)行拆分、重組的過程。即將APP按照業(yè)務(wù)劃分為不同的模塊,最后在打包為完整的APP時(shí)再整合為一起。


image.png

如上圖所示:可分為APP殼工程、業(yè)務(wù)組件層、功能組件層和基礎(chǔ)庫層。

APP殼工程負(fù)責(zé)管理各個(gè)業(yè)務(wù)組件和打包APK,沒有具體的業(yè)務(wù)功能。

業(yè)務(wù)組件層是根據(jù)不同的業(yè)務(wù)拆分的不同的業(yè)務(wù)組件。

功能組件層是為上層提供基礎(chǔ)的功能服務(wù)。

基礎(chǔ)庫中包含了各種開源庫以及和業(yè)務(wù)無關(guān)的各種自研工具庫。

比如可以新建項(xiàng)目如下:
image.png

切換到 project視圖,與app同目錄,建立business文件夾,然后在其中建立兩個(gè)Android Library,分比為food和waimai。

新建config.gradle文件,在其中定義變量:

ext {
    //標(biāo)識(shí)是以組件化模式運(yùn)行還是集成化模式運(yùn)行,
    // 如果是true,以集成化方式運(yùn)行,如果是false,以組件化方式運(yùn)行
    isModule = false
}

isModule用來標(biāo)識(shí)food是以Android 項(xiàng)目運(yùn)行還是作為一個(gè)普通的Android Module。

在project 的build.gradle文件中引入該文件:

apply from : "config.gradle"

分別修改app、food和waimai的build.gradle文件:

app的build.gradle文件:

///根據(jù)變量的取值來決定運(yùn)行方式
if(rootProject.ext.isModule){
    apply plugin: 'com.android.library'
}else{
    apply plugin: 'com.android.application'
}

....


    defaultConfig {
        if(!rootProject.ext.isModule){
            applicationId "com.example.zujianhuapro"
        }
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    
....

food的build.gradle文件:

if (rootProject.ext.isModule) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
...
    defaultConfig {
        if (rootProject.ext.isModule) {
            applicationId "com.example.food"
        }
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'
    }
...

waimai的同上。

可自行改變isModule 的值查看效果。

為什么要使用組件化
  • 各個(gè)組件專注自身功能的實(shí)現(xiàn),模塊中代碼高度內(nèi)聚,只負(fù)責(zé)一項(xiàng)任務(wù),也就是常說的單一職責(zé)原則。
  • 各業(yè)務(wù)研發(fā)可以互不干擾,提升協(xié)作效率。
  • 業(yè)務(wù)組件可以進(jìn)行插拔,靈活多變。
  • 業(yè)務(wù)組件之間將不再直接應(yīng)用和依賴,各個(gè)業(yè)務(wù)模塊組件更加獨(dú)立,降低耦合。
  • 加快編譯速度,提高開發(fā)效率。

最簡化最核心的就是動(dòng)態(tài)的切換library和application。

組件化最需要解決的問題--頁面跳轉(zhuǎn)

路由跳轉(zhuǎn)一般可采用隱式跳轉(zhuǎn)和顯示跳轉(zhuǎn)兩種方式,但是在組件化結(jié)構(gòu)中,因?yàn)椴煌M件不會(huì)相互依賴,所以無法采用顯示跳轉(zhuǎn)的方式,只可以采用隱式跳轉(zhuǎn),但是隱式跳轉(zhuǎn)也存在問題,使用隱式跳轉(zhuǎn)時(shí),必須先在生命文件中用intent-filter來限定隱式Action的啟動(dòng),其他的Module才可以使用隱式的Action跳轉(zhuǎn)到響應(yīng)的Activity。但是在組件化中使用這種方式并不友好,不僅多人開發(fā)困難,還存在安全隱患。

而路由組件正是為此而存在的。
image.png

首先需要得到目標(biāo)的頁面地址,然后在路由表中尋址,找到目標(biāo)頁后,得到Activity的Class對象,然后啟動(dòng)目標(biāo)頁。

可以看出,路由組件的關(guān)鍵在于路由表,而路由表就是一系列特定的URL和特定的Activity之間的映射集合,是一個(gè)Map結(jié)構(gòu),key是一個(gè)字符串即URL,value是Activity的Class對象。

路由表需要保證只有一份,所以需要使用單例模式,而路由框架需要被所有的組件依賴到。

在項(xiàng)目中新建路由Module,如下所示


image.png

在app、food和waimai中引入改Module,

    implementation project(':router-api')

在路由Module中新建如下類

/**
 * 項(xiàng)目名稱 zujianhuaPro
 * 創(chuàng)建人 xiaojinli
 * 創(chuàng)建時(shí)間 2020/8/29 9:31 AM
 * 路由表,需要被所有的組件依賴,所以需要使用單例模式
 **/
public class Router {
    private static Router mRouter;
    private static Context mContext;
    //路由表
    private static Map<String,Class<? extends Activity>> routers = new HashMap<>();
    
    public void init(Application application){
        mContext = application;
    }

    public static Router getInstance(){
        if(mRouter == null){
            synchronized (Router.class){
                if(mRouter == null){
                    mRouter = new Router();
                }
            }
        }
        return mRouter;
    }
    
    //向路由表中注冊一個(gè)Activity
    public void register(String path,Class<? extends Activity> cls){
        routers.put(path,cls);
    }

    //啟動(dòng)路由表中的一個(gè)Activity
    public void startActivity(String path){
        Class<? extends Activity> cls = routers.get(path);
        if(cls == null){
            return;
        }
        Intent intent = new Intent(mContext,cls);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
    }
}

關(guān)鍵方法有三個(gè),一個(gè)是實(shí)現(xiàn)單例模式方法,因?yàn)樾枰凰械臉I(yè)務(wù)Module引用,所以需要保持唯一性。二是向路由表中注冊一個(gè)頁面的register方法,三是啟動(dòng)一個(gè)路由表中頁面的方法。

使用時(shí)如下所示:

public class MainActivity extends AppCompatActivity {

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

        Router.getInstance().init(getApplication());

        Router.getInstance().register("/food/FoodActivity", FoodActivity.class);
        Router.getInstance().register("/waimai/WaiMaiActivity", WaiMaiActivity.class);

    }

    public void jumpToFood(View view) {
        Router.getInstance().startActivity("/food/FoodActivity");
    }
}

在殼Activity中初始化路由組件以及注冊頁面信息,就可實(shí)現(xiàn)跳轉(zhuǎn)。

在food組件中跳轉(zhuǎn)到waimai組件可使用如下方式

Router.getInstance().startActivity("/waimai/WaiMaiActivity");

在waimai組件中跳轉(zhuǎn)到food組件可使用如下方式

Router.getInstance().startActivity("/food/FoodActivity");

但是這種方式存在一個(gè)明顯的缺點(diǎn),即我們需要自己在路由表中注冊所有的頁面,不易維護(hù)。

下面我們會(huì)通過APT來進(jìn)行優(yōu)化。

什么是APT

APT是注解處理器,是javac處理注解的一種工具,他用來在編譯時(shí)掃描和處理注解,簡單來說就是在編譯期間,通過注解采集信息,生成.java文件,減少重復(fù)代碼的編寫。很多框架都是用了APT,包括Butterknife和Glide等。

通過上面的路由表可以看出,我們需要的關(guān)鍵信息是Activity的一個(gè)字符串參數(shù)以及Activity的Class對象,我們都可以通過注解來采集到。

創(chuàng)建JavaLib,依賴下面的依賴監(jiān)控到編譯期

    //構(gòu)建  -----》》》》----【編譯時(shí)期】------》》》打包-------》》》安裝
    // As-3.4.1  +  gradle-5.1.1-all + auto-service:1.0-rc4
    //在編譯器可以通過以下依賴讓我們自定義的AbstractProcessor工作,即可以通過這兩個(gè)服務(wù)讓我們在編譯器做一些事情
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

首先我們需要?jiǎng)?chuàng)建一個(gè)JavaLib,來創(chuàng)建一個(gè)注解

@Target(ElementType.TYPE) //作用在類上
@Retention(RetentionPolicy.CLASS) //保留到CLASS,因?yàn)槲覀冊诰幾g器需要,所以需要保留到Class
public @interface Route {
    String value(); //詳細(xì)路徑名,比如/main/MainActivity,接收一個(gè)字符串參數(shù)
    String group() default "";//路由組名,比如main
}

其次我們需要再定義一個(gè)JavaLib,來創(chuàng)建一個(gè)處理注解的類,需要繼承AbstractProcessor,默認(rèn)實(shí)現(xiàn)process方法。需要注意添加如下注解:

//自動(dòng)注冊,以便該類可以在編譯器干活
@AutoService(Processor.class)
//允許支持的注解類型,讓注解處理器處理
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE)
//指定JDK編譯版本,必須寫
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//注解處理器接收的參數(shù)
@SupportedOptions({ProcessorConfig.OPTIONS,ProcessorConfig.APT_PACKAGE})

此外我們需要在其他的Module中引入該處理器Module,注意需要使用annotationProcessor關(guān)鍵字

annotationProcessor project(path: ':route_compile')

待一切都準(zhǔn)備完成之后,點(diǎn)擊錘子符號(hào)即Make Project按鈕,如果一切都沒有問題,處理器文件夾下會(huì)出現(xiàn)build文件夾,在其中可以找到如下類,意味著注解處理器配置成功。

image.png

此時(shí)當(dāng)我們項(xiàng)目進(jìn)入編譯器時(shí),會(huì)在APP工程中掃描引入該注解處理器module的組件中的類,查看是否用使用了Route注解。下面就是在我們自定義的注解處理器內(nèi)添加相應(yīng) 的邏輯生成需要的java文件。

完整的項(xiàng)目地址:

https://github.com/lxj-helloworld/zujianhuaPro

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

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