页面跳转能力介绍


路由可用于处理页面跳转

1.0 路由表

1.1 声明路由项

如果一个页面(支持 Activity、Fragment)允许被路由打开,则需要使用注解 @Route 声明路由项,每个页面允许声明多个路由项,也就是一对多的能力,极大降低多端路由统一时的业务影响面。

参数释义

@Route(path = "http://therouter.com/home", action = "action://scheme.com",
        description = "第二个页面", params = {"hello", "world", "a", "b"})
public class HomeActivity extends AppCompatActivity {
}

1.2 路由表生成

1.2.1 路由表覆盖规则

如果两条路由的path、目标className完全相同,则认为是同一条路由,不会考虑参数是否相同

1.2.2 RouteMap.json 生成规则

RouteMap.json文件是TheRouter路由表的核心。他有两个作用:

如果以上两个能力你都不需要,那么你可以直接忽略这个 json 文件的存在,即便删除也没事,因为每次编译这个文件都会重新生成。而如果你没有删除,也没有关系,在编译期框架会自动合并相同项目,合并规则遵循 【1.2.1 路由表覆盖规则】

如果你用到了上面的能力,建议你将编译期配置项打开,并设置到error级别,详见【1.5 编译期配置】

1.2.3 路由表生成规则

编译期按照如下顺序取并集
覆盖规则:根据如下顺序,如果相同,后者可以覆盖前者的路由表规则。

  1. 编译期解析注解生成路由表
  2. 首先取 业务模块 aar 中的路由表
  3. 再取 主app module 代码中的路由表
  4. 最后取 assets/RouteMap.json 文件中声明的路由表。
    • 如果编译期没有这个文件,会生成一份默认路由表放在这个目录内;如果有,会将路由表合并
    • 路由表生成时可配置是否启用检查路由合法性,判断目标页面是否存在,(warning/error/delete)级别
  5. 运行时线上动态下发的路由表
    • 路由表允许线上动态下发,将覆盖本地路由表,详见 【1.3 远端下发路由表】

1.3 远端下发路由表

TheRouter项目每次编译后,会在apk内生成一份路由表,默认路径为:/assets/therouter/routeMap.json

同时这份路由表也支持远端动态下发,例如远端可以针对不同的APP版本,下发不同的路由表达到配置目的。因此有两种推荐的方式可供使用方选择:

  1. 将打包系统与配置系统打通,每次新版本APP打包后自动将assets/目录中的配置文件上传到配置系统,下发给对应版本APP 。优点在于全自动不会出错。
  2. 配置系统无法打通,线上手动下发需要修改的路由项,因为 TheRouter 会自动用最新下发的路由项覆盖包内的路由项。优点在于精确,且流量资源占用小。

注:一旦你设置了自定义的InitTask,原框架内路由表初始化任务将不再执行,你需要自己处理找不到路由表时的兜底逻辑,一种建议的处理方式见如下代码。

// 此代码 必须 在 Application.super.onCreate() 之前调用
RouteMapKt.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()
        }
    }
});

1.4 为第三方库添加路由表

共有两种方式

  1. 配置文件添加:在本地 assets/RouteMap.json 文件中声明第三方页面的路由表。
  2. 代码运行时添加:
/**
 * @params path 路由Path
 * @params className 第三方页面的Activity或Fragment完整类名
 * @params action 要向页面传入的动作,如果没有传空字符串
 * @params description  第三方页面的文档描述
 */
addRouteItem(RouteItem(path, className, action, description))

1.5 编译期配置

1.2.3-rc1版本开始,编译器配置已经迁移到build.gradle中,具体请查看:1.2.3版本编译改动

路由表中如果声明了一个不存在的Activity/Fragment,通常是因为旧代码删除,或重构改了包名类名时,忘记清理路由表了,这里可选报错或警告,提供参考。
可在local.properties配置编译期属性(warning输出日志,error直接报错)

2.0 页面跳转使用方式

2.1 路由跳转

传入的参数可以是 String 和8种基本数据类型、也可以是BundleSerializableParcelable对象,跟 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();
        
        // 回调的方式打开页面
        .navigation(v.getContext(), new NavigationCallback() {
            @Override
            public void onFound(@NotNull Navigator navigator) {
                super.onFound(navigator);
                Toast.makeText(v.getContext(), "找到页面,即将打开" + navigator.getUrl(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onLost(@NotNull Navigator navigator) {
                super.onLost(navigator);
                Toast.makeText(v.getContext(), "丢失页面" + navigator.getUrl(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onArrival(@NotNull Navigator navigator) {
                super.onArrival(navigator);
                Toast.makeText(v.getContext(), "已打开" + navigator.getUrl(), Toast.LENGTH_SHORT).show();
            }
        });

2.2 延迟路由跳转

延迟跳转主要应用场景有两种:

// 暂存的动作可以有多个,会在恢复时按顺序执行
TheRouter.build("http://therouter.com/home")
        .withInt("key1", 12345678)
        .pending()// 暂存当前跳转动作
        .navigation(context);
        
// 恢复
sendPendingNavigator(); //toplevel方法,无需类名调用,Java请通过NavigatorKt类名调用

2.3 路由参数接收

使用注解接收对象时,必须调用TheRouter.inject(this);建议直接在 Base 中统一调用。
接收有两种形式:

@Route(path = "http://therouter.com/home")
public class HomeActivity extends BaseActivity {
    // 允许解析成8种基本数据类型或对应封装类
    @Autowired
    int key1;

    // String类型可以自动解析,除了String和8种基本类型封装类以外,
    // 其他的Object需要自定义解析规则,自定义方式见下文高级功能介绍 2.4 节内容
    @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");
    }
}

2.4 对象参数接收

页面跳转间如果要对象,有两种方式:

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);
    }
}

2.5 自定义 @Autowired 解析

页面跳转间如果要对象,需要先将对象序列化为json,将json传递以后再反序列化为对象
通过 @Autowired 注解可以自定义将 json 解析为对象注入

TheRouter.addAutowiredParser(new MyAutowiredParser());

具体Parser类定义可参考代码DefaultUrlParser

3.0 路由跳转自定义处理器

框架内置四种自定义处理器可供业务场景定制,用于在路由跳转过程中,以切面的方式统一修改路由落地页参数信息。

3.1 Path 修改器

应用场景:用于修复客户端上路由 path 错误问题。

例如:相对路径转绝对路径,或由于服务端下发的链接无法固定https或http,但客户端代码写死了 https 的 path,就可以用这种方式统一。
必须在 TheRouter.build() 方法调用前添加处理器,否则处理器前的所有path不会被修改

NavigatorKt.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;
    }
});

