随着项目规模的不断扩大,为了更好的进行协作开发,提高开发效率,必须对项目进行改造以支持模块化、插件化。在对项目进行模块化时遇到的第一个挑战就是模块之间的通信。这篇文章将探讨 Android
项目中的跨模块通信。
更多文章可查看我的独立博客
模块化
首先解释一下为什么需要跨模块通信,模块之间为什么不能直接通信。
上图是项目模块化之前和模块化之后的对比图。在模块化之前一般的
Android
项目都在同一个
module
app
中,所有的功能模块都可以互相调用,不存在跨模块通信的问题。在模块化之后项目中的各模块有了层级关系,在最底层是一些与项目业务无关的
lib
库,上面一层是
base
,再上面一层是与业务紧密相关的各功能模块。在这种项目结构中,同一层级之间不直接依赖,因此同级模块之间不能直接跳转或通信,因此才有了各种跨模块通信机制。
跨模块通信机制
跨模块通信需要解决的两个问题
- 跨模块跳转
- 跨模块调用方法
跨模块通信核心原理
我们可以先考虑一下为什么模块之间不能直接通信?其实原因很简单,那就是模块之间禁止直接依赖,因为模块之间没有直接依赖,所以也就不能拿到对应的 class 对象。对应到 Android
就是在 startActivity
时拿不到要跳转的 Activity
的 class
对象。所以跨模块通信的核心原理非常简单:将字符串和 class 对象对应起来,然后通过字符串去进行通信。
跨模块通信实现
下面我们将一步一步实现一个简易版的跨模块通信框架。首先我们根据上图依赖关系新建一个模块化项目,依赖关系如下图:
跨模块通信框架简易版
可以看到项目依赖关系变简单了,但是这并不妨碍我们的模块间通信框架的设计。现在 main
中有 HomeActivity
, mine 中有 MineActivity
, account
中有 AccountActivity
, 现在我们的需求是 HomeActivity
中点击按钮跳转到 MineActivity
。因为 main 没有直接依赖 mine
,所以我们不能简单的通过 Android
的 startActivity
的方式进行跳转,所以接下来我们就实现我们的第一版跨模块通信。
因为模块之间没有互相依赖,所以模块之间的通信只能通过他们共同依赖的 base
实现了。
1. 首先各模块将需要暴露的 Activity 注入到 base 中。
//位于 base 中,各模块通过调用 inject 方法将 class 对象保存到 sClassMap 中
public class Injector {
private static Map> sClassMap = new HashMap<>();
public static void inject(String name, Class> clazz) {
sClassMap.put(name, clazz);
}
public static Class> getClass(String className) {
return sClassMap.get(className);
}
}
// 位于 mine 中
public class MineArchmage {
public static void init() {
Injector.inject("MineActivity", MineActivity.class);
}
}
//下面的代码可以在 Application 的 onCreate 方法中执行
MineArchmage.init();
MainArchmage.init();
AccountArchmage.init();
通过上面的代码已经将字符串和相应的 class 对象保存了起来。
2. 跳转
//此类位于 base 中,有了这个类之后,如果有跨模块跳转的需求可直接调用 startActivity 方法即可
public class Transfer {
public static void startActivity(Activity activity, String path, Intent intent) {
Class> clazz = parsePath(path);
if (clazz == null || !Activity.class.isAssignableFrom(clazz)) {
throw new IllegalStateException("con't find the class!");
}
intent.setClass(activity, clazz);
activity.startActivity(intent);
}
private static Class> parsePath(String path) {
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("path must not null!");
}
return Injector.getClass(path);
}
}
//例如
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
}
public void toMine(View view) {
Transfer.startActivity(this, "MineActivity", new Intent());
}
}
通过以上很简单的方法我们已经实现了跨模块的跳转,虽然代码很粗糙,但是我们的需求确实实现了。
3.跨模块调用方法
现在 base 中有一个接口,account
中的 AccountUtilImpl
进行了实现:
//位于 base 中
public interface AccountUtil {
boolean isLogin();
}
//位于 base 中
public class AccountUtilImpl implements AccountUtil {
@Override
public boolean isLogin() {
return false;
}
}
我们的需求是在 MineActivity 中点击按钮调用 AccountUtilImpl 的 isLogin 方法,有了前面跨模块跳转的基础,这个需求就非常简单了。
public class AccountArchmage {
public static void init() {
Injector.inject("AccountActivity", AccountActivity.class);
Injector.inject(AccountUtil.class.toString(), AccountUtilImpl.class);//新增
}
}
//新增方法
public static Class> obtainService(Class> service) {
return parsePath(service.toString());
}
AccountUtil accountUtil = (AccountUtil) Transfer.obtainService(AccountUtil.class).newInstance();
Toast.makeText(this, String.valueOf(accountUtil.isLogin()), Toast.LENGTH_SHORT).show();
到此为止,我们已经实现了跨模块通信的两个基础需求,然而代码粗糙,功能过于简陋,还存在着很多问题,因此下面我们进一步完善。
跨模块通信改进版
跨模块通信第一版的不足
- 内存浪费;上面我们的代码是在 Application 初始化的时候就将所有的需要暴露的
Activity
和 接口的 class 都载入了内存,这是一种浪费,因为用户每次访问我们的应用的时候不是每一个页面都会访问到的,而我们却将所有的 Activity 的 class 都在应用初始化的时候就载入了内存,这确实是一种内存浪费,而且影响了应用的初始化速度。 - 跨模块调用方法需要强转(
obtainService
)和反射。如果每次调用方法都需要反射调用势必会影响应用的性能。 - 重复且毫无技术含量代码多(如各个 module 中的
Archmage
)。
针对上面存在的问题,我们下面进一步改善。
我的思路是将所有需要暴露的 Activity 进行分组,在应用初始化的时候先将所有的组加载进内存,然后在调用到每个组的第一个 Activity 时将组内的所有 Activity
的 class 对象加载进内存,这样会有效的改善内存浪费的不足。其中组的划分以业务的关联程度为依据。
因此我们抽象出了 GroupLoader
接口, 所有的 GroupLoader
会在应用初始化的时候进行加载。在路由的时候如果调用到了当前 GroupLoader
,则 GroupLoader
会负责将组内所有的 Activity 的 class 对象加载进内存。 ActivityLoader
由 GroupLoader
调用加载组内所有的 Activity
的 class
对象。
// GroupLoader 接口,位于 base 中
public interface GroupLoader {
Map injectModule();
Map> injectService();
Class extends Activity> getActivity(String activityName);
}
// ActivityLoader 接口,位于 base 中
public interface ActivityLoader {
Map> injectActivity();
}
然后在各 module
中实现各个 GroupLoader
和 ActivityLoader
。
// 位于 account 中
public class AccountGroupLoader implements GroupLoader {
private Map> sActivityMap;
@Override
public Map injectModule() {
Map result = new HashMap<>();
result.put("account", new AccountGroupLoader());
return result;
}
public Map> injectService() {
Map> serviceMap = new HashMap<>();
serviceMap.put(AccountUtil.class.getSimpleName(), AccountUtilImpl.class);
return serviceMap;
}
@Override
public Class extends Activity> getActivity(String activityName) {
// 若 sActivityMap 为 null 则调用 AccountActivityLoader 进行加载 组内所有的 Activity 的 class
if (sActivityMap == null) {
sActivityMap = new AccountActivityLoader().injectActivity();
}
if (sActivityMap == null) {
throw new IllegalStateException(activityName + "not found!");
}
return sActivityMap.get(activityName);
}
}
// 位于 account 中
public class AccountActivityLoader implements ActivityLoader {
@Override
public Map> injectActivity() {
Map> result = new HashMap<>();
result.put("AccountActivity", AccountActivity.class);
return result;
}
}
在进行了分组之后跳转时的 path 类似如:account/AccountActivity
。下面我们再看一下注入 GroupLoader
的代码:
// inject 将所有的 GroupLoader 和 service 进行注入。由于 inject() 位于 base 中, base 不能依赖到各 module 所以 GroupLoader 的注入采用了
//反射的方式,但是由于 GroupLoader 的数量不会太多,所以 GroupLoader 的注入对于性能的影响不会太大
static void inject() {
try {
GroupLoader mainGroupLoader = (GroupLoader) Class.forName("com.huweiqiang.main.MainGroupLoader").newInstance();
Map mainModuleLoaderMap = mainGroupLoader.injectModule();
if (mainModuleLoaderMap != null) {
sModuleLoaderMap.putAll(mainModuleLoaderMap);
}
Map> mainServiceMap = mainGroupLoader.injectService();
if (mainServiceMap != null) {
sServiceClassMap.putAll(mainServiceMap);
}
GroupLoader mineGroupLoader = (GroupLoader) Class.forName("com.huweiqiang.mine.MineGroupLoader").newInstance();
Map mineModuleLoaderMap = mineGroupLoader.injectModule();
if (mineModuleLoaderMap != null) {
sModuleLoaderMap.putAll(mineModuleLoaderMap);
}
Map> mineServiceMap = mineGroupLoader.injectService();
if (mineServiceMap != null) {
sServiceClassMap.putAll(mineServiceMap);
}
GroupLoader accountGroupLoader = (GroupLoader) Class.forName("com.huweiqiang.account.AccountGroupLoader").newInstance();
Map accountModuleMap = accountGroupLoader.injectModule();
if (accountModuleMap != null) {
sModuleLoaderMap.putAll(accountModuleMap);
}
Map> accountServiceMap = accountGroupLoader.injectService();
if (accountServiceMap != null) {
sServiceClassMap.putAll(accountServiceMap);
}
} catch (Exception e) {
e.printStackTrace();
}
}
最后我们再看一下路由的代码
// Transfer 位于 base 中
public class Transfer {
public static void startActivity(Activity activity, String path, Intent intent) {
Class> clazz = parseActivityPath(path);
if (clazz == null || !Activity.class.isAssignableFrom(clazz)) {
throw new IllegalStateException("con't find the class!");
}
intent.setClass(activity, clazz);
activity.startActivity(intent);
}
public static IService obtainService(Class extends IService> service) {
return Injector.getService(service.getSimpleName());
}
private static Class> parseActivityPath(String path) {
String module = parseModule(path);
GroupLoader groupLoader = Injector.getModuleLoader(module);
String activityName = parseClass(path);
return groupLoader.getActivity(activityName);
}
private static String parseModule(String path) {
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("path must not null!");
}
int separatorIndex = path.indexOf("/");
if (separatorIndex == -1) {
throw new IllegalStateException("path must has / ");
}
return path.substring(0, separatorIndex);
}
private static String parseClass(String path) {
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("path must not null!");
}
int separatorIndex = path.indexOf("/");
if (separatorIndex == -1) {
throw new IllegalStateException("path must has / ");
}
return path.substring(separatorIndex + 1);
}
}
下面再看看 Injector.getService
和 Injector.getModuleLoader
的实现
static GroupLoader getModuleLoader(String moduleName) {
return sModuleLoaderMap.get(moduleName);
}
static IService getService(String serviceName) {
if (sServiceMap.get(serviceName) != null) {
return sServiceMap.get(serviceName);
}
if (sServiceClassMap.get(serviceName) != null) {
try {
// 对 service 进行了缓存,但是不是所有的 service 都能缓存,所以这一块需要进一步优化
sServiceMap.put(serviceName, sServiceClassMap.get(serviceName).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return sServiceMap.get(serviceName);
}
经过上面的改进我们的跨模块通信框架已经完善了很多,但是还是有一个最大的问题没有解决。从上面的代码我们可以看到有很多无脑重复的代码,例如 各个 GroupLoader
和 ActivityLoader
,如果要解决这个问题我们可以使用编译时注解,这就是另外一个话题了,在这里我只提供几个关键字 APT
、 javapoet
总结
本文通过一个简单的实例实现了一个简易版的跨模块通信机制,通过实现这个简易的跨模块通信框架应该对于跨模块通信有了一个基本的认识,再学习和使用一些完善的路由框架时也有了章法。
示例代码