写点什么

Router_ 一款单品、组件化、插件化全支持的路由框架

用户头像
Android架构
关注
发布于: 50 分钟前

下表是路由前缀与路由地址之前的匹配关系,横排表示 RouteRule 配置的路由地址,竖排表示 baseUrl:


自动解析 url 参数

Router 的自动参数解析。是结合的 Parceler 框架来进行使用的,关于 Parceler 框架的介绍可以参考下方的链接:


Parceler: 优雅的使用Bundle进行数据存取就靠它了!


请注意:Parceler 框架并不是 Router 所必须依赖的框架。只是添加此框架使用后,能使用更强大的特性。


如果不使用的话。所有的 url 参数。都将默认解析为 String 并进行传递。


  1. 参数自动转换:


首先。我们先在 Activity 基类中配置注入入口,配置此入口后。就会自动从 intent 中读取对应的数据。注入到子类中的被 Arg 注释过的成员变量中去了:


public class BaseActivity extends Activity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Parceler.toEntity(this,getIntent());}}


假设当前我们有以下一个页面:


@RouterRule("haoge://page/example")public class ExampleActivity extends BaseActivity {@ArgString name;@Arglong id;@Argboolean isLogin;...}


可以看到。这个页面含有三个属性。并都添加了 Arg 注解。那么此时我们可以以下方的链接来进行跳转传参:


Router.create("haoge://page/example?name=haoge&id=10086&isLogin=false").open(context);


链接中的参数将会自动根据 Arg 注解的类型进行自动转换。并在转换后转载入 Intent 中进行传递. 即相当于以下的操作:


String url = "haoge://page/example?name=haoge&id=10086&isLogin=false";Uri uri = Uri.parse(url);Bundle bundle = new Bundle;bundle.putString("name", uri.getQueryParameter("name"))bundle.putLong("id",Long.parseLong(uri.getQueryParameter("id")));bundle.putBoolean("isLogin",Boolean.parseBoolean(uri.getQueryParameter("isLogin")));


// 启动路由并传递 bundle...


此种自动转换类型的参数。只支持基本数据类型。若 Arg 所注释的属性类型不为基本数据类型。则不触发自动转换,将读取的 String 串直接存入 Intent 中。


  1. 传递复杂参数


但是很多时候我们的传参数据又不止是基本数据类型。比如说普通实体 bean。比如说一个列表。所以这就是 Parceler 展现光芒的时候了!


Parceler 自带 JSON 数据转换功能。对于传递的是非基本数据类型的。则可以在参数中传递此类型的 json 串:这也是为什么推荐引入 Parceler 框架进行使用的原因:小而强大!


复杂参数需要使用 Parceler 的转换功能,关于数据转换器。也在上面那篇文章中有描述。这里我使用的是 FastJson 的转换器:


Parceler.setDefaultConverter(FastJsonConverter.class);


首先假设当前有个此页面, 需要传参为普通实体类 User:


@RouterRule("usercenter")public class UserCenterActivity extends BaseActivity{@ArgUser user;}


public class User {public String username;public String password;...}


那么就可以通过以下链接进行跳转:


// 这里为了理解方便,我没有直接拼装链接。User user = new User("router", "123456");String json = JSON.toJSONString(user);// 对 json 串需要先进行 url 编码。String encodeJson = URLEncoder.encode(json);String url = String.format("haoge://page/usercenter?user=%s", encodeJson);Router.create(url).open(context);


可以看到,通过此种方式,可以直接传递具体的 json 数据进行传递。请注意对于链接中的 json 数据。一定要先进行 encode 编码。避免内部 uri 解析异常。


由于目标页对应的 user 类型不为基本数据类型。所以此处将直接将 user 所指代的值。自动解码后直接放入 Intent 中传递入目标页。目标页中则会使用 Parceler 自动将此 json 转换解析成 User 类。完成复杂参数的传递

动作路由

上面所介绍的。都是通过一个链接。打开一个对应的页面。此种路由跳转称为页面路由。


Router 也支持另一种路由:动作路由,此种路由没有页面跳转。只是用于做一些特殊的操作。


比如说:加入购物车、清空购物车数据、退出登录等。


@R


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


