Router_ 一款单品、组件化、插件化全支持的路由框架,安卓开发面试题自定义 view
创建路由表
与很多其他的路由框架不同。此路由框架并未使用自动注册路由表的方式来做。因为自动注册路由表在灵活性上有所欠缺。
创建路由表分为两种方式:手动创建与使用注解在编译时动态生成路由表。
这里主要介绍通过使用注解动态生成的创建方式。手动创建路由表的使用场景只在部分不支持运行时注解的编译环境下推荐使用。
上面提到了,一个路由映射是一组特定 url 与特定页面之间的映射关系。所以通过对指定页面。添加注解配置上指定 url。即可得到一组对应的路由映射:
@RouterRule("haoge://page/user")public class UserActivity extends Activity {...}
添加好此注解后。即可对项目触发一次编译。使其自动生成对应的路由表类:
自动生成的路由表,类名为 RouterRuleCreator:
// 此类为编译时注解自动生成的类。public class RouterRuleCreator implements RouteCreator {@Overridepublic Map<String, ActivityRouteRule> createActivityRouteRules() {Map<String,ActivityRouteRule> routes = new HashMap<>();routes.put("haoge://page/user", new ActivityRouteRule(UserActivity.class));return routes;}
@Overridepublic 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 并进行传递。
参数自动转换:
首先。我们先在 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 中。
传递复杂参数
但是很多时候我们的传参数据又不止是基本数据类型。比如说普通实体 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 也支持另一种路由:动作路由,此种路由没有页面跳转。只是用于做一些特殊的操作。
比如说:加入购物车、清空购物车数据、退出登录等。
@RouterRule("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);
评论