在組件化之前的一種業(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í)再整合為一起。

如上圖所示:可分為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)目如下:

切換到 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ā)困難,還存在安全隱患。
而路由組件正是為此而存在的。

首先需要得到目標(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,如下所示

在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文件夾,在其中可以找到如下類,意味著注解處理器配置成功。

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