3.2 页面替换器

应用场景:需要将某些path指定为新链接的时候使用。 也可以用在修复链接的场景,但是与 path 修改器不同的是,修改器通常是为了解决通用性的问题,替换器只在页面跳转时才会生效,更多是用来解决特性问题。

例如模块化的时候,首页壳模板组件中开发了一个SplashActivity广告组件作为应用的MainActivity,在闪屏广告结束的时候自动跳转业务首页页面。 但是每个业务不同,首页页面的 Path 也不相同,而不希望让每个业务线自己去改这个首页壳模板组件,此时就可以组件中先写占位符https://kymjs.com/splash/to/home,让接入方通过 Path 替换器解决。
必须在 TheRouter.build().navigation() 方法调用前添加处理器,否则处理器前的所有跳转不会被替换

NavigatorKt.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;
    }
});

3.3 路由替换器

应用场景:常用在未登录不能使用的页面上。例如访问用户钱包页面,在钱包页声明的时候,可以在路由表上声明本页面是需要登录的,在路由跳转过程中,如果落地页是需要登录的,则先替换路由到登录页,同时将原落地页信息作为参数传给登录页,登录流程处理完成后可以继续执行之前的路由操作。

路由替换器的拦截点更靠后,主要用于框架已经从路由表中根据 path 找到路由以后,对找到的路由做操作。

这种逻辑在所有页面跳转前写不太合适,以前的做法通常是在落地页写逻辑判断用户是否具有权限,但其实在路由层完成更合适。
必须在 TheRouter.build().navigation() 方法调用前添加处理器,否则处理器前的所有跳转不会被替换

// 对于如何判断当前页面是否需要登录,我们推荐在 assets/RouteMap.json 路由表或者注解中配置(会自动加入路由表),例如:

// 用注解定义 needLogin
@Route(path = "http://kymjs.com/home", params = {"needLogin","true"})
public class TestActivity extends AppCompatActivity {}

// 用  assets/RouteMap.json 路由表定义的方式如下
  {
    "path": "http://kymjs.com/home",
    "className": "com.therouter.demo.shell.TestActivity",
    "action": "",
    "description": "",
    "params": {
    	"needLogin":true
    }
  }

// 在拦截器处理跳转逻辑
NavigatorKt.addRouterReplaceInterceptor(new RouterReplaceInterceptor() {
    @Override
    public RouteItem replace(RouteItem routeItem) {
        if (Boolean.parseBoolean(routeItem.getExtras().get("needLogin").toString())) {
            RouteItem target = matchRouteMap("登录页面的url");
            target.setDescription("可以在这修改路由参数,推荐把routeItem作为参数传给登录页");
            return target;
        }
        return routeItem;
    }
});

3.4 路由AOP拦截器

与前三个处理器不同的点在于,路由的AOP拦截器全局只能有一个。用于实现AOP的能力,在整个TheRouter跳转的过程中,跳转前,目标页是否找到的回调,跳转时,跳转后,都可以做一些自定义的逻辑处理。

使用场景:场景很多,最常用的是可以拦截一些跳转,例如debug页面在生产环境不打开,或定制startActivity跳转方法。

NavigatorKt.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);
        }
    }
});

3.5 跳转结果回调

如果使用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);
    }
});

4.0 其他API

4.1 判断一个 url 是否为路由Path

如果返回为空,表示当前url不是路由表内的path

// kotlin toplevel方法,Java调用请使用RouteMapKt类
matchRouteMap("url填这里") == null 

相关推荐:

1.2.3版本编译改动1.2.3版本编译改动

从`1.2.3-rc1`版本开始,我们对`TheRouter`编译过程做了大量优化,同时适配了`Gradle`的`4.x-8.x`全部版本,理论上更新的版本或更老的版本也能支持,只是没有去测试。

2 mins
TheRouterSwift iOS 路由介绍TheRouterSwift iOS 路由介绍

TheRouterSwift是货拉拉TheRouter系列开源框架的Swift版本,为日益增多的Swift开发者提供一高可用路由框架。TheRouterSwift用于模块间解耦和通信,基于Swift协议进行动态懒加载注册路由与打开路由的工具。同时支持通过Service-Protocol寻找对应的模块,并用 protocol进行依赖注入和模块通信。

16 mins
TheRouter iOS 路由介绍TheRouter iOS 路由介绍

TheRouter 是货拉拉打造的一款同时支持 Android 及 iOS 的轻量级路由中间件,在iOS端吸取了其他其他语言的特性,支持注解功能,极大提升了路由在iOS端的使用体感。摒弃了传统 iOSer 的 target-action 或 protocol 理念,向更广的后台或 Android 应用对齐。

4 mins