前言
開發(fā)中,我習(xí)慣性會把一個模塊的功能放在一個包下,便于查找,但煩于耦合性太高,后期維護(hù)太費勁,因此對項目進(jìn)行組件化拆分勢在必行。組件化好處:便于開發(fā),團(tuán)隊成員只關(guān)注自己的開發(fā)的小模塊,降低耦合性,后期維護(hù)方便等。相當(dāng)于先有很多小組件,各自開發(fā),最后組裝,成一個 app。
關(guān)系圖


app:殼工程;
module1:組件1;
module2:組件2;
common:第三方庫,公用工具、自定義 View、主題等。
效果預(yù)覽

組件化過程很容易想到一些問題,比如 module1 我想單獨調(diào)試怎么做?module1 有頁面需要跳轉(zhuǎn)到module2怎么辦等。接下來,我一一探索,提供解決方案。
全局設(shè)置 Gradle
如果有很多項目,可以設(shè)置全局來統(tǒng)一管理版本號或依賴庫,這樣就不用一個個去改了,根目錄下 build.gradle 添加:
def androidSupportVersion = '25.3.1'
ext {
//編譯的 SDK 版本,如API20
compileSdkVersion = 25
//構(gòu)建工具的版本,其中包括了打包工具aapt、dx等,如API20對應(yīng)的build-tool的版本就是20.0.0
buildToolsVersion = "26.0.0"
//兼容的最低 SDK 版本
minSdkVersion = 14
//向前兼容,保存新舊兩種邏輯,并通過 if-else 方法來判斷執(zhí)行哪種邏輯
targetSdkVersion = 22
appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
constraintLayout = 'com.android.support.constraint:constraint-layout:1.0.2'
}
其中module/build.gradle:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
//……
}
資源名重名
每個 module 都有 app_name,為了不讓資源名重名,可以在每個組件的 build.gradle 中增加 resourcePrefix "xxx_",固定每個組件的資源前綴。但是 resourcePrefix 這個值只能限定 xml 里面的資源,并不能限定圖片資源,所有圖片資源仍然需要你手動去修改資源名。不過我更建議把圖片、 strings、 colors、dimens 等資源放到 common 去,可以防止不同的資源名字卻對應(yīng)了同一資源值。
組件單獨調(diào)試
application 與 library 切換
module1 在開發(fā)階段應(yīng)該 application,等 release 后才是 library,這里可以設(shè)置一個變量控制下,在根項目 gradle.properties 加入:
# 組件單獨調(diào)試開關(guān),true 可以,false 不可以,需要點擊 "Sync Project"。
isDebug=false
module1/build.gradle:
if (isDebug.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
//……
}
applicationId
開發(fā)階段,module1 還必須有個 applicationId:
android {
//……
defaultConfig {
// 作為library時不能有applicationId,只有作為一個獨立應(yīng)用時才能夠如下設(shè)置
if (isDebug.toBoolean()){
applicationId "com.wuxiaolong.module1"
}
//……
}
}
入口類
到這里還不行,還得有 AndroidManifest 設(shè)置入口類,release 后這個 AndroidManifest 不需要打包進(jìn)去,新建文件 debug,然后在 build.gradle 指定路徑:
android {
//……
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
java {
//release 時 debug 目錄下文件不需要合并到主工程
exclude 'debug/**'
}
}
}
}
}
另外,module 可能會需要使用到自定義的 Application,release 同樣也不需要打包進(jìn)去,不然合并會有沖突。
組件間通信
組件間通信包括兩個場景:(1)UI 跳轉(zhuǎn);(2)調(diào)用組件某個類的某個方法。
這里涉及路由,何為路由,就是頁面請求,都交給它處理。網(wǎng)上有很多路由庫,我這里選的是阿里的 ARouter,ARouter 能解決上面的問題,但是也遺留一個問題,我獨立運行 module1 時,想訪問 module2 頁面就做不到了,Router 不支持跨進(jìn)程訪問,這個問題待定,也可能是我使用 ARouter 姿勢不對,如果您能做到,望告知。
ARouter 使用
1、common
dependencies {
//arouter
compile rootProject.ext.arouterApi
}
2、組件
app 和 module 都需要加入:
android {
defaultConfig {
//arouter
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
dependencies {
//arouter
annotationProcessor rootProject.ext.arouterCompiler
}
3、使用
sample 列出了組件跳轉(zhuǎn)、組件跳轉(zhuǎn)-帶參數(shù)、獲取 Frgment、調(diào)用組件某個類的使用方法,詳見我的 GitHub 分享。
詳細(xì)使用請閱讀 ARouter,不得不吐槽,文檔寫的不是一般的爛。
library 重復(fù)依賴
module1 和 module2 分別都依賴了 common,會不會導(dǎo)致 library 重復(fù)依賴呢,想必大家也有這個疑問了,實際上在 release 構(gòu)建 APP 的過程中 Gradle 會自動將重復(fù)的 aar 包排除,APP 中也就不會存在相同的代碼了,可以打包反編譯驗證下,我試了,確實沒有重復(fù)依賴。
ButterKnife
Attribute value must be constant
在 Android Studio 的 library 的 module 中無法使用 ButterKnife。
網(wǎng)上說用 R2 替代(為什么能用 R2?),但都沒有說 R2 怎么生成的?這篇《butterknife在library中使用問題處理》文章說使用 android-apt,確實可行,但是帶來一個新坑,發(fā)現(xiàn) apply plugin: 'android-apt' 與 arouter 沖突,這時候 arouter 失效了。正確姿勢,用 Android ButterKnife Zelezny 插件生成,手動改成 R2,clean 下就 OK,感謝群里的小伙伴提示。
OnClick 方法
ButterKnife 還有個坑,OnClick 方法中同樣使用 R2,但是找 id 的時候使用 R,然而 library 中是不能使用 switch- case 找 id 的(原因:《在Android library中不能使用switch-case語句訪問資源ID的原因分析及解決方案》),可以使用 if-else:
@OnClick({R2.id.module1_button, R2.id.module1_button2})
public void onViewClicked(View view) {
int id = view.getId();
Log.d("wxl","id="+id);
if (id == R.id.module1_button) {
toastShow("module1_button");
} else if (id == R.id.module1_button2) {
toastShow("module1_button2");
}
}
當(dāng)你寫 switch- case 時,Android Studio 也有提示,可以一鍵轉(zhuǎn)換成 if-else。
源碼
https://github.com/WuXiaolong/ModularSample
最后
1、擼了一次組件化,感覺自己好菜比,好多東西還需要學(xué)習(xí),遺留:(1)、每個 module 的配置最好有個固定模版,這樣新建 module 就不用一一配置了;(2)、關(guān)于注解與依賴注入,不明不白,導(dǎo)致組件間通信花費了太多時間,后續(xù)要系統(tǒng)學(xué)習(xí)下這塊知識。
2、可能還有未知的坑,大家可以 Star ModularSample,我會持續(xù)更新。
3、網(wǎng)上組件化文章不少,但優(yōu)秀的文章屈指可數(shù),很多只是講組件化思想,點到即止,最討厭這種半藏著半掖式分享,感覺他們在耍流氓。對于那些無私愿意分享的人,我一直都是很欽佩的,因為有他們,讓我們這些后人在開發(fā)的路上不孤單無助。
4、熟悉我的朋友,可能知道我在無錫,二線城市,總感覺技術(shù)很落后,所以我一直要保持學(xué)習(xí),不知道組件化是不是在大城市在項目中運用很普遍?據(jù)說所知,無錫組件化用的很少,理論上在一線城市會處在技術(shù)前沿。
5、很多朋友說我文章總是會一個難點講的通俗易懂,其實不知道我在易懂的背后做了多少實踐做支撐,實踐得真理,我是相信這句話的。