方案設計背景(前面都是廢話)
初期Android專案構建方式基本為分層結構設計,由於初期專案較小這種架構簡單,清晰因此沿用至今。而當專案業務逐漸擴大時發現簡單的分層結構已經無法滿足現有專案架構,尤其是多人開發時各個業務之間溝通成本成指數上升。 應運而生的模組化思想誕生,初期大家只是將專案的業務進行簡單拆分成各個module,以主module去引用子module。
後來發現這種方式隔絕了子module之間的通訊,且主module只能主動的引用子module,無法被子module訪問。隨之而來進入主題了--元件間如何去通訊
元件間通訊方式
目前最常用的通訊方式有Event、協議、RPC介面三種主要形式,但實際上都只是協議的一種變形封裝而已。從標題能看出我參考了微信的模組化通訊我就不墨跡,我更傾向於微信的RPC形式的通訊方式。 原因有以下幾點:
- 協議以介面形式展現能避免協議格式以及欄位的維護
- 雙方開發時省卻協議解析步驟,避免解析過程中錯誤概率
- 將介面以aar形式對外開放,能極大節省雙方接入以及後續維護成本
方式已經確定那接下來就是解決實現上的難題,從上述原因中我們可以看到幾個關鍵點:
- 如何暴露介面
- 如何生成介面aar
- 如何註冊並初始化介面實現類
首先我們來說說如何生成介面aar,大家都知道aar對應的是一個獨立的module,那如何自動生成該module? 從微信的實現上看它是為setting新增了include_with_api方法,並修改了gradle指向的setting實現類,從而在初始化setting類時自動生成介面module。 由於個人研究時間有限目前採用取巧方案,在setting.gradle檔案中新增include_with_api方法用來自動生成介面module,而build.gradle和AndroidManifest.xml檔案皆從實現module中拷貝過來,並修改AndroidManifest.xml中的package以及去掉android:label資訊,再去掉原有build.gradle的介面module依賴,從而順利解決介面module生成方式。
def include_with_api(def projectName) {
include projectName
String rootDir = rootDir.getAbsolutePath();
String moduleName = ((String) projectName).replace(":", "")
String parentName = moduleName.replace("plugin-", "");
copy() {
from rootDir + '/' + parentName + '/build.gradle'
into rootDir + '/' + moduleName + '/'
filter { line ->
String content = line;
if (content.contains(moduleName)) {
content = "";
}
content
}
}
copy() {
from rootDir + '/' + parentName + '/src/main/AndroidManifest.xml'
into rootDir + '/' + moduleName + '/src/main/'
filter { line ->
String content = line;
content = content.replace("android:label=\"@string/app_name\"", "")
if (content.contains("package=\"")) {
content = content.replace("\">", ".plugin\">")
}
content
}
}
}
複製程式碼
再來看下關於介面暴露的問題,其實介面暴露方案主要還是參考微信只不過中間實現細節是否一樣就不可知了。 下面著重介紹下我的細節實現,從微信方案中可以看出是將介面檔案從.java字尾改成.api,然後將.api字尾的檔案拷貝至介面module中。通過gradle外掛來定製依賴配置項compileApi,實現module通過compileApi依賴關聯介面module,在解析中將實現module中介面檔案拷貝至介面module並修改字尾名從而通過編譯。
Configuration configurationtest = project.configurations.create("compileApi")
configurationtest.canBeResolved = true;
configurationtest.canBeConsumed = true;
configurationtest.setVisible(false)
//當該依賴配置被解析時會執行
configurationtest.allDependencies.all {obj->
moveFile(project, obj.name)
project.dependencies.add("compile", project.project(':' + obj.name))
}
複製程式碼
接下來我們可以考慮如何註冊以及初始化介面實現類,所謂的註冊也就是如何通過介面找到實現類並隱式例項化。
@AService("com.netease.add.Add")
public abstract class IAdd extends IService{
abstract int add(int a, int b);
}
public static <T extends IService> void register(Class<T> clazz) {
try {
AService aService = clazz.getAnnotation(AService.class);
if (aService == null) {
return;
}
String clazzName = aService.value();
if (!TextUtils.isEmpty(clazzName)) {
T t = (T) Class.forName(clazzName).newInstance();
ServiceManager.getInstance().put(clazz.getSimpleName(), t);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
複製程式碼
考慮到介面的侷限性,我通過註解形式將實現類資訊傳遞給介面。至於具體的初始化環節我就不搬過來了,大家可以參照微信的方式自由發揮。
在註冊這個點上還有很多其他方案比如aop/ioc等,大家有興趣的話可以去研究並不難。
最後
上述只是我對微信模組化通訊環節的實現,從而打通了整個環路。至於專案的模組化改造還得依情況而定,從思維角度來講要遵循大事化小,如果本身就是個小專案就沒必要引入繁瑣的模組化架構。模組化不單單只是說說以及架構環節,還涉及如何劃分模組邊界等細節問題。 最後希望我們分享的一點經驗能對大家有些價值。