在 Android 開發(fā)中,當(dāng)項(xiàng)目增加一定規(guī)模之后,一般都會采用多模塊的項(xiàng)目結(jié)構(gòu)。當(dāng)然也能采用插件化的開發(fā)模式,具體采用什么開發(fā)模式,開發(fā)者可以自行定奪。這里將介紹下我所熟悉的一種模塊化開發(fā)機(jī)制。本質(zhì)是基于 gradle 的 Multi-Project 構(gòu)建和 Java 的動態(tài)代理機(jī)制。
這個方案現(xiàn)已提取開源:https://github.com/stefanJi/Android-MPD
// settings.gradle
include ':app', ':feature_a', ':feature_b', ':feature_c'
利用這套機(jī)制時:比如開發(fā)者開發(fā) A 功能時,只會涉及 feature_a 模塊中代碼的修改,那么開發(fā)者就可以讓 gradle 不編譯其他模塊中的代碼(在 settings.gradle 中注釋不需要的模塊),從而能夠減少本地開發(fā)時的編譯耗時,也能夠讓某些代碼只會在開發(fā)期間存在(比如為了方便測試單獨(dú)提供的 admin 模塊)。
選擇性編譯
同時為了避免因?yàn)槟K間彼此依賴,導(dǎo)致個別模塊不編譯的目標(biāo)無法實(shí)現(xiàn)。于是要求各個模塊之間不能直接依賴具體實(shí)現(xiàn),只能依賴某個公共模塊(比如 app 模塊)提供的接口。
具體實(shí)現(xiàn)是:
在 app 模塊中定義模塊 feature_a 能夠提供的功能,比如打開 feature_a 中的一個 Activity.
public interface IFeatureA {
@Nullable
public Class<Activity> getActivityOfA();
}
然后在 feature_a 中實(shí)現(xiàn)這個接口:
public class FeatureA implements IFeatureA {
public Class<Activity> getActivityOfA() {
return FeatureAActivity.class;
}
}
然后在 app 中注入 IFeatureA 接口的具體實(shí)現(xiàn):
public class FeatureRegister {
private static IFeatureA featureA;
@Nullable
public static IFeatureA getFeatureA() {
if (featureA == null) {
try {
// 這里使用反射能夠在即使 feature_a 模塊未加入編譯之后也能夠成功編譯
featureA = (IFeatureA) Class.forName("io.github.stefanji.feature_a.FeatureA").newInstance();
} catch (ClassNotFoundException e) {
// 如果 feature_a 未加入編譯,則會觸發(fā)異常
}
}
return featureA;
}
}
然后 feature_b 需要獲取 feature_a 的 FeatureAActivity:
IFeatureA featureA = FeatureRegister.getFeatureA();
Class<Activity> activityClass = null;
if (featureA != null) {
activityClass = featureA.getActivityOfA();
};
動態(tài)代理未加入編譯的模塊
在上面我們已經(jīng)能夠?qū)崿F(xiàn)不編譯某些模塊,并且項(xiàng)目整體編譯不會出現(xiàn)問題。但是每次訪問其他模塊提供的功能時,從 app 模塊獲取實(shí)現(xiàn)之后需要進(jìn)行判空處理。不是太優(yōu)雅。
于是可以利用動態(tài)代理,如果目標(biāo)模塊沒有被編譯,那么就返回一個實(shí)現(xiàn)了目標(biāo)模塊功能接口的代理對象。
修改 app 中代碼如下:
@NotNull
public static IFeatureA getFeatureA() {
if (featureA == null) {
try {
featureA = (IFeatureA) Class.forName("io.github.stefanji.feature_a.FeatureA").newInstance();
} catch (ClassNotFoundException e) {
}
// 如果 feature_a 模塊未沒編譯,F(xiàn)eatureA 類將找不到,就動態(tài)生成一個 Proxy 類
if (featureA == null) {
featureA = (IFeatureA) Proxy.newProxyInstance(FeatureRegister.class.getClassLoader(),
new Class[]{IFeatureA.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class returnType = method.getReturnType();
// 讓原始類型返回零值
if (returnType == boolean.class) {
return false;
}
if (returnType == int.class) {
return 0;
}
if (returnType == float.class) {
return 0f;
}
//...
// 讓引用類型返回 null
return null;
}
});
}
}
return featureA;
}
這樣就不用做如下判空了:
if (featureA != null) {
activityClass = featureA.getActivityOfA();
};
自動化
每新增一個模塊,我們就需要重復(fù)上面的步驟,在 FeatureRegister 中注冊新的 Feature 接口。這種重復(fù)的操作,當(dāng)然可以利用注解處理器或 gradle transform 之類的代碼生成機(jī)制,讓編譯器去自動生成模板代碼。