Android路由框架Router
什么是路由?说简单点就是映射页面跳转关系的,当然它也包含跳转相关的一切功能。
路由框架的意义
Android系统已经给我们提供了api来做页面跳转,比如startActivity
,为什么还需要路由框架呢?我们来简单分析下路由框架存在的意义:
- 在一些复杂的业务场景下,灵活性比较强,很多功能都是动态配置的,比如下发一个活动页面,我们事先并不知道具体的目标页面,但如果事先做了约定,提前做好页面映射,便可以自由配置。
- 随着业务量的增长,客户端必然随之膨胀,开发人员的工作量越来越大,比如64K问题,比如协作开发问题。App一般都会走向组件化的道路,而组件化的前提就是解耦,那么我们首先要做的就是解耦页面之间的依赖关系。
- 简化代码。数行跳转代码精简成一行代码。
- 其他...
特性
Router
有哪些特性或者优点呢?
- 基于注解,使用方便,源码简洁
- 链式调用,api友好
- 多路径支持
- 结果回调,每次跳转都会回调跳转结果
- 编译期处理注解,不影响运行时性能
- 除了可以使用注解定义路由,还支持手动分配路由
- 自定义拦截器,可以对路由进行拦截,比如登录判断和埋点处理
- 自定义路由匹配规则,相比较其他路由框架,该项目并没有限制路由的写法,除了内置的几个匹配器,用户完全可以定义自己的规则
- 支持隐式Intent跳转
- 支持多模块使用,支持组件化开发
- 不仅支持注解Activity,还支持注解Fragment
- 支持Kotlin
其他功能正在添加中...
集成
router-gradle-plugin |
router |
router-compiler |
|
---|---|---|---|
最新版本 | [站外图片上传中……(3)] |
集成过程也可参考项目主页README。
- 在项目级的
build.gradle
中加入依赖:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.x ↑'
classpath 'com.chenenyu.router:gradle-plugin:latest.integration'
}
}
注意,Router
需要使用2.2.0
及以上版本的Android gradle plugin
来处理注解,截至写作时,最新版本为2.3.3
。
- 在module级的
build.gradle
中使用plugin:
apply plugin: 'com.android.application/library'
apply plugin: 'com.chenenyu.router'
至此,集成工作就完成了,简单的两步:添加插件路径和应用插件。这应该是类似框架中最简单的集成方式了。
使用
-
Router
需要初始化,用于初始化路由表,建议放到Application
中做:
public class App extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
// 开启log,要放到前面才能看到初始化过程的log
if (BuildConfig.DEBUG) {
Router.openLog();
}
// 初始化
Router.initialize(this);
}
}
- 添加路由注解
// 单路径注解
@Route("test")
public class TestActivity extends Activity {
...
}
// 多路径注解,这几个注解都能打开该Activity
@Route({"user", "example://user", "http://example.com/user"})
public class UserActivity extends Activity {
...
}
@Route("fragment")
public class TestFragment extends Fragment {
...
}
- 发起路由操作
// 最简单的路由跳转,打开TestActivity
Router.build("test").go(context);
// 其他部分api
Router.build("test")
.requestCode(int) // 调用startActivityForResult
.with(bundle) // 携带跳转参数
.addFlags(flag) // 添加标记,比如intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.anim(enter, exit) // 添加跳转动画
.callback(calback) // 跳转结果回调
.go(this);
全部可用API可参考IRouter
接口。
- startActivityForResult
Router.build(uri).requestCode(int).go(this);
其中requestCode
一定得是一个非负数。
go()
有如上几个重载方法,如果是在Fragment中发起startActivityForResult
路由操作,切记传当前fragment实例哦!不然在fragment中是接受不到回调的。
- 添加跳转动画
// 1. 基础动画
Router.build(uri).anim(enter, exit).go(this);
// 2. 转场动画ActivityOptions
Router.build(uri).activityOptions(options).go(this);
- 获取目标页面
// 1. 获取intent,然后可以操作intent
Intent intent = Router.build(uri).getIntent(context);
// 2. 获取fragment,然后可以将该fragment添加到Activity中
Fragment fragment = (Fragment) Router.build(uri).getFragment(context);
- 全局拦截器
// 1. 添加全局拦截器
Router.addGlobalInterceptor(routeInterceptor);
// 2. 跳过全局拦截器
Router.build(uri).skipInterceptors().go(this);
- 添加拦截器
拦截器是Router
的功能之一,作用就是在执行路由之前判断是否需要拦截该次路由请求。比如可以在拦截器中做登录状态判断。
// 定义拦截器(通过注解)
@Interceptor("SampleInterceptor")
public class SampleInterceptor implements RouteInterceptor {
@Override
public boolean intercept(Context context, @NonNull Uri uri, @Nullable Bundle extras) {
// 返回true表示拦截当前路由
return true;
}
}
// 定义拦截器(通过代码)
Router.handleInterceptorTable(new InterceptorTable() {
@Override
public void handle(Map<String, Class<? extends RouteInterceptor>> map) {
map.put("SampleInterceptor", SampleInterceptor.class);
}
});
// 应用拦截器(通过注解)
@Route(value = "test", interceptors = "SampleInterceptor")
public class TestActivity extends AppCompatActivity {
...
}
// 应用拦截器(通过代码)
Router.handleTargetInterceptors(new TargetInterceptors() {
@Override
public void handle(Map<Class<?>, String[]> map) {
map.put(TestActivity.class, new String[]{"SampleInterceptor"});
}
});
- 自定义路由
除了可以使用注解来添加路由外(上面步骤2介绍的方式),还可以通过代码手动控制路由表。
// 动态添加路由表
Router.handleRouteTable(new RouteTable() {
@Override
public void handle(Map<String, Class<?>> map) {
map.put("dynamic1", TestActivity.class);
map.put("dynamic2", TestFragment.class);
...
}
});
该方式与注解没有冲突,可以同时使用。
- 路由匹配规则
该功能是Router
最大的特色功能,不同于其他框架,Router
并没有规定路由的写法规则,而是抽象出Matcher
的概念,交给用户去控制。但是Router
仍然内置了4个常用的Matcher
。
匹配优先级从高到低依次是
DirectMatcher
、SchemeMatcher
、ImplicitMatcher
和BrowserMatcher
,关于原理将在后序的原理篇中进行讲解。假设有如下路由页面:
@Route({"user", "http://example.com/user"})
public class UserActivity extends Activity {
...
}
除了可以通过Router.build("test").go(this)
和Router.build("http://example.com/user").go(this)
打开UserActivity之外(DirectMatcher
命中),还可以通过Router.build("http://example.com/user?id=9527&status=0").go(this)
打开(通过SchemeMatcher
命中),并且自动帮你配置了Bundle参数,即:
@Route({"user", "http://example.com/user"})
public class UserActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Bundle bundle = getIntent().getExtras();
String id = bundle.getString("id");
String status = bundle.getString("status");
}
}
Matcher
支持配置多个,会根据优先级依次进行匹配。
- 参数注入
上面讲了可以通过路由传递参数,然后在目标页面通过Bundle获取,其实这个过程也被Router
简化了。通过@InjectParam
注解可以为Activity或者Fragment的成员变量添加参数注入
@Route({"test", "http://example.com/test", "router://test"})
public class TestActivity extends AppCompatActivity {
@InjectParam
int id = 123;
@InjectParam(key = "status")
private String sts = "default"; // 不建议使用private修饰符
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Router.injectParams(this); // 实现参数注入
}
}
-
@InjectParam
会在Bundle中取出对应key的值传给成员变量,默认key为变量名,也可以通过key=""属性指定 - 参数注入支持变量的默认值,目前支持默认值的变量类型有基本数据类型,String,CharSequence,其他类型的默认值都是null
- 变量不建议使用
private
修饰符,因为私有的变量会采用反射的方式注入参数 - 需要使用
Router.injectParams(this)
来实现最终的参数注入
Router
还有其他一些人性化的小功能,在这里就不一一介绍了,有问题可以在项目主页提issue。后续会给大家讲解一下背后的实现原理。
总结
Router
是一个十分小巧灵活的路由框架,代码设计也很优雅简洁,且完美支持组件化开发,目前仍在不断地迭代中,源码地址为https://github.com/chenenyu/Router,欢迎各位试用点评。