热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

简介由于现在已经有很多各种各样的路由框架了,所以在这里。我也不再赘述什么是路由?路由框架的意义是什么之类的了。特性安全:路由启动过程中。全程catch住异常并通知用户

简介

由于现在已经有很多各种各样的路由框架了,所以在这里。我也不再赘述什么是路由?路由框架的意义是什么之类的了。

特性

  • 安全: 路由启动过程中。全程catch住异常并通知用户。完全不用担心crash问题。
  • 强大的拦截器功能:与大部分的路由不同。提供三种路由拦截器机制,对应不同业务下使用。
  • 方便: 使用apt注解生成路由表,配置方便,易维护。
  • 灵活: 配置路由表方式多样,满足你在任意条件下进行使用。
  • 支持两种路由:页面路由与动作路由。
  • 支持重启路由:路由被拦截后。可通过一行代码无缝恢复重启路由。在登录检查中会很有用。
  • 高度可定制:单品、组件化完美支持,对于插件化环境。也可以针对性的定制使用。

用法

点击前往Github页

本篇文章主要介绍Router在单品、组件化环境下的使用方式,针对插件化环境下的使用适配。请参考以下文章:

Router: 教你如何进行任意插件化环境下的路由适配

依赖

  • 添加jitpack仓库
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
  • 添加依赖
compile "com.github.yjfnypeu.Router:router-api:2.6.0"
annotationProcessor "com.github.yjfnypeu.Router:router-compiler:2.6.0"

路由表

路由表的定义

要理解什么是路由表。需要先明确以下几点定义:

路由映射: 一组特定url特定页面的映射。比如www.baidu.com映射的是百度页面。

路由表: 也叫路由映射表,一个用于存储所有的路由映射的容器。

路由: 是指通过一个url在路由表中匹配到对应的页面。并完成启动的过程。称为一次路由。

以下就是一个简单的路由流程:

所以对于Android端来说。路由表可以理解为一系列特定url与特定Activity之间的映射集合

创建路由表

与很多其他的路由框架不同。此路由框架并未使用自动注册路由表的方式来做。因为自动注册路由表在灵活性上有所欠缺。

创建路由表分为两种方式:手动创建使用注解在编译时动态生成路由表。

这里主要介绍通过使用注解动态生成的创建方式。手动创建路由表的使用场景只在部分不支持运行时注解的编译环境下推荐使用。

上面提到了,一个路由映射是一组特定url与特定页面之间的映射关系。所以通过对指定页面。添加注解配置上指定url。即可得到一组对应的路由映射:

@RouterRule("haoge://page/user")
public class UserActivity extends Activity {
    ...
}

添加好此注解后。即可对项目触发一次编译。使其自动生成对应的路由表类:

自动生成的路由表,类名为RouterRuleCreator:

// 此类为编译时注解自动生成的类。
public class RouterRuleCreator implements RouteCreator {
  @Override
  public Map createActivityRouteRules() {
    Map routes = new HashMap<>();
    routes.put("haoge://page/user", new ActivityRouteRule(UserActivity.class));
    return routes;
  }

  @Override
  public Map createActionRouteRules() {
    Map routes = new HashMap<>();
    ...
    return routes;
  }
}

PS:如果当前环境不支持编译时注解。可以选择手动创建此RouteCreator路由表实例类进行使用。

注册路由表

生成具体的路由表类后。即可通过以下代码进行路由表注册了:

RouterConfiguration.get().addRouteCreator(new RouterRuleCreator());

注册成功之后。则通过以下方式进行启动:

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

以上是最简单的路由配置及用法。下面将一步步的更深入的介绍更多用法

一对多

对于同一个页面。可以配置多个不重复的路由链接:

@RouterRule({url1, url2, url3})
public class ExampleActivity extends Activity {
    ...
}

页面内获取启动的uri

所有的路由启动事件,都会将启动的url链接,存入bundle中进行传递。可通过以下key值进去读取:

Uri uri = getIntent().getParcelableExtra(Router.RAW_URI);

配置baseUrl

一般来说:一个app所定义使用的路由url都会有个特定的前缀。而如果是在插件化环境下。也推荐对各个插件分别定义一份独有的路由前缀。原因将在下一篇介绍插件化环境路由配置的文章中进行具体说明。

框架提供RouteConfig注解。一个module只能配置一次且必须配置于Application子类之上:

@RouteConfig(baseUrl="haoge://page/")
public class App extends Application {
    ...
}

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

自动解析url参数

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

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

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

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

  1. 参数自动转换:

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

public class BaseActivity extends Activity {

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

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

@RouterRule("haoge://page/example")
public class ExampleActivity extends BaseActivity {
    @Arg
    String name;
    @Arg
    long id;
    @Arg
    boolean 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{
    @Arg
    User 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也支持另一种路由:动作路由,此种路由没有页面跳转。只是用于做一些特殊的操作。

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

@RouterRule("shopcar.clear")
public class ClearShopcarAction extends ActionSupport {
    @Override
    public 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{
    @Override
    public boolean intercept(Uri uri, RouteBundleExtras extras, Context context){
        // 未登录时进行拦截
        return !LoginChecker.isLogin();
    }

    @Override
    public 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 {

    @Arg
    Uri uri;
    @Arg
    RouteBundleExtras 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

具体代码可以参考github上的组件化demo

插件化

对于介绍插件化环境下的使用方式的文章, 可以参考下方链接

Router: 教你如何进行任意插件化环境下的路由适配

如果你当前的插件化方案是使用的Small或者RePlugin。那么也可以参考以下两个demo

Small环境下使用Router

RePlugin环境下使用Router


推荐阅读
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
author-avatar
key920721
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有