outerRule("shopcar.clear")public class ClearShopcarAction extends ActionSupport {@Overridepublic void onRouteTrigger(Context context, Bundle bundle) {// TODO 清空购物车操作}}


然后即可通过以下链接触发清空购物车操作:


Router.create("haoge://page/shopcar.clear").open(context);

额外请求参数

上面举的例子。都是全部数据通过一个 url 直接传递。但是很多时候。我们是需要在此 url 的基础上。再额外添加一些别的数据进行传递的。比如转场动画配置、requestCode 配置、Intent.flags 配置等.


Router.create(url).addExtras(bundle) // 添加额外 bundle 数据参数.requestCode(code) // 用于 startActivityForResult.setAnim(enterAnim, exitAnim)// 转场动画.addFlags(flag)// intent.addFlags(flag);.addInterceptor(interceptor)// 添加拦截器.setCallback(callback)// 设置路由回调.open(context);

添加路由回调

在讲路由表的时候有提到过,路由是一种特殊的启动流程,且启动不一定成功。所以很自然的,这个时候就需要有一个路由回调接口。


路由回调接口为 RouteCallback 类:


public interface RouteCallback {// 当路由寻址失败时。触发此回调 void notFound(Uri uri, NotFoundException e);// 当路由启动成功时。触发此回调 void onOpenSuccess(Uri uri, RouteRule rule);// 当路由启动失败时。触发此回调。包括 void onOpenFailed(Uri uri, Throwable e);}


路由回调配置分为两种:


  • 全局路由回调:所有的路由启动事件。都会触发此回调


RouterConfiguration.get().setCallback(callback);


  • 局部路由回调:只被当前路由启动触发。


Router.create(url).setCallback(callback).open(context);


路由回调在进行页面跳转埋点时,会是非常有用的一个特性。

日志打印

当配置 Router.DEBUG 为 true 时(默认为 false)。框架将会启用内部日志输出。建议使用 BuildConfig.DEBUG 进行日志开关控制, 达到在只在开发时进行日志输出的作用:


Router.DEBUG = BuildConfig.DEBUG


日志可通过 RouterLog 进行过滤查看


拦截器

顾名思义:拦截器就是用于在路由启动过程中,进行一系列的提前检查,当检查不符合规则时,则使此次路由启动失败。


举个最经典的案例:登录检查



当不使用路由进行跳转时,这种情况就会导致你本地写上了大量的登录判断逻辑代码。这在维护起来是很费劲的。而且也非常不灵活,而使用拦截器的方式来做登录检查,就会很方便了:



下面是一个简单的登录拦截实现:


// 实现 RouteInterceptor 接口 public class LoginInterceptor implements RouteInterceptor{@Overridepublic boolean intercept(Uri uri, RouteBundleExtras extras, Context context){// 未登录时进行拦截 return !LoginChecker.isLogin();}


@Overridepublic void onIntercepted(Uri uri, RouteBundleExtras extras, Context context) {// 拦截后跳转登录页并路由信息传递过去,便于登录后进行恢复 Intent loginIntent = new Intent(context,LoginActivity.class);// uri 为路由链接 loginIntent.putExtra("uri",uri);// extras 中装载了所有的额外配置数据 loginIntent.putExtra("extras",extras);context.startActivity(loginIntent);}}


public class LoginActivity extends BaseActivity {


@ArgUri uri;@ArgRouteBundleExtras extras;


void onLoginSuccess() {if(uri != null) {// 登录成功。使用此方法直接无缝恢复路由启动 Router.resume(uri, extras).open(context);}finish();}}


拦截器功能是 Router 框架的重点,且经过长时间的迭代。Router 目前提供三种拦截器提供使用,你可以根据你自己的需要。灵活的选择使用什么类型的拦截器。

1. 全局默认拦截器:

设置方式:RouterConfiguration.get().setInterceptor(interceptor);


作用域:此全局默认拦截器。将会被所有启动的路由事件所触发。


推荐使用场景


一些需要进行全局判断的检查:比如登录检查等。且最好此处所配置的拦截器。添加上对应的开关控制。


比如说做登录检查的,控制如果有 requestlogin 参数的才启用登录检查。将登录检查控制交给提供 url 的地方。

2. 针对某次路由所特别设置的拦截器

设置方式:Router.create(url).addInterceptor(interceptor);


作用域:只被此处所创建的路由触发。


推荐使用场景:一些只在此处启动路由时才需要触发的检查:比如 deeplink 外部链接入口处,检查外部链接是否合法等。

3. 针对某个目标路由所特别设置的拦截器

设置方式:在配置了 RouterRule 的目标类上。添加 @RouteInterceptor()注解。将需要配置的注解 Class 加入:


@RouteInterceptors(CustomInterceptors.class)@RouterRule(rule)public class ExampleActivity extends Activity {}


作用域:当路由 url 所匹配的目标路由为此路由时被触发


推荐使用场景:针对此页面跳转的的检查,比如对传递参数进行过滤,避免传入无效数据导致不可期异常等。


此三种拦截器,触发的优先顺序为:全局默认 > 某次路由 > 某个目标路由,且若当此路由已被某个拦截器拦截了。则将不会继续触发后续拦截器

Router 在组件化环境下进行使用

对于组件化中使用 Router 框架。可以参考此处放于github上的组件化demo


可结合上方 demo 与下方描述一同查看。达到更加方便理解的作用!


在组件化环境下使用路由框架。主要需要考虑以下几点:

注册多个路由表

由于 Router 本身没有使用自动注册的方式来进行路由表注册。而是提供了相应的接口、相应的方法来手动注册,这种配置方式本身即可做到动态注册多个路由表的效果:


RouterConfiguration.get().addRouteCreator(routeCreator);


所以在组件化中进行使用。只要想办法注册多个组件自身生成的路由表即可。

激活业务组件的注解处理器

路由表类的生成,是在编译时自动生成的。而编译时生成框架的作用域只在当前 module 中。所以需要将 Router 的注解处理器,分别配置添加到每个业务组件之中,使每个业务组件都能够使用注解生成自身的路由表类:


annotationProcessor "com.github.yjfnypeu.Router:router-compiler:2.6.0"


而组件化中,对于使用的注解处理器。推荐的做法是将所有需要使用的注解处理器都抽离到一个统一的 gradle 脚本中。然后由各个组件直接 apply 应用即可:


创建 processor.gradle:


dependencies {annotationProcessor "com.github.yjfnypeu.Router:router-compiler:2.6.0"...// 所有注解处理器均放置于此配置}


然后在组件中的 build.gradle 中直接通过 apply from 方法应用此脚本即可。


这样的做法有以下几点好处:


  1. 由于编译时注解的注解处理器。是直接提供给 IDE 进行使用的。并不会再打包时将对应的代码打包进 apk 中。所以不用担心引入额外的不需要的代码进入 apk 中。

  2. 便于版本升级统一控制

避免多个组件生成的路由表冲突

在单品环境下使用时。有介绍会在编译时生成一个具体的路由表类 RouterRuleCreator, 而这个类生成的包名是默认写死的:com.lzh.router.


所以在多组件环境下进行使用时,需要对每个 module 指定不同的生成路由表类的包名。避免出现重复类冲突问题:


@RouteConfig(pack="com.router.usercenter")public class UCApp extends Application {...}


RouteConfig 注解不止提供单品中使用的 baseUrl 方法进行路由前缀配置。也提供 pack 方法。用于指定此 module 所生成的路由表的具体包名。所以对于组件化环境。只要对不同组件指定不同的包名即可!

推荐的注册方式

由于组件化其实所有组件都是被 app 壳所加载的。并不像插件化那样会出现按需加载的情况。所以这种环境下,多路由表的注册方式,推荐使用反射,一次性将所有有效组件全部加载的方式进行使用:


private void loadRouteRulesIfExist() {// 此 packs 为所有组件中定义的路由表类生成包名集。String[] packs = ComponentPackages.Packages;String clzNameRouteRules = ".RouterRuleCreator";for (String pack : packs) {try {Class<?> creator = Class.forName(pack + clzNameRouteRules);RouteCreator instance = (RouteCreator) creator.newInstance();RouterConfiguration.get().addRouteCreator(instance);} catch (Exception ignore) {// ignore}}}


因为使用的是反射注册。所以请不要忘了加上混淆配置:


-keep class * implements com.lzh.nonview.router.module.RouteCreator

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Router_一款单品、组件化、插件化全支持的路由框架