人之所以能,是相信能.
一,前言
开始模块化开发项目之后,一个很重要的问题就是页面见的跳转问题.
关于模块化发开,可详见我的另一片文章 Android 模块化开发探索 .
正是由于将项目模块化拆分,各模块之间没有任何依赖关系,也互相不可见,那么从 A 模块的 a 界面跳转到 B 模块的 b 界面该怎么办呢?
二,跨模块跳转的方法
这里我们会先介绍这几种常见的跳转方法:
显示跳转
隐示跳转
Scheme 协议跳转
Router 路由表方案
2.1 显示跳转
显示跳转即我们最最常用的跳转方法:使用 Intent,传入当前 Activity 上下文,和目标 Activity 的 class 对象即可,如下:
显然,这种方法只能是目标 Activity 可见(Activity 在同一个 Module 下)的时候才可以这样调用.不适合跨模块间的跳转.
Intent intent = new Intent();
intent.setClass(mContext, GuideActivity.class);
startActivity(intent);
2.2 隐示跳转
我们这里说的隐示跳转,intent 不设置 class,而是设置 Action 或者 Category.
例如:
在清单文件中
<!--网页展示界面-->
跳转时:
<activity
android:name="com.whaty.base.BasewebViewActivity"
android:hardwareAccelerated="true">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.whaty.base.BaseWebViewActivity" />
</intent-filter>
</activity>
2.3 scheme 跳转
//创建一个隐式的 Intent 对象:Action 动作
Intent intent = new Intent();
//设置 Intent 的动作为清单中指定的action
intent.setAction("com.whaty.base.BaseWebViewActivity");
startActivity(intent);
如果我们为 B 页面定义一个 URI - wsc://home/bbb,然后把共享的 messageModel 拍平序列化成 Json 串,那么 A 只需要拼装一个符合 B 页面 scheme 的跳转协议就可以了. wsc://home/bbb?message={"name":"John", "age":31, "city":"New York"
在清单文件中,配置 data 属性,设置其 host,path,scheme 等
跳转时:
<activity android:name=".ui.BbbActivity"
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.VIEW" />
<data
android:host="bbb"
android:path="/home"
android:scheme="wsc" />
</intent-filter>
</activity>
以上的方法,都不是我们想要的,接下来开始介绍我们的 Router 方案.
final Uri uri = new Uri.Builder().authority("wsc").path("home/bbb").appendQueryParameter("message", new Gson().toJson(messageModel)).build();
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
startActivity(intent);
三,为什么要用 Router
Google 提供了显式和隐式两种原生路由方案.但在模块化开发中,显式 Intent 存在类直接依赖的问题,造成模块间严重耦合.隐式 Intent 则需要在 Manifest 中配置大量路径,导致难以拓展(如进行跳转拦截).为了解决以上问题,我们需要采用一套更为灵活的 Router 方案.
四,实现思路
思路是这样的:
使用注解,为每个目标 Activity 标注别名.在应用启动时,对所有类进行扫名,将注解过的 Activity 存于路由表中.
跳转时,在路由表中通过别名获取目标 Activity 的 class 对象,使用 Intent 实现跳转.
五,代码实现
5.1 自定义注解
/**
* Description: 路由跳转界面 注解
* Created by jia on 2018/1/10.
* 人之所以能,是相信能
关于自定义注解的详细介绍,请阅读我的文章 java 进阶之自定义注解 .这里不再多说.
*/
@Target(ElementType.TYPE) //注解作用于类型(类,接口,注解,枚举)
@Retention(RetentionPolicy.RUNTIME) //运行时保留,运行中可以处理
@Documented // 生成javadoc文件
public@interface Action {
String DEFAULT = "js";
String value()
default DEFAULT;
}
5.2 注解 Activity
在创建 Activity 时,用刚刚自定义的注解进行注解,为其注释别名.
@Action("MainActivity")
public class MainActivity extends BaseActivity implements TabLayout.OnTabSelectedListener {
...
}
5.3 启动时扫描
在应用启动时,Application 中对包下的所有类进行扫描,先找到名字中到 activity 的(定义到 activity 包下),并将带有注解标注的 Activity,存入 map 中.
private void getAllActivities(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") && entryName.contains("Activity")) {
//通过反射获得Activity类
Class entryClass = Class.forName(entryName);
if (entryClass.isAnnotationPresent(Action.class)) {
Action action = (Action) entryClass.getAnnotation(Action.class);
this.map.put(action.value(), entryClass);
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
5.4 跳转
/**
* 页面跳转
跳转的时候传入目标 Activity 的别名即可(这里的别名就是注解的别名).
* @param activity
* @param alias
*/
public void jumpActivity(Activity activity, String alias) throws ClassNotFoundException {
if (map.containsKey(alias)) {
Intent intent = new Intent(activity, map.get(alias));
activity.startActivity(intent);
} else {
throw new ClassNotFoundException();
}
}
总结
通过这种方式,解决了跳转 Activity 所产生的的模块依赖问题,相较于原生方案,拓展性更强.但这种方案只是阶段性的,还存在一些问题.首先,加载过程中,频繁使用到反射,会产生性能问题.其次,对于每个 Activity 的别名,需要进行统一维护,增加了协作成本.还有待优化.
当然,市面上有很多流行的 Router 方案(如阿里的 ARouter),这里只是介绍了一个思路,有好的建议欢迎交流,一起进步.
来源: https://juejin.im/post/5a55c969518825692262f1ec