前言
随着app的业务越来越复杂,如果不分模块的话,维护性和扩展性简直惨不忍睹,举个栗子,项目有登录模块和用户模块,业务需求是登录之后展示用户信息,这个时候登录模块是拿不到用户模块任何类的引用的,可能你会说那就引入进来不就好了,对于小的项目还好说,对于大的项目,如果各个模块互相引用,那么模块化就没有意义了,同级的模块互相引用导致,有时候需求只需要更改一个模块的东西,但是因为耦合,却要编译好多模块,亦可赛艇。所以这就出现了路由。来看一张图:
现在的需求是从loginLibrary中的登录界面跳转到userInfoLibrary的用户信息界面,用于loginLibrary和userInfoLibrary互相不可见因此不能直接跳转,这个时候我们注意到loginLibrary和userInfoLibrary都引用了commmLib模块,那我就拿这个做文章,把commmLib当做桥梁,在loginLibrary和userInfoLibrary之间建立联系,这是典型的桥接模式。这种模式在现实生活中很常见,比如房产中介。这就安卓的路由,下面我们分三种方式来实现安卓的路由:
第一种:隐式的Intent
我们知道安卓中activity之间的跳转,除了startActivity(intent)这种显式的跳转之外,还可以再AndroidManifest.xml中通过intentFilter的配置,通过匹配规则来实现跳转:
看下代码:
/**
* 通过action实现的路由
*/
private void jumpByAction() {
Intent intent = new Intent();
intent.setAction(ActionConstant.ACTION_USER_INFO);
intent.putExtra(ActionConstant.KEY_USER_NAME, mEtUserName.getText().toString());
intent.putExtra(ActionConstant.KEY_PASS_WORD, mEtPwd.getText().toString());
if (ActionConstant.isIntentAvailable(this, intent)) {
startActivity(intent);
}
}
/**
* 通过Schema实现的路由
*/
private void jumpByScheme() {
Intent intent = new Intent();
intent.setAction(ActionConstant.ACTION_USER_INFO);
intent.putExtra(ActionConstant.KEY_USER_NAME, mEtUserName.getText().toString());
intent.putExtra(ActionConstant.KEY_PASS_WORD, mEtPwd.getText().toString());
intent.setData(Uri.parse("http://www.baidu.com:80/login"));
intent.getCategories();
if (ActionConstant.isIntentAvailable(this, intent)) {
startActivity(intent);
}
}复制代码
这里主要实现了通过Action和Scheme的隐式跳转,注意的是通过隐式跳转的Activity注册的时候一定要加上<category android:name="android.intent.category.DEFAULT" />否则奔溃如下:
Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.USERINFO (has extras) }复制代码
第二种:通过初始化路由表的方式实现
public class RouterManagerByMap {
private static RouterManagerByMap instance = new RouterManagerByMap();
private ArrayMap<String, Class> routers = new ArrayMap<>();
private RouterManagerByMap() {
}
public static RouterManagerByMap getInstance() {
return instance;
}
public void addRouter(String key, Class activity) {
routers.put(key, activity);
}
public Class getRouter(String key) {
return routers.get(key);
}
}复制代码
RouterManagerByMap维护了路由表,在RouterApp初始化的时候将需要跳转的Activity以KeyValue的形式,存起来,如下:
RouterManagerByMap.getInstance().addRouter(ActionConstant.ACTION_USER_INFO, UserInfoActivity.class);复制代码
这要的话使用的时候
RouterManagerByMap.getInstance().getRouter(ActionConstant.ACTION_USER_INFO)
就可以获取到对应的要跳转的类了。
第三种:通过注解
其实第三种和第二种差不多,只不过初始化理由表的实现方式不一样,是通过注解实现的,来看下初始化代码:
public class RouterByAnnotationManager {
private static RouterByAnnotationManager instance = new RouterByAnnotationManager();
private ArrayMap<String, Class> routers = new ArrayMap<>();
private RouterByAnnotationManager() {
}
public static RouterByAnnotationManager getInstance() {
return instance;
}
private void addRouter(String key, Class activity) {
routers.put(key, activity);
}
public Class getRouter(String key) {
return routers.get(key);
}
public void init(Application application) {
try {
//通过资源路径获得DexFile,注意5.0以上版本要求关掉instant run 方法否则会自动拆包遍历不到所有的类
DexFile e = new DexFile(application.getPackageCodePath());
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(RouterTarget.class)) {
RouterTarget action = (RouterTarget) entryClass.getAnnotation(RouterTarget.class);
RouterByAnnotationManager.getInstance().addRouter(action.value(), entryClass);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}复制代码
注意的是:通过资源路径获得DexFile,注意5.0以上版本要求关掉instant run 方法否则会自动拆包遍历不到所需要activity类
这里定义一个运行时注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface RouterTarget {
String value();
}复制代码
对注解不是很熟悉的朋友可以先去网上看看,用法如下图:
怎么样是不是很方便。
总结
以上三种方式都可以实现路由的跳转,但是第一种由于安卓不支持在代码里面设置Activity的intentFilter,所以造成一个问题:比如药跳转的UserInfoActivity的action是“android.intent.action.USERINFO”为了能够匹配setAction()和UserInfoActivity的AndroidManifest.xml中的要严格匹配,而安卓不支持在代码里面设置Activity的intentFilter,所以不能通过ActionConstant来统一维护,小项目还好,一旦项目大起来,那么就很难保证匹配。第三种看起来使用起来很方便,但是有两个问题:
1.通过遍历反射射包下所有的类,效率低下;
2.注意到DexFile已经打上了废弃标志,不出意外接下来的安卓版本可能会不支持,有风险
所以建议使用第二种。代码地址