Android框架实战:Router

Router的定义

路由,简单的来说就是映射页面直接跳转关系的。需要注意的是,这个映射关系需要在App启动的时候建立好。

Router的优点

  • 解耦合:这个是最直观的。之前我们需要引入目标页面的Activity.class现在这些不需要要了。
  • 自由配置:这个是为了方便产品或者运维的。我们事先定义好页面的映射关系,那么运营就可以通过后台配置他想跳转的页面了。
  • 模块化:随着业务逐渐增多,就需要模块化。模块化的前提是解决页面之前的依赖关系,也就是前面说的解耦合。
  • KSRouter可以通过RouterConfig对象配置多个域名。
  • KSRouter支持在目标页面使用注解的方式取值。
  • KSRouter想对于之前的Router方案来说去除了配置参数的类型。
  • KSRouter将用户相关的操作(登录等)抛出到具体应用内部去处理,KSRouter库不做处理。
  • KSRouter将WebView相关操作(cookie等)抛出到具体应用内部去处理,KSRouter库不做处理.

初始化Router

1
2
3
4
5
6
7
RouterConfig config = new RouterConfig.Builder()
.addHost("https://borrower.kesucorp.com")
.addHost("ksrouter://borrower.kesucorp.com")
.addInterceptor(new BrowserRouterInterceptor())
.addInterceptor(new UserRouterInterceptor())
.build();
KSRouter.getInstance().initialize(this, config);

通过Builder的方式创建RouterConfig。Host的配置可以是多个,比如有网页形式的https://开头的,也有本地形式的ksrouter开头的。拦截器首先通过BrowserRouterInterceptor拦截器,如果没有匹配到规则,默认会走内部WebView的方式打开。当然,如果匹配到了,走到UserRouterInterceptor中会拦截到当前打开的页面需不需要登录,如果需要登录并且当前用户还没有登录的情况下。此拦截器内部会直接跳转登录页面。否则会进入InnerRouterInterceptor这个拦截器当中。

简单用法

跳转到下一个页面

1
2
3
4
//当前页面
KSRouter.getInstance().open(context, "path");
//目标页面
@Router("path")

跳转到下一个页面并携带参数

跳转代码如下:

1
2
3
4
5
Map<String, Object> map = new HashMap<>();
map.put(RouterConstant.IntentKey.NAME, "wuchuang");
map.put(RouterConstant.IntentKey.AGE, 17);
map.put(RouterConstant.IntentKey.SERIALIZABLE, new TestEntity("entity_name",12));
KSRouter.getInstance().open(context, RouterConstant.NEXT_WITH_PARAMS, map);

目标页面代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Router(value = RouterConstant.NEXT_WITH_PARAMS)
public class NextWithParamsActivity extends BaseActivity {
@Override
public void initView() {
super.initView();
setTitle("NextWithParams");
final String name = getIntent().getStringExtra(RouterConstant.IntentKey.NAME);
final int age = getIntent().getIntExtra(RouterConstant.IntentKey.AGE, 0);
final TestEntity entity = getIntent().getSerializableExtra(RouterConstant.IntentKey.SERIALIZABLE);
//TODO
}

@Override
public int getLayoutId() {
return R.layout.activity_next_with_params;
}
}

当然,如果你觉得getIntent().getXXXExtra()太繁琐,也可以通过@RouterPatams的方式获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Router(value = RouterConstant.NEXT_WITH_PARAMS)
public class NextWithParamsActivity extends BaseActivity {

@RouterParams(RouterConstant.IntentKey.NAME)
String name;
@RouterParams(RouterConstant.IntentKey.AGE)
int age;
@RouterParams(RouterConstant.IntentKey.SERIALIZABLE)
TestEntity entity;

@Override
public void initView() {
super.initView();
setTitle("NextWithParams");
//TODO
}

@Override
public int getLayoutId() {
return R.layout.activity_next_with_params;
}
}

RouterParams注册使用的前提需要在Activity中调用KSRouter的inject方法注入,这个步骤可以放在BaseActivity中,或者放在ActivityLifecycleCallbacks中来完成都是可以的。

其他Router功能

通过一个接口来展示Router功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 初始化
*/
void initialize(Context context, RouterConfig routerConfig);

/**
* Router注入
* @param activity
*/
void inject(Activity activity);

/**
* 用于WebView打开App页面(可携带参数)
*/
RouterInfo openWithWeb(Context context, String url);


/**
* 用于WebView打开App页面(可携带参数)
*/

RouterInfo openForResultWithWeb(Context context, String url);
/**
* App内部打开App页面(不带参数)
*/
RouterInfo open(Context context, String path);

/**
* App内部打开App页面(带参数)
*/
RouterInfo open(Context context, String path, Map<String, Object> params);

