隨著項(xiàng)目規(guī)模的不斷擴(kuò)大癌蓖,為了更好的進(jìn)行協(xié)作開發(fā),提高開發(fā)效率份名,必須對(duì)項(xiàng)目進(jìn)行改造以支持模塊化碟联、插件化。在對(duì)項(xiàng)目進(jìn)行模塊化時(shí)遇到的第一個(gè)挑戰(zhàn)就是模塊之間的通信僵腺。這篇文章將探討 Android
項(xiàng)目中的跨模塊通信鲤孵。
更多文章可查看我的獨(dú)立博客
模塊化
首先解釋一下為什么需要跨模塊通信演怎,模塊之間為什么不能直接通信惜辑。
上圖是項(xiàng)目模塊化之前和模塊化之后的對(duì)比圖译断。在模塊化之前一般的
Android
項(xiàng)目都在同一個(gè) module
app
中碴开,所有的功能模塊都可以互相調(diào)用德谅,不存在跨模塊通信的問題熏瞄。在模塊化之后項(xiàng)目中的各模塊有了層級(jí)關(guān)系拍埠,在最底層是一些與項(xiàng)目業(yè)務(wù)無關(guān)的 lib
庫秸抚,上面一層是 base
豌蟋,再上面一層是與業(yè)務(wù)緊密相關(guān)的各功能模塊廊散。在這種項(xiàng)目結(jié)構(gòu)中,同一層級(jí)之間不直接依賴夺饲,因此同級(jí)模塊之間不能直接跳轉(zhuǎn)或通信奸汇,因此才有了各種跨模塊通信機(jī)制。
跨模塊通信機(jī)制
跨模塊通信需要解決的兩個(gè)問題
- 跨模塊跳轉(zhuǎn)
- 跨模塊調(diào)用方法
跨模塊通信核心原理
我們可以先考慮一下為什么模塊之間不能直接通信往声?其實(shí)原因很簡(jiǎn)單擂找,那就是模塊之間禁止直接依賴,因?yàn)槟K之間沒有直接依賴浩销,所以也就不能拿到對(duì)應(yīng)的 class 對(duì)象贯涎。對(duì)應(yīng)到 Android
就是在 startActivity
時(shí)拿不到要跳轉(zhuǎn)的 Activity
的 class
對(duì)象。所以跨模塊通信的核心原理非常簡(jiǎn)單:將字符串和 class 對(duì)象對(duì)應(yīng)起來慢洋,然后通過字符串去進(jìn)行通信塘雳。
跨模塊通信實(shí)現(xiàn)
下面我們將一步一步實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的跨模塊通信框架。首先我們根據(jù)上圖依賴關(guān)系新建一個(gè)模塊化項(xiàng)目普筹,依賴關(guān)系如下圖:
跨模塊通信框架簡(jiǎn)易版
可以看到項(xiàng)目依賴關(guān)系變簡(jiǎn)單了败明,但是這并不妨礙我們的模塊間通信框架的設(shè)計(jì)。現(xiàn)在 main
中有 HomeActivity
, mine 中有 MineActivity
, account
中有 AccountActivity
, 現(xiàn)在我們的需求是 HomeActivity
中點(diǎn)擊按鈕跳轉(zhuǎn)到 MineActivity
太防。因?yàn)?main 沒有直接依賴 mine
妻顶,所以我們不能簡(jiǎn)單的通過 Android
的 startActivity
的方式進(jìn)行跳轉(zhuǎn),所以接下來我們就實(shí)現(xiàn)我們的第一版跨模塊通信。
因?yàn)槟K之間沒有互相依賴讳嘱,所以模塊之間的通信只能通過他們共同依賴的 base
實(shí)現(xiàn)了幔嗦。
1. 首先各模塊將需要暴露的 Activity 注入到 base 中。
//位于 base 中沥潭,各模塊通過調(diào)用 inject 方法將 class 對(duì)象保存到 sClassMap 中
public class Injector {
private static Map<String, Class<?>> 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 方法中執(zhí)行
MineArchmage.init();
MainArchmage.init();
AccountArchmage.init();
通過上面的代碼已經(jīng)將字符串和相應(yīng)的 class 對(duì)象保存了起來邀泉。
2. 跳轉(zhuǎn)
//此類位于 base 中,有了這個(gè)類之后钝鸽,如果有跨模塊跳轉(zhuǎn)的需求可直接調(diào)用 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());
}
}
通過以上很簡(jiǎn)單的方法我們已經(jīng)實(shí)現(xiàn)了跨模塊的跳轉(zhuǎn)汇恤,雖然代碼很粗糙,但是我們的需求確實(shí)實(shí)現(xiàn)了寞埠。
3.跨模塊調(diào)用方法
現(xiàn)在 base 中有一個(gè)接口屁置,account
中的 AccountUtilImpl
進(jìn)行了實(shí)現(xiàn):
//位于 base 中
public interface AccountUtil {
boolean isLogin();
}
//位于 base 中
public class AccountUtilImpl implements AccountUtil {
@Override
public boolean isLogin() {
return false;
}
}
我們的需求是在 MineActivity 中點(diǎn)擊按鈕調(diào)用 AccountUtilImpl 的 isLogin 方法焊夸,有了前面跨模塊跳轉(zhuǎn)的基礎(chǔ)仁连,這個(gè)需求就非常簡(jiǎn)單了。
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();
到此為止阱穗,我們已經(jīng)實(shí)現(xiàn)了跨模塊通信的兩個(gè)基礎(chǔ)需求饭冬,然而代碼粗糙,功能過于簡(jiǎn)陋揪阶,還存在著很多問題昌抠,因此下面我們進(jìn)一步完善。
跨模塊通信改進(jìn)版
跨模塊通信第一版的不足
- 內(nèi)存浪費(fèi)鲁僚;上面我們的代碼是在 Application 初始化的時(shí)候就將所有的需要暴露的
Activity
和 接口的 class 都載入了內(nèi)存炊苫,這是一種浪費(fèi),因?yàn)橛脩裘看卧L問我們的應(yīng)用的時(shí)候不是每一個(gè)頁面都會(huì)訪問到的冰沙,而我們卻將所有的 Activity 的 class 都在應(yīng)用初始化的時(shí)候就載入了內(nèi)存侨艾,這確實(shí)是一種內(nèi)存浪費(fèi),而且影響了應(yīng)用的初始化速度拓挥。 - 跨模塊調(diào)用方法需要強(qiáng)轉(zhuǎn)(
obtainService
)和反射唠梨。如果每次調(diào)用方法都需要反射調(diào)用勢(shì)必會(huì)影響應(yīng)用的性能。 - 重復(fù)且毫無技術(shù)含量代碼多(如各個(gè) module 中的
Archmage
)侥啤。
針對(duì)上面存在的問題当叭,我們下面進(jìn)一步改善。
我的思路是將所有需要暴露的 Activity 進(jìn)行分組盖灸,在應(yīng)用初始化的時(shí)候先將所有的組加載進(jìn)內(nèi)存蚁鳖,然后在調(diào)用到每個(gè)組的第一個(gè) Activity 時(shí)將組內(nèi)的所有 Activity
的 class 對(duì)象加載進(jìn)內(nèi)存,這樣會(huì)有效的改善內(nèi)存浪費(fèi)的不足赁炎。其中組的劃分以業(yè)務(wù)的關(guān)聯(lián)程度為依據(jù)醉箕。
因此我們抽象出了 GroupLoader
接口, 所有的 GroupLoader
會(huì)在應(yīng)用初始化的時(shí)候進(jìn)行加載。在路由的時(shí)候如果調(diào)用到了當(dāng)前 GroupLoader
琅攘,則 GroupLoader
會(huì)負(fù)責(zé)將組內(nèi)所有的 Activity 的 class 對(duì)象加載進(jìn)內(nèi)存垮庐。 ActivityLoader
由 GroupLoader
調(diào)用加載組內(nèi)所有的 Activity
的 class
對(duì)象。
// GroupLoader 接口坞琴,位于 base 中
public interface GroupLoader {
Map<String, GroupLoader> injectModule();
Map<String, Class<? extends IService>> injectService();
Class<? extends Activity> getActivity(String activityName);
}
// ActivityLoader 接口哨查,位于 base 中
public interface ActivityLoader {
Map<String, Class<? extends Activity>> injectActivity();
}
然后在各 module
中實(shí)現(xiàn)各個(gè) GroupLoader
和 ActivityLoader
。
// 位于 account 中
public class AccountGroupLoader implements GroupLoader {
private Map<String, Class<? extends Activity>> sActivityMap;
@Override
public Map<String, GroupLoader> injectModule() {
Map<String, GroupLoader> result = new HashMap<>();
result.put("account", new AccountGroupLoader());
return result;
}
public Map<String, Class<? extends IService>> injectService() {
Map<String, Class<? extends IService>> serviceMap = new HashMap<>();
serviceMap.put(AccountUtil.class.getSimpleName(), AccountUtilImpl.class);
return serviceMap;
}
@Override
public Class<? extends Activity> getActivity(String activityName) {
// 若 sActivityMap 為 null 則調(diào)用 AccountActivityLoader 進(jìn)行加載 組內(nèi)所有的 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<String, Class<? extends Activity>> injectActivity() {
Map<String, Class<? extends Activity>> result = new HashMap<>();
result.put("AccountActivity", AccountActivity.class);
return result;
}
}
在進(jìn)行了分組之后跳轉(zhuǎn)時(shí)的 path 類似如:account/AccountActivity
剧辐。下面我們?cè)倏匆幌伦⑷?GroupLoader
的代碼:
// inject 將所有的 GroupLoader 和 service 進(jìn)行注入寒亥。由于 inject() 位于 base 中, base 不能依賴到各 module 所以 GroupLoader 的注入采用了
//反射的方式荧关,但是由于 GroupLoader 的數(shù)量不會(huì)太多溉奕,所以 GroupLoader 的注入對(duì)于性能的影響不會(huì)太大
static void inject() {
try {
GroupLoader mainGroupLoader = (GroupLoader) Class.forName("com.huweiqiang.main.MainGroupLoader").newInstance();
Map<String, GroupLoader> mainModuleLoaderMap = mainGroupLoader.injectModule();
if (mainModuleLoaderMap != null) {
sModuleLoaderMap.putAll(mainModuleLoaderMap);
}
Map<String, Class<? extends IService>> mainServiceMap = mainGroupLoader.injectService();
if (mainServiceMap != null) {
sServiceClassMap.putAll(mainServiceMap);
}
GroupLoader mineGroupLoader = (GroupLoader) Class.forName("com.huweiqiang.mine.MineGroupLoader").newInstance();
Map<String, GroupLoader> mineModuleLoaderMap = mineGroupLoader.injectModule();
if (mineModuleLoaderMap != null) {
sModuleLoaderMap.putAll(mineModuleLoaderMap);
}
Map<String, Class<? extends IService>> mineServiceMap = mineGroupLoader.injectService();
if (mineServiceMap != null) {
sServiceClassMap.putAll(mineServiceMap);
}
GroupLoader accountGroupLoader = (GroupLoader) Class.forName("com.huweiqiang.account.AccountGroupLoader").newInstance();
Map<String, GroupLoader> accountModuleMap = accountGroupLoader.injectModule();
if (accountModuleMap != null) {
sModuleLoaderMap.putAll(accountModuleMap);
}
Map<String, Class<? extends IService>> accountServiceMap = accountGroupLoader.injectService();
if (accountServiceMap != null) {
sServiceClassMap.putAll(accountServiceMap);
}
} catch (Exception e) {
e.printStackTrace();
}
}
最后我們?cè)倏匆幌侣酚傻拇a
// 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
的實(shí)現(xiàn)
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 {
// 對(duì) service 進(jìn)行了緩存,但是不是所有的 service 都能緩存忍啤,所以這一塊需要進(jìn)一步優(yōu)化
sServiceMap.put(serviceName, sServiceClassMap.get(serviceName).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return sServiceMap.get(serviceName);
}
經(jīng)過上面的改進(jìn)我們的跨模塊通信框架已經(jīng)完善了很多加勤,但是還是有一個(gè)最大的問題沒有解決。從上面的代碼我們可以看到有很多無腦重復(fù)的代碼同波,例如 各個(gè) GroupLoader
和 ActivityLoader
鳄梅,如果要解決這個(gè)問題我們可以使用編譯時(shí)注解,這就是另外一個(gè)話題了未檩,在這里我只提供幾個(gè)關(guān)鍵字 APT
戴尸、 javapoet
總結(jié)
本文通過一個(gè)簡(jiǎn)單的實(shí)例實(shí)現(xiàn)了一個(gè)簡(jiǎn)易版的跨模塊通信機(jī)制,通過實(shí)現(xiàn)這個(gè)簡(jiǎn)易的跨模塊通信框架應(yīng)該對(duì)于跨模塊通信有了一個(gè)基本的認(rèn)識(shí)冤狡,再學(xué)習(xí)和使用一些完善的路由框架時(shí)也有了章法孙蒙。
示例代碼