前面我们使用APT+JavaPoet完成了组件化路由框架搭建
即:
1:通过添加自定义注解ARouter,通过对应的注解处理器动态扫描含有ARouter注解的Activity
2:在注解处理器中处理获取的含有ARouter注解的Activity,通过JavaPoet生成对应的Group路由和Path路由
3:通过动态添加注解Parameter,通过对应的注解处理器动态臊面含有Parameter注解的属性以及对应的Activity,完成传参
这节主要解决,模块一,模块二之间没有任何依赖,但是可以通信。
开始之前我们先带着几个疑问:
1:之前我们已经生成好了带有ARouter的Activity到 “com/netease/modular/apt” 下,那我们要跳转的话直接从这个包下面去找对应的Class类就可以了。那么如何去找?
2:之前章节中的跳转是build后,都是类似于通过下面的方式,我们知道,这样在编译的时候也会出现问题,所以我们该怎么优化呢?
public void jump(View view) {
Intent intent = new Intent(this, OrderActivity$$ARouter.findTargetClass("/app/OrderActivity"));
startActivity(intent);
}
构思:
因为我们在Activity创建的时候,在类上添加了自定义的注解:@ARouter(path = "/order/Order_MainActivity") 类似于这样,这个也是ARouterLoadPath路由中的key,我们可以通过这个key找到我们需要的class文件,最终完成跳转。
也就是通过 RouterManager将"/order/Order_MainActivity"传到RouterManager里面,在这个里面解析并查找具体的class文件
开始撸码
我们创建一个路由管理类(单例模式的路由管理类)
路由加载管理器 RouterManager
新建一个路由管理器,我们做Activity跳转的时候,就要通过这个类去传递
/**
* 路由加载管理器
*/
public final class RouterManager {
// 路由组名
private String group;
// 路由详细路径
private String path;
private static RouterManager instance;
// 单例方式,全局唯一
public static RouterManager getInstance() {
if (instance == null) {
synchronized (RouterManager.class) {
if (instance == null) {
instance = new RouterManager();
}
}
}
return instance;
}
}
因为是管理类,那么就要去“com/netease/modular/apt” 包下,将所有生成的Gruop组路由和Path路由都查出来,放到集合中,然后再从集合中取。
所以为了性能考虑 我们先建两个有缓存功能的Map集合,因 ARouter$$Path$$app 和 ARouter$$Group$$app 都是固定写法,只有最后一个$$符号后有变化,所以取最后一个$$符号后的数据为key,他们的父类ARouterLoadGroup 和ARouterLoadPath 为值
// Lru缓存,key:类名, value:路由组Group加载接口
private LruCache groupCache;
// Lru缓存,key:类名, value:路由组Group对应的详细Path加载接口
private LruCache pathCache;
// APT生成的路由组Group源文件前缀名 为了方便查找 现将前面不变的字符串提取出来,方便后面拼接
private static final String GROUP_FILE_PREFIX_NAME = ".ARouter$$Group$$";
private RouterManager() {
// 初始化,并赋值缓存中条目的最大值(最多163组)
groupCache = new LruCache<>(163);
// 每组最多163条路径值
pathCache = new LruCache<>(163);
}
为了遵循设计模式的单一原则,我们尽量一个类只负责一个功能,当前的这个RouterManager负责从集合中找到对应了路径的Activity跳转,我们需要新建一个传参的管理器。因传参管理器也是跳转的一部分,所以我们采用建造者模式(builder模式)将复杂对象使用此模式分解。
即BundleManager
/**
* Bundle拼接参数管理类
*/
public final class BundleManager {
private Bundle bundle = new Bundle();
// 底层业务接口
private Call call;
// 是否回调setResult()
private boolean isResult;
Bundle getBundle() {
return bundle;
}
Call getCall() {
return call;
}
void setCall(Call call) {
this.call = call;
}
boolean isResult() {
return isResult;
}
// @NonNull不允许传null,@Nullable可以传null
public BundleManager withString(@NonNull String key, @Nullable String value) {
bundle.putString(key, value);
return this;
}
// 示例代码,需要拓展
public BundleManager withResultString(@NonNull String key, @Nullable String value) {
bundle.putString(key, value);
isResult = true;
return this;
}
public BundleManager withBoolean(@NonNull String key, boolean value) {
bundle.putBoolean(key, value);
return this;
}
public BundleManager withInt(@NonNull String key, int value) {
bundle.putInt(key, value);
return this;
}
public BundleManager withBundle(@NonNull Bundle bundle) {
this.bundle = bundle;
return this;
}
public Object navigation(Context context) {
return RouterManager.getInstance().navigation(context, this, -1);
}
// 这里的code,可能是requestCode,也可能是resultCode。取决于isResult
public Object navigation(Context context, int code) {
return RouterManager.getInstance().navigation(context, this, code);
}
}
上面的重点是:我们将具体的跳转操作(也就是路由)交回给RouterManager去处理。
/**
* 开始跳转
*
* @param context 上下文
* @param bundleManager Bundle拼接参数管理类
* @param code 这里的code,可能是requestCode,也可能是resultCode。取决于isResult
* @return 普通跳转可以忽略,用于跨模块CALL接口
*/
Object navigation(@NonNull Context context, BundleManager bundleManager, int code) {
// 精华:阿里的路由path随意写,导致无法找到随意拼接APT生成的源文件,如:ARouter$$Group$$abc
// 找不到,就加载私有目录下apk中的所有dex并遍历,获得所有包名为xxx的类。并开启了线程池工作
// 这里的优化是:代码规范写法,准确定位ARouter$$Group$$app
//前面为Group路由的固定写法,group变量为初始化RouterManager动态截取的
String groupClassName = context.getPackageName() + ".apt" + GROUP_FILE_PREFIX_NAME + group;
Log.e("netease >>> ", "groupClassName -> " + groupClassName);
try {
//返回group组的Map集合( Map> )
ARouterLoadGroup groupLoad = groupCache.get(group);
//取出来的数据类似于:ARouter$$Group$$app
//如果集合不为空,说明获取过这个路径的信息
if (groupLoad == null) {
Class> clazz = Class.forName(groupClassName);
groupLoad = (ARouterLoadGroup) clazz.newInstance();
//groupLoad ==ARouter$$Group$$app.Class
//将所有
//集合里面为空 说明之前没有获取过这个信息 那么就把信息加入到集合中。 方便二次获取
groupCache.put(group, groupLoad);
}
// 获取路由路径类ARouter$$Path$$app
if (groupLoad.loadGroup().isEmpty()) {
throw new RuntimeException("路由加载失败");
}
ARouterLoadPath pathLoad = pathCache.get(path);
//返回的信息 Map
if (pathLoad == null) {
Class extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
if (clazz != null) pathLoad = clazz.newInstance();
if (pathLoad != null) pathCache.put(path, pathLoad);
}
if (pathLoad != null) {
// tempMap赋值
pathLoad.loadPath();
if (pathLoad.loadPath().isEmpty()) {
throw new RuntimeException("路由路径加载失败");
}
//根据详细的路径获取具体的RouterBean
RouterBean routerBean = pathLoad.loadPath().get(path);
if (routerBean != null) {
switch (routerBean.getType()) {
case ACTIVITY:
Intent intent = new Intent(context, routerBean.getClazz());
intent.putExtras(bundleManager.getBundle());
// startActivityForResult -> setResult
if (bundleManager.isResult()) {
((Activity) context).setResult(code, intent);
((Activity) context).finish();
}
if (code > 0) { // 跳转时是否回调
((Activity) context).startActivityForResult(intent, code, bundleManager.getBundle());
} else {
context.startActivity(intent, bundleManager.getBundle());
}
break;
case CALL:
Class> clazz = routerBean.getClazz();
Call call = (Call) clazz.newInstance();
bundleManager.setCall(call);
return bundleManager.getCall();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
总结一下:
以person为例:
1:查找Arouter$$Group$$personal ( 通过 包名+ 拼接 + 类名) 类加载
2:Arouter$$Group$$personal执行loadGroup
3:执行loadGroup return groupMap
4:执行groupMap.get(path)
5:根据上面的执行,即拿到了Arouter$$Paht$$personal.class
6:执行Arouter$$Paht$$personal的loadPath
7:pathMap.get(path)
8:通过上面的执行,拿到了RouterBean
9:拿到RouterBean里面的Personal_MainActivity.class
10:完成跳转
下面我们开始完成跳转
public void jumpApp(View view) {
RouterManager.getInstance()
.build("/app/MainActivity")
.withResultString("call", "I'am comeback!")
.navigation(this);
}
public void jumpPersonal(View view) {
RouterManager.getInstance()
.build("/personal/Personal_MainActivity")
.withString("name", "simon")
.navigation(this);
}
;