在引入多模块开发后,我们首先要解决的就是模块间页面跳转的问题。本文意在提供一种思路而非框架。
为什么要使用Router
我们知道,Google SDK提供了显式和隐式两种原生路由方案。但在模块化开发中,显式Intent存在类直接依赖的问题,造成模块间严重耦合。隐式Intent则需要在Manifest中配置大量路径,导致难以拓展(如进行跳转拦截)。为了解决以上问题,我们需要采用一套更为灵活的Router方案。
方案思路
我们的思路很简单,使用注解,为每个activity类标注别名。在启动时对类进行扫描,将带有注解的activity存放路由表中。整个过程被我们封装在ActionManager
类中,并对外暴露startAction(String alias ,Bundle data)
接口,跳转时通过别名在路由表中进行匹配,完成跳转。大致流程如下:
代码实现
以下是我截取的代码片段,方便大家理解,
- 在创建Activity时,通过注解,为其注释别名:
@Action("loginActivity ")
public class LoginActivity extends BaseBase{
//代码省略...
}
- 在启动时(Application类中),对包下的所有类进行扫描,将带有注解标注的Activity,存入map,代码如下:
private void activityScan(Context ctx) {
try {
//通过资源路径获得DexFile
DexFile e = new DexFile(ctx.getPackageResourcePath());
Enumeration entries = e.entries();
//遍历所有元素
while(entries.hasMoreElements()) {
String entryName = (String)entries.nextElement();
//匹配Activity包名
if(entryName.contains("activity")) {
//通过反射获得Activity类
Class entryClass = Class.forName(entryName);
if(entryClass.isAnnotationPresent(Action.class)) {
Action action = (Action)entryClass.getAnnotation(Action.class);
this.mapping.put(action.value(), entryClass.getName());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 对外暴露接口,提供根据别名跳转Activity的方法:
ActionManager.getInstance().startAction(currentActivity.this ,"loginActivity ");
我们可以请求方法中做一些拦截处理,同样可以通过Bundle传输数据。以下是startAction方法的实现:
public void startAction(Activity original, String alias) throws ClassNotFoundException {
if(this.mapping.containsKey(alias)) {
Intent intent = new Intent(original, Class.forName((String)this.mapping.get(alias)));
original.startActivity(intent);
} else {
throw new ClassNotFoundException();
}
}
我们通过这种方式,解决了跳转Activity所产生的的模块依赖问题,相较于原生方案,拓展性更强。但这种方案只是阶段性的,还存在一些问题。首先,加载过程中,频繁使用到反射,会产生性能问题。其次,对于每个Activity的别名,需要进行统一维护,增加了协作成本。对此,我们正在尝试使用APT工具改进,目的是让扫描过程在编译期完成,避免运行时加载。
期望
目前市场上有不少Router框架,秉承不重复造轮子的原则,我们可以在项目中直接使用。但如开篇所述,我们意在提供一种思路,记录演进过程,框架是别人的,思路是自己的,只有这样才能形成对自己有益的技术栈。