/**
* App内部打开App页面并且需要返回结果(不带参数)
*/
RouterInfo openForResult(Context context, String path, int requestCode);

/**
* App内部打开App页面并且需要返回结果(带参数)
*/
RouterInfo openForResult(Context context, String path, Map<String, Object> params, int requestCode);
/**
* 获取当前路由的Path
*/
String getCurrentPath(Class cls);

源码解析

定义RouterInfo

如何定义页面直接的映射和跳转关系,可以从一个实体类中看出。

1
2
3
4
5
6
7
public class RouterInfo implements Cloneable{
private List<String> paths;
private String activityCls;
private Map<String, Object> paramsMap;
private int requestCode;
private String currentPath;
}

paths包含了一个一对多的关系,也就是说一个页面可以存在多个Path,不同的Path只要在当前页面声明过都可以跳转到这个页面。
activityCls是用原生Intent跳转是所需要的参数。
paramsMap为页面跳转所携带的数据。如果页面通过startActivityForResult形式,那么requestCode是必不可少的参数。
currentPath可以获取当前跳转页面对应的Path,用来处理Activity内部多个Fragment跳转的场景。

InnerRouterInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Override
public RouterInfo intercept(Context context, RouterChain routerChain) {
RouterInfo routerInfo = routerChain.routerInfo();
if (routerInfo != null) {
nextWithRouterInfo(context, routerInfo);
}
return routerInfo;
}

private void nextWithRouterInfo(Context context, RouterInfo routerInfo) {
Intent intent = new Intent();
Map<String, Object> map = routerInfo.getParamsMap();
if (map != null)
addParams(map, intent);
intent.setClassName(context, routerInfo.getActivityCls());
int requestCode = routerInfo.getRequestCode();
if (requestCode != 0) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
}
} else {
context.startActivity(intent);
}
}


private void addParams(Map<String, Object> map, Intent intent) {
for (String key : map.keySet()) {
Object value = map.get(key);
Class valueClass = value.getClass();
if (valueClass.isAssignableFrom(Integer.class) || valueClass.isAssignableFrom(int.class)) {
intent.putExtra(key, (Integer) value);
} else if (valueClass.isAssignableFrom(Long.class) || valueClass.isAssignableFrom(long.class)) {
intent.putExtra(key, (Long) value);
} else if (valueClass.isAssignableFrom(Float.class) || valueClass.isAssignableFrom(float.class)) {
intent.putExtra(key, (Float) value);
} else if (valueClass.isAssignableFrom(Double.class) || valueClass.isAssignableFrom(double.class)) {
intent.putExtra(key, (Double) value);
} else if (valueClass.isAssignableFrom(String.class)) {
intent.putExtra(key, (String) value);
} else if (Serializable.class.isAssignableFrom(valueClass)) {
intent.putExtra(key, (Serializable) value);
} else if (Parcelable.class.isAssignableFrom(valueClass)) {
intent.putExtra(key, (Parcelable) value);
}
}
}

注:根据requestCode来判断是否需要返回结果给前一个Activity,通过isAssignableFrom方法来确定当前参数的类型[常用的基本数据类型,Serializable或者Parcelable数据]。并按照指定类型放入Intent中。

和老版本对比

HOST或者说是协议的添加

比如首页的Router配置信息如下:

1
2
3
4
@Router({
BuildConfig.dai + "/finance", BuildConfig.dai + "/project/list", BuildConfig.dai + "/creditassign/list", BuildConfig.dai + "/discovery", BuildConfig.dai + "/user",
BuildConfig.www + "/finance", BuildConfig.www + "/project/list", BuildConfig.www + "/creditassign/list", BuildConfig.www + "/discovery", BuildConfig.www + "/user"
})

KSRouter中只需要这样写

1
2
3
@Router({
"/finance", "/project/list", "/creditassign/list", "/discovery", "/user"
})

Router注解不提供参数类型

比如充值页面Router配置信息如下:

1
@Router(value = BuildConfig.passport + "/user/recharge", floatParams = "rechargenum", transfer = "amount=>rechargenum", booleanParams = "skip_result_act")

注:不配置参数类型也是可以获取到数据。

KSRouter中只需要这样写

1
@Router("/user/recharge")

目标页面参数的名称转换

比如充值页面Router配置信息如下:

1
@Router(value = BuildConfig.passport + "/user/recharge", floatParams = "rechargenum", transfer = "amount=>rechargenum", booleanParams = "skip_result_act")

KSRouter中只需要这样写

1
2
3
4
5
@RouterParams("amount")
float rechargenum;
或者
@RouterParams
float amount;

首先寻找注解上对应的key,如果不存在,就会拿当前字段的名称当做key来取值。

您的支持是我原创的动力