-
Notifications
You must be signed in to change notification settings - Fork 132
Navigator
路由可用于处理页面跳转
- 支持
Activity
和Fragment
- 支持
path
与页面多对一关系或一对一关系 - 页面
Path
支持正则表达式声明 - 支持
json
格式路由表导出 - 支持动态下发
json
路由表 - 支持任意
object
跨模块传递(无需序列化,且能保证对象类型) - 支持页面跳转拦截处理
- 支持自定义页面参数解析方式(例如将json解析为对象)
- 支持使用路由跳转到第三方SDK中的
Activity(Fragment)
如果一个页面(支持 Activity、Fragment)允许被路由打开,则需要使用注解 @Route 声明路由项,每个页面允许声明多个路由项,也就是一对多的能力,极大降低多端路由统一时的业务影响面。
参数释义
-
path: 路由path 【必传】。
建议是一个url。path内支持使用正则表达式(为了匹配效率,正则必须包含反双斜杠\),允许多个path对应同一个Activity(Fragment)。 - action: 自定义事件【可选】。 一般用来打开目标页面后做一个执行动作,例如自定义页面弹出广告弹窗
- description: 页面描述【可选】。 会被记录到路由表中,方便后期排查的时候知道每个path或Activity是什么业务
- params: 页面参数【可选】。 自动写入intent中,允许写在路由表中动态下发修改默认值,或通过路由跳转时代码传入。
@Route(path = "http://therouter.com/home", action = "action://scheme.com",
description = "第二个页面", params = {"hello", "world"})
public class HomeActivity extends AppCompatActivity {
}
如果两条路由的path、目标className完全相同,则认为是同一条路由,不会考虑参数是否相同。
路由表生成规则:编译期按照如下顺序取并集。
覆盖规则:根据如下顺序,如果相同,后者可以覆盖前者的路由表规则。
- 编译期解析注解生成路由表
- 首先取
业务模块 aar
中的路由表 - 再取
主app module
代码中的路由表 - 最后取
assets/RouteMap.json
文件中声明的路由表。
- 如果编译期没有这个文件,会生成一份默认路由表放在这个目录内;如果有,会将路由表合并
- 路由表生成时可配置是否启用检查路由合法性,判断目标页面是否存在,(warning/error)级别
- 运行时线上动态下发的路由表
- 路由表允许线上动态下发,将覆盖本地路由表,详见 【1.3 远端下发路由表】
TheRouter项目每次编译后,会在apk内生成一份路由表,默认路径为:/assets/therouter/routeMap.json
同时这份路由表也支持远端动态下发,例如远端可以针对不同的APP版本,下发不同的路由表达到配置目的。因此有两种推荐的方式可供使用方选择:
- 将打包系统与配置系统打通,每次新版本APP打包后自动将assets/目录中的配置文件上传到配置系统,下发给对应版本APP 。优点在于全自动不会出错。
- 配置系统无法打通,线上手动下发需要修改的路由项,因为 TheRouter 会自动用最新下发的路由项覆盖包内的路由项。优点在于精确,且流量资源占用小。
注:一旦你设置了自定义的InitTask,原框架内路由表初始化任务将不再执行,你需要自己处理找不到路由表时的兜底逻辑,一种建议的处理方式见如下代码。
// 此代码 必须 在 Application.super.onCreate() 之前调用
RouteMap.setInitTask(new RouterMapInitTask() {
/**
* 此方法执行在异步
*/
@Override
public void asyncInitRouteMap() {
// 此处为纯业务逻辑,每家公司远端配置方案可能都不一样
// 不建议每次都请求网络,否则请求网络的过程中,路由表是空的,可能造成APP无法跳转页面
// 最好是优先加载本地,然后开异步线程加载远端配置
String json = Connfig.doHttp("routeMap");
// 建议加一个判断,如果远端配置拉取失败,使用包内配置做兜底方案,否则可能造成路由表异常
if (!TextUtils.isEmpty(json)) {
List<RouteItem> list = new Gson().fromJson(json, new TypeToken<List<RouteItem>>() {
}.getType());
// 建议远端下发路由表差异部分,用远端包覆盖本地更合理
RouteMap.addRouteMap(list);
} else {
// 在异步执行TheRouter内部兜底路由表
initRouteMap()
}
}
});
共有两种方式
- 配置文件添加:在本地 assets/RouteMap.json 文件中声明第三方页面的路由表。
- 代码运行时添加:
/**
* @params path 路由Path
* @params className 第三方页面的Activity或Fragment完整类名
* @params action 要向页面传入的动作,如果没有传空字符串
* @params description 第三方页面的文档描述
*/
addRouteItem(RouteItem(path, className, action, description))
路由表中如果声明了一个不存在的Activity,通常是因为旧代码删除时忘记清理路由表了,这里可选报错或警告,提供参考。
可在local.properties
配置编译期属性(warning输出日志,error直接报错)
- 路由表中声明了不存在的
Activity
检查CHECK_ROUTE_MAP=warning
warning/error可选
传入的参数可以是 String
和8种基本数据类型、也可以是Bundle
、Serializable
、
Parcelable
对象,跟 Intent
传值规则一致。
同时也支持为本次跳转的 Intent
添加Flag/Uri/ClipData/identifier
等业务特殊参数。
// 传入参数可以通过注解 @Autowired 解析成任意类型,如果是对象建议传json
// context 参数如果不传或传 null,会自动使用 application 替换
TheRouter.build("http://therouter.com/home")
.withInt("key1", 12345678)
.withString("key2", "参数")
.withBoolean("key3", false)
.withSerializable("key4", object)
.withObject("object", any) // 与上一个方法不同,这个方法可以传递任意对象,但是接收的地方对象类型请保证一致
.navigation(context);
// 如果传入 requestCode,默认使用startActivityForResult启动Activity
.navigation(context, 123);
// 如果要打开的是fragment,需要使用
.createFragment();
延迟跳转主要应用场景有两种:
- 第一种:初始化时期,如果路由表的量非常巨大时。这种情况在别的路由框架上要么会白屏一段时间,要么直接丢弃这次跳转。在TheRouter中,框架会暂存当前的跳转动作,在路由表初始化完成后立刻执行跳转。
- 第二种:从Android 8.0开始,Activity 不能在后台启动页面,这对于业务判断造成了很大的影响。由于可能会有前台 Service 的情况,不能单纯以 Activity 生命周期判断前后台。在TheRouter中,框架允许业务自定义前后台规则,如果为后台情况,可以将跳转动作暂存,当进入前台后再恢复跳转。
// 暂存的动作可以有多个,会在恢复时按顺序执行
TheRouter.build("http://therouter.com/home")
.withInt("key1", 12345678)
.padding()// 暂存当前跳转动作
.navigation(context);
// 恢复
sendPendingNavigator(); //toplevel方法,无需类名调用,Java请通过NavigatorKt类名调用
使用注解接收对象时,必须调用TheRouter.inject(this);建议直接在 Base 中统一调用。
接收有两种形式:
- 通过注解自动接收,默认支持 String 和8种基本数据类型,也支持自定义对象的解析
- 通过代码从 intent 中获取
@Route(path = "http://therouter.com/home")
public class HomeActivity extends BaseActivity {
// 允许解析成8种基本数据类型或对应封装类
@Autowired
int key1;
// String类型可以自动解析,除了String和8种基本类型封装类以外,
// 其他的Object需要自定义解析规则,自定义方式见下文高级功能介绍 2.3 节内容
@Autowired
String key2;
// 如果没有声明 name,则使用变量名作为 Intent 解析的key
@Autowired(name = "key3")
String user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 使用注解接收对象时,必须调用这一句,建议直接在 Base 中统一调用。
TheRouter.inject(this);
// 当然如果不嫌麻烦,也是允许自己手动解析的,但是只能解析成String,因为传的时候都是以String类型传递
String key1 = activity.getIntent().getStringExtra("key1");
}
}
页面跳转间如果要对象,有两种方式:
- 第一种:先将对象序列化为json,将json传递以后,自定义解析器反序列化为对象,参考
自定义 @Autowired 解析
- 第二种:直接传递,在直接传递时需要注意对象类型,接收的地方不要出现强转错误
对象参数只能使用注解接收。使用注解接收对象时,必须调用
TheRouter.inject(this);
建议直接在Base
中统一调用。
TheRouter.build("http://therouter.com/home")
.withString("user_info_object", json) // 传递json对象
.withObject("user_object", any) // 这个方法可以传递任意对象,但是接收的地方对象类型请保证一致
.navigation(context);
@Route(path = "http://therouter.com/home")
public class HomeActivity extends BaseActivity {
@Autowired
User user_info_object;
@Autowired
User user_object;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 使用注解接收对象时,必须调用这一句,建议直接在 Base 中统一调用。
TheRouter.inject(this);
}
}
页面跳转间如果要对象,需要先将对象序列化为json,将json传递以后再反序列化为对象
通过 @Autowired
注解可以自定义将 json 解析为对象注入
TheRouter.addAutowiredParser(new MyAutowiredParser());
具体Parser类
定义可参考代码DefaultUrlParser
类
框架内置四种自定义处理器可供业务场景定制,用于在路由跳转过程中,以切面的方式统一修改路由落地页参数信息。
应用场景:用于修复客户端上路由 path 错误问题。
例如:相对路径转绝对路径,或由于服务端下发的链接无法固定https或http,但客户端代码写死了 https 的 path,就可以用这种方式统一。
注:必须在 TheRouter.build() 方法调用前添加处理器,否则处理器前的所有path不会被修改。
Navigator.addNavigatorPathFixHandle(new NavigatorPathFixHandle() {
@Override
public String fix(String path) {
path = path.replace("http://", "https://");
if (!path.contains("www.kymjs.com") && path.startsWith("/")) {
path = "https://www.kymjs.com" + path;
}
return path;
}
/**
* 数字越大,优先级越高,
* 优先级方法通常情况下不需要重写,除非你真的有什么特殊场景会用到
*/
@Override
public int getPriority() {
return 5;
}
});
应用场景:需要将某些path
指定为新链接的时候使用。 也可以用在修复链接的场景,但是与 path 修改器
不同的是,修改器通常是为了解决通用性的问题,替换器只在页面跳转时才会生效,更多是用来解决特性问题。
例如模块化的时候,首页壳模板组件中开发了一个SplashActivity
广告组件作为应用的MainActivity
,在闪屏广告结束的时候自动跳转业务首页页面。
但是每个业务不同,首页页面的 Path
也不相同,而不希望让每个业务线自己去改这个首页壳模板组件,此时就可以组件中先写占位符https://kymjs.com/splash/to/home
,让接入方通过 Path
替换器解决。
注:必须在 TheRouter.build().navigation() 方法调用前添加处理器,否则处理器前的所有跳转不会被替换。
Navigator.addPathReplaceInterceptor(new PathReplaceInterceptor() {
@Override
public String replace(String path) {
if ("https://kymjs.com/splash/to/home".equals(path)) {
return "https://kymjs.com/business/home";
}
return path;
}
});
应用场景:常用在未登录不能使用的页面上。例如访问用户钱包页面,在钱包页声明的时候,可以在路由表上声明本页面是需要登录的,在路由跳转过程中,如果落地页是需要登录的,则先替换路由到登录页,同时将原落地页信息作为参数传给登录页,登录流程处理完成后可以继续执行之前的路由操作。
路由替换器的拦截点更靠后,主要用于框架已经从路由表中根据 path 找到路由以后,对找到的路由做操作。
这种逻辑在所有页面跳转前写不太合适,以前的做法通常是在落地页写逻辑判断用户是否具有权限,但其实在路由层完成更合适。
注:必须在 TheRouter.build().navigation() 方法调用前添加处理器,否则处理器前的所有跳转不会被替换。
Navigator.addRouterReplaceInterceptor(new RouterReplaceInterceptor() {
@Override
public RouteItem replace(RouteItem routeItem) {
if (user.age() < 18 && routeItem.getClassName().contains("ChildrenProhibitActivity")) {
RouteItem target = new RouteItem();
target.setClassName(HomeActivity.class.getName());
String[] path = {"https://kymjs.com/too/young"};
target.setPathArray(path);
target.setDescription("也可以在这里修改原有路由的参数信息");
return target;
}
return routeItem;
}
});
与前三个处理器不同的点在于,路由的AOP拦截器全局只能有一个。用于实现AOP的能力,在整个TheRouter跳转的过程中,跳转前,目标页是否找到的回调,跳转时,跳转后,都可以做一些自定义的逻辑处理。
使用场景:场景很多,最常用的是可以拦截一些跳转,例如debug页面在生产环境不打开,或定制startActivity
跳转方法。
Navigator.setRouterInterceptor(new RouterInterceptor() {
@Override
public void process(RouteItem routeItem, InterceptorCallback callback) {
if (!TheRouter.isDebug() && routeItem.getClassName().equals("com.kymjs.DebugActivity")) {
return;
} else {
// callback 对象也是可以自定义的,看你怎么用了
callback.onContinue(routeItem);
}
}
});
如果使用TheRouter
跳转,传入了一个不识别的的path
,则不会有任何处理。你也可以定义一个默认的全局回调,来处理跳转情况,如果落地页是 Fragment
则不会回调。
当然,跳转结果的回调不止这一个用途,可以根据业务有自己的处理。
回调也可以单独为某一次跳转设置,navigation()
方法有重载可以传入设置。
NavigatorKt.defaultNavigationCallback(new NavigationCallback() {
// 落地页Activity打开后,执行到onCreate会回调
@Override
public void onActivityCreated(@NonNull Navigator navigator, @NonNull Activity activity) {
super.onActivityCreated(navigator, activity);
}
// startActivity执行后会立刻回调
@Override
public void onArrival(@NonNull Navigator navigator) {
super.onArrival(navigator);
}
// 找到待跳转的落地页时就会回调(startActivity之前)
@Override
public void onFound(@NonNull Navigator navigator) {
super.onFound(navigator);
}
// 找不到落地页的时候会回调
@Override
public void onLost(@NonNull Navigator navigator) {
super.onLost(navigator);
}
});
如果返回为空,表示当前url不是路由表内的path
// kotlin toplevel方法,Java调用请使用RouteMapKt类
matchRouteMap("url填这里") == null