手把手带你撸一个路由(2)--带参跳转

继上一篇 手把手带你撸一个路由(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对象,同时对外提供两个简单的方法withStringwithInt

接下来需要对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。

比如www.zhihu.com/question/21…,这一串地址可以唯一标识一个资源,所以是一个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 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;
        }
    }

}
复制代码

github 地址

RouteDemo

猜你喜欢

转载自juejin.im/post/5b4ad9ea6fb9a04fe91a69b7