前言
本文讨论的其实是项目经过模块化后的一种情况,如果没有模块化需求其实是无所谓的。如果项目已经进行了模块化,推荐大家花几分钟看下。
需求
首先,我们来看一张常见的模块化后的图:
项目结构图.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机制,有兴趣的可以自行了解。
类库推荐
利用上面的思想,我封装了一个工具库,专门用来提供服务发现:
Android服务发现库
https://github.com/FamliarMan/AndroidServiceProvider
大家有兴趣可以使用看看,感觉还是能省不少功夫的,如果有问题欢迎提issue。