這里的組件化每個(gè)模塊可以單獨(dú)運(yùn)行描滔、打包、測試踪古,可隨意拆卸含长、隨意組裝,既不互相依賴又可以互相調(diào)用伏穆。是通過在一個(gè)Project下通過創(chuàng)建多個(gè)Module實(shí)現(xiàn)的拘泞。
假設(shè)三個(gè)模塊:App(可以認(rèn)為是首頁或是空殼)、ShoppingCar枕扫、Order陪腌。
問題一:每個(gè)Module下都有build.gradle,怎么統(tǒng)一管理版本號和引入包烟瞧?
- 在Project目錄下創(chuàng)建
config.gradle
诗鸭,并且在Project目錄下的build.gradle
中引入:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"
buildscript {
...
}
allprojects {
...
}
...
- 在
config.gradle
中統(tǒng)一管理:
// 添加多個(gè)自定義屬性,可以通過ext代碼塊
ext {
androidId = [
compileSdkVersion: 28,
buildToolsVersion: "29.0.0",
minSdkVersion : 21,
targetSdkVersion : 28,
versionCode : 1,
versionName : "1.0"
]
appId = [
app : "com.yu.modular",
]
supportLibrary = "28.0.0"
dependencies = [
"appcompat" : "com.android.support:appcompat-v7:${supportLibrary}",
"recyclerview" : "com.android.support:recyclerview-v7:${supportLibrary}",
"constraint" : "com.android.support.constraint:constraint-layout:1.1.3"
]
}
- app的
build.gradle
中使用:
apply plugin: 'com.android.application'
// 賦值與引用
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
android {
compileSdkVersion androidId.compileSdkVersion
buildToolsVersion androidId.buildToolsVersion
defaultConfig {
applicationId appId.applicationId
minSdkVersion androidId.minSdkVersion
targetSdkVersion androidId.targetSdkVersion
versionCode androidId.versionCode
versionName androidId.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 標(biāo)準(zhǔn)寫法
// implementation group: 'com.android.support', name:'appcompat-v7', version:'28.0.0'
// 簡寫
// implementation 'com.android.support:appcompat-v7:28.0.0'
// 依賴第三方庫最簡潔的方式:
support.each { k, v -> implementation v } // 上面已經(jīng)定義過support参滴,是在Project的config.gradle中統(tǒng)一管理的
}
其他的Module的build.gradle
中也是一樣用强岸,只是如果是library的話不需要applicationId
問題二:怎么組合Module? 怎么單獨(dú)運(yùn)行Module卵洗?
如果創(chuàng)建Module的是Phone & Tablet Module请唱, 那么這個(gè)Module是可以單獨(dú)運(yùn)行的,但是怎么集成進(jìn)app过蹂?這樣的Module直接implementation會報(bào)循環(huán)引用的錯(cuò)誤十绑。
如果創(chuàng)建Module的是Android Library,那這個(gè)Module是可以在app中引入的酷勺,但是這樣的Module如何獨(dú)立運(yùn)行本橙?
這里就需要?jiǎng)討B(tài)去切換Module的類型。對比Phone Module和Android Library的build.gradle
可看出不同處只有兩點(diǎn):
- Phone Module中:
apply plugin: 'com.android.application'
脆诉,而Android Library中:apply plugin: 'com.android.library'
- Phone Module有applicationId甚亭,而Android Library沒有。
要完成在Module的類型切換击胜,只需要?jiǎng)討B(tài)修改這兩個(gè)地方亏狰。
在config.gradle
中定義一個(gè)標(biāo)簽用來標(biāo)示組件化還是集成化,并且統(tǒng)一管理Module的applicationId:
ext {
// false: 組件化模式(子模塊可以獨(dú)立運(yùn)行)偶摔,true :集成化模式(打包整個(gè)項(xiàng)目apk暇唾,子模塊不可獨(dú)立運(yùn)行)
isRelease = false
···
appId = [
app : "com.yu.modular",
order : "com.yu.order",
shopping : "com.yu.shopping"
]
...
}
app中不需要修改,無論什么環(huán)境下都是Phone Module辰斋,下面以order模塊的build.gradle
為例策州,按上面兩個(gè)不同點(diǎn),這里需要修改一下兩處:
if (isRelease) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
...
def appId = rootProject.ext.appId
android {
...
defaultConfig {
if (!isRelease) {
applicationId appId.order
}
...
}
...
}
...
ShoppingCar也需要做同樣修改宫仗,然后在app的build.gradle
中加入:
apply plugin: 'com.android.application'
...
dependencies {
...
if (isRelease) {
implementation project(':order')
implementation project(':shoppingcar')
}
}
這里有兩點(diǎn)需要注意:
- 創(chuàng)建Module的時(shí)候建議選擇Phone & Tablet Module够挂,如果選擇的是Android Library,
AndroidManifest
文件和res
目錄內(nèi)容不全藕夫,需要手動修改孽糖。 - Module中java和res目錄下的命名前面盡量加上Module名,類似:
Order_MainActivity.java
汁胆、order_activity_main.xml
這樣梭姓,方便區(qū)分。
完成以上操作嫩码,只要修改config.gradle
中的isRelease
誉尖,就可以實(shí)現(xiàn)集成化和插件化的切換了。isRelease=true
時(shí)铸题,打包出來是一個(gè)apk铡恕,除app外其他的module不可以單獨(dú)運(yùn)行;isRelease=false
時(shí)丢间,打包出來有幾個(gè)module做過上述配置就有幾個(gè)apk探熔,所有的module都可以單獨(dú)運(yùn)行。
問題三:Debug時(shí)候的一些類并不想在Release的時(shí)候打包進(jìn)去柬甥,怎么去隔離這些文件?比如單獨(dú)模塊提供給測試的時(shí)候其垄,需要有個(gè)Activity去模擬觸發(fā)其他模塊傳遞過來的操作苛蒲,而這個(gè)Activity打包的時(shí)候并不需要。
這里需要為這些module在不同環(huán)境下創(chuàng)建不同的AndroidManifest文件绿满,并且設(shè)置Release環(huán)境下的過濾目錄臂外,以O(shè)rder模塊為例,在Order的build.gradle
文件中添加:
...
android {
...
// 配置資源路徑喇颁,方便測試環(huán)境漏健,打包不集成到正式環(huán)境
sourceSets {
main {
if (!isRelease) {
// 如果是組件化模式,需要單獨(dú)運(yùn)行時(shí)
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
// 集成化模式橘霎,整個(gè)項(xiàng)目打包apk
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// release 時(shí) debug 目錄下文件不需要合并到主工程
exclude '**/debug/**'
}
}
}
}
}
...
對應(yīng)創(chuàng)建出文件和目錄:
這樣在debug包下的文件蔫浆,就不會打包進(jìn)Release時(shí)的apk中,如果是Activity姐叁,也只需要配置Debug環(huán)境下的
AndroidManifest
就可以克懊。這里就不貼圖了,可以在打包出來的apk中驗(yàn)證下(驗(yàn)證時(shí)關(guān)閉分包更好找一些)七蜘。
問題四:公共基礎(chǔ)庫怎么引用谭溉,比如圖片下載,網(wǎng)絡(luò)請求等橡卤?
這里沒什么特殊處理扮念,新創(chuàng)建一個(gè)Android Library,比如:common
碧库,然后再需要用到的module的build.gradle
中跟引入其他三方庫一樣柜与,添加:
..
dependencies {
...
implementation project(':common') // 公共基礎(chǔ)庫
}
問題五:集成化的Module之間怎么交互?
集成化環(huán)境下app模塊可以調(diào)用其他的模塊嵌灰,因?yàn)?code>isRelease==true時(shí)會引入其他模塊:
if (isRelease) {
implementation project(':order')
implementation project(':shoppingcar')
}
但是其他模塊之間的互相調(diào)用呢弄匕?
EventBus?很雜沽瞭,不方便管理迁匠。
反射?高版本api有可能被@hide驹溃。
隱示意圖城丧?廣播? 都不夠舒服豌鹤,沒人愿意這么寫亡哄。
先看下面兩種實(shí)現(xiàn)方式
- 用類加載
Class clazz = Class.forName("com.yu.modular.shoppingcar.ShoppdingCar_MainActivity");
Intent intent = new Intent(this, clazz);
startActivity(intent);
這樣寫很容易寫錯(cuò)先不說,一旦一模塊修改了包名或類名布疙,其他模塊都要改蚊惯,很難維護(hù)愿卸。
- 在公共庫
common
中對Activity進(jìn)行統(tǒng)一管理,因?yàn)楦髂K都引入了公共庫common
截型,所以都可以對common
進(jìn)行操作擦酌。
公共庫的PathManager:
public class PathManager {
/**
*
* @param groupName 組名,如:"order"
* @param pathName 路勁名菠劝,如:"Order_MainActivity"
* @param clazz 類對象,如:Order_MainActivity.class
*/
public static void set(String groupName, String pathName, Class<?> clazz) {
...
}
/**
*
* @param groupName 組名
* @param pathName 路徑名
* @return 跳轉(zhuǎn)目標(biāo)的class類對象
*/
public static Class<?> get(String groupName, String pathName) {
...
}
...
}
以什么形式存睁搭?
這里把Activity路徑和類封裝成一個(gè)Bean去儲存:
public class PathBean {
private String path;
private Class clazz;
public PathBean(String path, Class clazz) {
this.path = path;
this.clazz = clazz;
}
...
}
Map里的存儲結(jié)構(gòu)類似這樣:
[
app : [app_PathBean1, app_PathBean2, app_PathBean3],
shoppingCar: [shoppingCar_PathBean1, shoppingCar_PathBean2, shoppingCar_PathBean3],
order: [order_PathBean1, order_PathBean2, order_PathBean3]
]
什么時(shí)候去存赶诊?如果在Activity中去存肯定是來不及的,所以app(主模塊)的Application的onCreate方法中去存:
public class AppApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
PathManager.set("app", "MainActivity", MainActivity.class);
PathManager.set("shoppingcar", "ShoppingCar_MainActivity", MainActivity.class);
PathManager.set("order", "Order_MainActivity", MainActivity.class);
}
}
跳轉(zhuǎn)時(shí)這樣調(diào)用:
Class clazz = PathManager.get("order", "Order_MainActivity");
Intent intent = new Intent(this, clazz);
startActivity(intent);
這樣就完成集成化模塊間通訊了园骆。
項(xiàng)目地址
手動去管理這個(gè)存儲的Map還是不太舒服舔痪,有沒有什么辦法可以自動生成?
有锌唾,使用APT+JavaPoet技術(shù)锄码。Android組件化實(shí)現(xiàn)方案(二)