Android模組化中的服務發現機制
前言
本文討論的其實是專案經過模組化後的一種情況,如果沒有模組化需求其實是無所謂的。如果專案已經進行了模組化,推薦大家花幾分鐘看下。
需求
首先,我們來看一張常見的模組化後的圖:

專案結構圖.png
然後我們有這樣一個需求,我希望在app中獲取所依賴的所有業務module的名稱。
注意,模組化後各個業務模組一般都是可以拆卸的,也就是說隨時會增加或減少模組
分析
現在我們分析下有哪些方案:
方案一:App模組中手動維護
public class ModuleName { private static List<String> moduleName = new ArrayList<>(); static { moduleName.add("businessone"); moduleName.add("businesstwo"); } public static List<String> getModuleName(){ return moduleName; } }
這種方案是最容易想到的,也是最簡單的,但有一個問題,如果解除安裝了某個模組或者新增了一個模組,我們就得手動修改ModuleName這個類,這導致了一個結果: 一個模組的變更會影響另一個模組的功能,這和我們模組化解耦的要求完全不符合,所以肯定不能採用,這樣的程式碼完全無法維護。
方案二:各業務模組主動注入
既然方案一耦合太嚴重,我們換種思路,將這個moduleName的list放到bottomlibrary中,然後各模組自己往裡面新增資料。
首先在bottomlibrary中:
public class ModuleName { private static List<String> moduleName = new ArrayList<>(); public static void addModuleName(String name){ moduleName.add(name); } public static List<String> getModuleName(){ return moduleName; } }
然後在各模組中手動註冊:
ModuleName.addModuleName("businessone");
現在,對比方案一,我們發現耦合嚴重的問題已經沒有了,一個模組不載入或者新增一個模組再也不需要改動其他模組的程式碼了,但一個新的問題出現了: 我們何時何地呼叫那行註冊名稱的程式碼呢?
要知道,一般業務模組中是沒有Application存在的,如果希望在模組載入初始化時就呼叫的話就必須在各模組中定義自己的Application介面,然後統一由App中的Application呼叫分發。仔細想想這個過程,和app中獲取各模組的名稱是不是一個道理?只不過這裡是在app中要獲取各模組中的Application介面罷了。我們不可能用問題A去解決問題A,所以這樣不可取,為了解決這個問題我們看下一個方案。
方案三:服務發現機制
經過了方案二的討論,我們發現問題出在各模組什麼時候將自己的資訊註冊到統一管理類ModuleName中去,既然主動註冊時機不好確定,我們能不能換個思路,不要由各模組主動註冊,而是ModuleName主動去查詢呢?實際上是可行的。我們看下具體如何實現:
- 在bottomlibrary中提供一個介面:
public interface IModuleName { /** * 獲取所屬module的名稱 * @return module的名稱 */ String getModuleName(); }
這裡主要是為了統一服務提供形式
-
各模組實現這個介面:
比如在businessone模組中實現如下:
public class OneModuleName implements IModuleName { @Override public String getModuleName() { return "businessone"; } }
在businesstwo模組實現如下:
public class TwoModuleName implements IModuleName { @Override public String getModuleName() { return "businesstwo"; } }
-
各模組暴露自己的服務
在businessone的AndroidManifest.xml中定義一個meta-data:
<application> <meta-data android:name="com.jianglei.businessone.OneModuleName" android:value="module_name"/> </application>
在businesstwo模組中:
<application> <meta-data android:name="com.jianglei.businesstwo.TwoModuleName" android:value="module_name"/> </application>
-
發現服務
所謂發現服務就是我們要在呼叫ModuleName.getModuleName()之前先查詢各模組的具體實現服務,核心就是讀取AndroidManifest.xml中定義的meta-data中的資料:
public static List<String> getModuleName(Context context,String metaDataValue){ if(moduleName.size != 0){ return moduleName; } List<String > res = new ArrayList<>(); if (context == null || metaDataValue == null) { throw new IllegalArgumentException("Context or metaDataValue can not be null"); } Bundle metaData = getMetaData(context); if (metaData == null) { return res; } Set<String> keySet = metaData.keySet(); List<IModuleName> services = new ArrayList<>(); for (String metaDataKey : keySet) { Object obj = metaData.get(metaDataKey); if (!(obj instanceof String)) { continue; } String registerValue = metaData.getString(metaDataKey); if(registerValue == null || !registerValue.equals(metaDataValue)){ continue; } try { Class serviceCls = Class.forName(metaDataKey); Object service = serviceCls.newInstance(); services.add(service); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); throw new IllegalArgumentException("The service should have a public and non-parameter constructor"); } } if (services.size() != 0) { for(IModuleName iModuleName : services){ res.add(iModuleName.getModuleName()); } } return res; }
我們看下現在的getModuleName的實現,核心就是讀取註冊在AndroidManifest.xml中的IModuleName服務,然後通過反射例項化,這樣我們就成功的拿到了資料。
對比方案二,方案三無需各模組主動呼叫註冊,而是換成了在AndroidManifest.xml中註冊的形式,更加解耦,我提供什麼服務就註冊什麼服務,這套思想其實借鑑了java的SPI機制,有興趣的可以自行了解。
類庫推薦
利用上面的思想,我封裝了一個工具庫,專門用來提供服務發現:
大家有興趣可以使用看看,感覺還是能省不少功夫的,如果有問題歡迎提issue。