手把手带你撸一个路由(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。

比如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;
        }
    }

}

github 地址

RouteDemo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容