继上一篇 手把手带你撸一个路由(1)--界面跳转之后,这篇文章来说一说如何实现携带参数的跳转
写在前头,本系列文章为了更简单的讲述一些路由的知识,demo中的代码写的十分简单和直接.所以会存在不少判断不合理的地方.对demo有合理建议的人可以再评论处指出。
带参跳转
正常情况下Activity之间的跳转如下
Intent intent = new Intent(this, TestActivity1.class);
intent.putExtra("name", "张三");
startActivity(intent);
//intent内部维护着一个Bundle对象,而Bundle实现了Parcelable接口,相对而言算是比较高效率的参数传递方式。
既然Android原生已经有了高效的参数传递方式,那自然是要利用起来。增加一个 IntentWrapper.class
用来表示对intent参数的封装
public class IntentWrapper {
private Bundle mBundle;
private String originalUrl;
private RouteDemo routeDemo;
private volatile static IntentWrapper instance = null;
public static IntentWrapper getInstance() {
if (instance == null) {
synchronized (IntentWrapper.class) {
if (instance == null) {
instance = new IntentWrapper();
}
}
}
return instance;
}
public IntentWrapper build(RouteDemo routeDemo, String url) {
this.routeDemo = routeDemo;
this.originalUrl = url;
mBundle = new Bundle();
return this;
}
public IntentWrapper withString(String key, String value) {
mBundle.putString(key, value);
return this;
}
public IntentWrapper withInt(String key, int value) {
mBundle.putInt(key, value);
return this;
}
public void open() {
routeDemo.open(originalUrl, mBundle);
}
}
声明一个Bundle
对象,同时对外提供两个简单的方法withString
和withInt
。
接下来需要对RouteDemo
进行改造
在上一篇文章中,由于路由跳转是没有携带参数的,所以是以
RouteDemo.open("test");
这样的简单形式。
改造之后的调用方式
RouteDemo.getInstance().build("route://test")
.withString("name", "张三")
.withInt("age", 15)
.open();
通过build函数获取前文所提到的IntentWrapper对象,借由这个对象来传递参数。最后通过open()函数回调RouteDemo
中的跳转逻辑.
完整的RouteDemo.class
public class RouteDemo {
private static HashMap<String, Class> activityMap = new HashMap<>();
private static Application mApplication;
private volatile static RouteDemo instance = null;
public static RouteDemo getInstance() {
if (instance == null) {
synchronized (RouteDemo.class) {
if (instance == null) {
instance = new RouteDemo();
}
}
}
return instance;
}
public void init(Application application) {
mApplication = application;
try {
//通过反射调用AutoCreateModuleActivityMap_app类的方法,并给activityMap赋值
Class clazz = Class.forName("com.dly.routeDemo.AutoCreateModuleActivityMap_app");
Method method = clazz.getMethod("initActivityMap", HashMap.class);
method.invoke(null, activityMap);
for (String key : activityMap.keySet()) {
System.out.println("activityMap = " + activityMap.get(key));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public IntentWrapper build(String url) {
return IntentWrapper.getInstance().build(this,url);
}
public void open(String url, Bundle bundle) {
for (String key : activityMap.keySet()) {
if(url.equals(key)){
Intent intent = new Intent(mApplication, activityMap.get(key));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtras(bundle);
mApplication.startActivity(intent);
}
}
}
public void open(String url) {
for (String key : activityMap.keySet()) {
if (url.equals(key)) {
Intent intent = new Intent(mApplication, activityMap.get(key));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mApplication.startActivity(intent);
}
}
}
}
如果只是像上述那样显示跳转,那路由毫无意义。通常我们更多的需求会是服务器返回一个url,客户端根据这个url进行匹配跳转。
比如首页的一个banner页,昨天要求跳转到testActivity1
这个界面,今天又突然改需求,要求跳转到testActivity2
这个界面。这个时候路由的动态配置就派上用场了。
比较常见的调用方法如下
RouteDemo.getInstance().open("route://test?name=555");
再讲述URL跳转之前,必须得先说明一下Android下的URL和URI,以及他们之间的关系和格式,为之后URI解析做下铺垫。
URL,URN,URI的关系 (对URI比较熟悉的可以跳过这一段)###
URI在于I(Identifier)是统一资源标识符,可以唯一标识一个资源。
URL在于L(Location)是统一资源定位符,可以提供找到该资源的路径。
URN在于N( name)统一资源名称,通过名字标识资源。
一般来说URL和URN都是URI的子集,它们两者共同组成了URI。
比如https://www.zhihu.com/question/21950864,这一串地址可以唯一标识一个资源,所以是一个URI,同时也可以通过这个地址找到资源,所以也是一个URL。
还有一种情况,urn:isbn:0-486-27557-4这是一本书的isbn,可以唯一标识一本书,但我们无法通过这一串isbn找到书籍,所以这里是URI,不是URL,准确说这一串isbn是一个URN。
URI的结构
URI的几种划分形式
基本划分
[scheme:]scheme-specific-part[#fragment]
进一步划分
[scheme:][//authority][path][?query][#fragment]
再进一步划分
[scheme:][//host:port][path][?query][#fragment]
其中有一些简单的规则
- path可以有多个,每个用/连接,比如
scheme://authority/path1/path2/path3?query#fragment
- query参数可以带有对应的值,也可以不带,如果带对应的值用=表示,如:
scheme://authority/path1/path2/path3?id = 1#fragment
,这里有一个参数id,它的值是1 - query参数可以有多个,每个用&连接
scheme://authority/path1/path2/path3?id = 1&name = mingming&old#fragment
这里有三个参数:
参数1:id,其值是:1
参数2:name,其值是:mingming
参数3:old,没有对它赋值,所以它的值是null - 在android中,除了scheme、authority是必须要有的,其它的几个path、query、fragment,它们每一个可以选择性的要或不要,但顺序不能变,比如:
其中"path"可不要:scheme://authority?query#fragment
其中"path"和"query"可都不要:scheme://authority#fragment
其中"query"和"fragment"可都不要:scheme://authority/path
"path","query","fragment"都不要:scheme://authority
其中有一些简单的规则
- path可以有多个,每个用/连接,比如
scheme://authority/path1/path2/path3?query#fragment
- query参数可以带有对应的值,也可以不带,如果带对应的值用=表示,如:
scheme://authority/path1/path2/path3?id = 1#fragment
,这里有一个参数id,它的值是1 - query参数可以有多个,每个用&连接
scheme://authority/path1/path2/path3?id=1&name =张三&old#fragment
这里有三个参数:
参数1:id,其值是:1
参数2:name,其值是:张三
参数3:old,没有对它赋值,所以它的值是null - 在android中,除了scheme、authority是必须要有的,其它的几个path、query、fragment,它们每一个可以选择性的要或不要,但顺序不能变,比如:
其中"path"可不要:scheme://authority?query#fragment
其中"path"和"query"可都不要:scheme://authority#fragment
其中"query"和"fragment"可都不要:scheme://authority/path
"path","query","fragment"都不要:scheme://authority
做一个简单的例子匹配
http://www.java2s.com:8080/yourpath/fileName.htm?name=张三&id=4#niknowzcd
- scheme:http
- host:www.java2s.com
- port:8080
- path:/yourpath/fileName.htm
- query:name=张三&id=4
- fragment:niknowzcd
常用的api
同样的例子
http://www.java2s.com:8080/yourpath/fileName.htm?name=张三&id=4#niknowzcd
- getScheme() :获取Uri中的scheme字符串部分,在这里即http
- getSchemeSpecificPart():获取Uri中的scheme-specific-part:部分,这里是://www.java2s.com:8080/yourpath/fileName.htm?
- getFragment():获取Uri中的Fragment部分,niknowzcd
- getAuthority():获取Uri中Authority部分,即www.java2s.com:8080
- getPath():获取Uri中path部分,即/yourpath/fileName.htm
- getQuery():获取Uri中的query部分,即name=张三&id=4
- getHost():获取Authority中的Host字符串,即www.java2s.com
- getPost():获取Authority中的Port字符串,即8080
另外还有两个常用的属性:getPathSegments()
,getQueryParameter(String key)
List<String> getPathSegments() 会将整个path路径保存下来,并以/符号作为分隔符
String mUriStr = "http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic";
Uri mUri = Uri.parse(mUriStr);
List<String> pathSegList = mUri.getPathSegments();
for (String pathItem:pathSegList){
Log.d("debug",pathItem);
}
//输出
yourpath
fileName.htm
getQueryParameter(String key): 则是根据key来获取对应的Query值
String mUriStr = "http://www.java2s.com:8080/yourpath/fileName.htm?name=张三&id=4#niknowzcd";
mUri = Uri.parse(mUriStr);
Log.d(debug,"getQueryParameter(\"name\"):"+mUri.getQueryParameter("name"));
Log.d(debug,"getQueryParameter(\"id\"):"+mUri.getQueryParameter("id"));
//输出
getQueryParameter("name"):张三
getQueryParameter("id"):
在path中,即使针对某一个KEY不对它赋值是允许的,但在利用getQueryParameter()获取该KEY对应的值时,获取到的是null;
匹配URI跳转
我们回过头看看之前写的路径的匹配方式
public void open(String url) {
for (String key : activityMap.keySet()) {
if(url.equals(key)){
Intent intent = new Intent(mApplication, activityMap.get(key));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mApplication.startActivity(intent);
}
}
}
采用的是字符串Url全匹配的形式, 这种方式局限性很大,就拿
route://test?name=555
上述这个字符串来说,我希望的是跳转的test
对照的Activity中,并且携带参数name=555
.
如果用全URL匹配的话,我们可能需要做的是在目标Activity上加上类似的标注
@Route("route://test?name=555")
这样跳转是能跳转了,不过如何携带参数又成了问题。
这时再回去看看URI的结构划分,取其中一种适用性比较广泛的如下
[scheme:][//host:port][path][?query][#fragment]
其中
[scheme:][//host:port][path]
这部分就足够表示一个唯一的界面资源了,后面的参数只是区分当前界面所需要显示的数据是那些而已。
就比如一个UserInfoActivity
不管你传入的userId
是什么,你所在的view都是UserInfoActivity
。
所以在匹配一个外部的URL的时候,需要匹配的是URL路径部分,即
[scheme:][//host:port][path]
这一部分
顺着个思路,来改写RouteDemo中的代码。
增加一个checkUrlPath
函数
//Uri的标准格式 scheme、authority 二者是必须的
private static boolean checkUrlPath(String targetUrl, String matchUrl) {
Uri targetUri = Uri.parse(targetUrl);
Uri matchUri = Uri.parse(matchUrl);
Assert.assertNotNull(targetUri.getScheme());
Assert.assertNotNull(targetUri.getHost());
if (targetUri.getScheme().equals(matchUri.getScheme()) && targetUri.getHost().equals(matchUri.getHost())) {
return TextUtils.equals(targetUri.getPath(), matchUri.getPath());
} else {
return false;
}
}
这里还是采用了一个简单粗暴的方式,当scheme
,host
,path
三者都相等的时候,我们认为匹配上了,这里没提到port
是因为路由中通常使用不到port
这个属性,如果需要的话,可以getAuthority()
去获取。
匹配到了path之后,还需要一个步骤,就是把我们需要的参数传递过去。使用
是getQueryParameterNames()
和getQueryParameter(queryParameterName)
具体实现如下
private Intent parseParams(Intent intent, String targetUrl) {
Uri uri = Uri.parse(targetUrl);
Set<String> queryParameterNames = uri.getQueryParameterNames();
for (String queryParameterName : queryParameterNames) {
intent.putExtra(queryParameterName, uri.getQueryParameter(queryParameterName));
}
return intent;
}
最后再贴一下改造后的RouteDemo的函数
public class RouteDemo {
private static HashMap<String, Class> activityMap = new HashMap<>();
private static Application mApplication;
private volatile static RouteDemo instance = null;
public static RouteDemo getInstance() {
if (instance == null) {
synchronized (RouteDemo.class) {
if (instance == null) {
instance = new RouteDemo();
}
}
}
return instance;
}
public void init(Application application) {
mApplication = application;
try {
//通过反射调用AutoCreateModuleActivityMap_app类的方法,并给activityMap赋值
Class clazz = Class.forName("com.dly.routeDemo.AutoCreateModuleActivityMap_app");
Method method = clazz.getMethod("initActivityMap", HashMap.class);
method.invoke(null, activityMap);
for (String key : activityMap.keySet()) {
System.out.println("activityMap = " + activityMap.get(key));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public IntentWrapper build(String url) {
return IntentWrapper.getInstance().build(this,url);
}
public void open(String url, Bundle bundle) {
for (String key : activityMap.keySet()) {
if (checkUrlPath(url, key)) {
Intent intent = new Intent(mApplication, activityMap.get(key));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtras(bundle);
mApplication.startActivity(intent);
}
}
}
public void open(String url) {
for (String key : activityMap.keySet()) {
if (checkUrlPath(url, key)) {
Intent intent = new Intent(mApplication, activityMap.get(key));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent = parseParams(intent, url);
mApplication.startActivity(intent);
}
}
}
private Intent parseParams(Intent intent, String targetUrl) {
Uri uri = Uri.parse(targetUrl);
Set<String> queryParameterNames = uri.getQueryParameterNames();
for (String queryParameterName : queryParameterNames) {
intent.putExtra(queryParameterName, uri.getQueryParameter(queryParameterName));
}
return intent;
}
//Uri的标准格式 scheme、authority 二者是必须的
private static boolean checkUrlPath(String targetUrl, String matchUrl) {
Uri targetUri = Uri.parse(targetUrl);
Uri matchUri = Uri.parse(matchUrl);
Assert.assertNotNull(targetUri.getScheme());
Assert.assertNotNull(targetUri.getHost());
if (targetUri.getScheme().equals(matchUri.getScheme()) && targetUri.getHost().equals(matchUri.getHost())) {
return TextUtils.equals(targetUri.getPath(), matchUri.getPath());
} else {
return false;
}
}
}