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

简介

因为如今已经有不少各类各样的路由框架了,因此在这里。我也再也不赘述什么是路由?路由框架的意义是什么之类的了。java

特性

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

用法

点击前往Github页git

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

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

依赖

  • 添加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"
复制代码

路由表

路由表的定义

要理解什么是路由表。须要先明确如下几点定义:api

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

路由表: 也叫路由映射表,一个用于存储全部的路由映射的容器。bash

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

如下就是一个简单的路由流程:框架

因此对于Android端来讲。路由表能够理解为一系列特定url与特定Activity之间的映射集合maven

建立路由表

与不少其余的路由框架不一样。此路由框架并未使用自动注册路由表的方式来作。由于自动注册路由表在灵活性上有所欠缺。

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

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

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

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

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

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

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

  @Override
  public Map<String, ActionRouteRule> createActionRouteRules() {
    Map<String,ActionRouteRule> 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