繼上一篇 手把手帶你擼一個(gè)路由(1)--界面跳轉(zhuǎn)之后,這篇文章來(lái)說(shuō)一說(shuō)如何實(shí)現(xiàn)攜帶參數(shù)的跳轉(zhuǎn)
寫(xiě)在前頭,本系列文章為了更簡(jiǎn)單的講述一些路由的知識(shí),demo中的代碼寫(xiě)的十分簡(jiǎn)單和直接.所以會(huì)存在不少判斷不合理的地方.對(duì)demo有合理建議的人可以再評(píng)論處指出。
帶參跳轉(zhuǎn)
正常情況下Activity之間的跳轉(zhuǎn)如下
Intent intent = new Intent(this, TestActivity1.class);
intent.putExtra("name", "張三");
startActivity(intent);
//intent內(nèi)部維護(hù)著一個(gè)Bundle對(duì)象,而B(niǎo)undle實(shí)現(xiàn)了Parcelable接口,相對(duì)而言算是比較高效率的參數(shù)傳遞方式。
既然Android原生已經(jīng)有了高效的參數(shù)傳遞方式,那自然是要利用起來(lái)迈嘹。增加一個(gè) IntentWrapper.class
用來(lái)表示對(duì)intent參數(shù)的封裝
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);
}
}
聲明一個(gè)Bundle
對(duì)象,同時(shí)對(duì)外提供兩個(gè)簡(jiǎn)單的方法withString
和withInt
表锻。
接下來(lái)需要對(duì)RouteDemo
進(jìn)行改造
在上一篇文章中,由于路由跳轉(zhuǎn)是沒(méi)有攜帶參數(shù)的,所以是以
RouteDemo.open("test");
這樣的簡(jiǎn)單形式蛀醉。
改造之后的調(diào)用方式
RouteDemo.getInstance().build("route://test")
.withString("name", "張三")
.withInt("age", 15)
.open();
通過(guò)build函數(shù)獲取前文所提到的IntentWrapper對(duì)象,借由這個(gè)對(duì)象來(lái)傳遞參數(shù)饲齐。最后通過(guò)open()函數(shù)回調(diào)RouteDemo
中的跳轉(zhuǎn)邏輯.
完整的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 {
//通過(guò)反射調(diào)用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);
}
}
}
}
如果只是像上述那樣顯示跳轉(zhuǎn),那路由毫無(wú)意義字支。通常我們更多的需求會(huì)是服務(wù)器返回一個(gè)url,客戶端根據(jù)這個(gè)url進(jìn)行匹配跳轉(zhuǎn)雷厂。
比如首頁(yè)的一個(gè)banner頁(yè),昨天要求跳轉(zhuǎn)到testActivity1
這個(gè)界面,今天又突然改需求,要求跳轉(zhuǎn)到testActivity2
這個(gè)界面惋增。這個(gè)時(shí)候路由的動(dòng)態(tài)配置就派上用場(chǎng)了。
比較常見(jiàn)的調(diào)用方法如下
RouteDemo.getInstance().open("route://test?name=555");
再講述URL跳轉(zhuǎn)之前,必須得先說(shuō)明一下Android下的URL和URI,以及他們之間的關(guān)系和格式,為之后URI解析做下鋪墊改鲫。
URL,URN,URI的關(guān)系 (對(duì)URI比較熟悉的可以跳過(guò)這一段)###
URI在于I(Identifier)是統(tǒng)一資源標(biāo)識(shí)符,可以唯一標(biāo)識(shí)一個(gè)資源诈皿。
URL在于L(Location)是統(tǒng)一資源定位符,可以提供找到該資源的路徑林束。
URN在于N( name)統(tǒng)一資源名稱,通過(guò)名字標(biāo)識(shí)資源。
一般來(lái)說(shuō)URL和URN都是URI的子集,它們兩者共同組成了URI稽亏。
比如https://www.zhihu.com/question/21950864,這一串地址可以唯一標(biāo)識(shí)一個(gè)資源,所以是一個(gè)URI,同時(shí)也可以通過(guò)這個(gè)地址找到資源,所以也是一個(gè)URL壶冒。
還有一種情況,urn:isbn:0-486-27557-4這是一本書(shū)的isbn,可以唯一標(biāo)識(shí)一本書(shū),但我們無(wú)法通過(guò)這一串isbn找到書(shū)籍,所以這里是URI,不是URL,準(zhǔn)確說(shuō)這一串isbn是一個(gè)URN。
URI的結(jié)構(gòu)
URI的幾種劃分形式
基本劃分
[scheme:]scheme-specific-part[#fragment]
進(jìn)一步劃分
[scheme:][//authority][path][?query][#fragment]
再進(jìn)一步劃分
[scheme:][//host:port][path][?query][#fragment]
其中有一些簡(jiǎn)單的規(guī)則
- path可以有多個(gè)截歉,每個(gè)用/連接胖腾,比如
scheme://authority/path1/path2/path3?query#fragment
- query參數(shù)可以帶有對(duì)應(yīng)的值,也可以不帶瘪松,如果帶對(duì)應(yīng)的值用=表示咸作,如:
scheme://authority/path1/path2/path3?id = 1#fragment
,這里有一個(gè)參數(shù)id宵睦,它的值是1 - query參數(shù)可以有多個(gè)记罚,每個(gè)用&連接
scheme://authority/path1/path2/path3?id = 1&name = mingming&old#fragment
這里有三個(gè)參數(shù):
參數(shù)1:id,其值是:1
參數(shù)2:name壳嚎,其值是:mingming
參數(shù)3:old桐智,沒(méi)有對(duì)它賦值,所以它的值是null - 在android中烟馅,除了scheme说庭、authority是必須要有的,其它的幾個(gè)path郑趁、query刊驴、fragment,它們每一個(gè)可以選擇性的要或不要穿撮,但順序不能變缺脉,比如:
其中"path"可不要:scheme://authority?query#fragment
其中"path"和"query"可都不要:scheme://authority#fragment
其中"query"和"fragment"可都不要:scheme://authority/path
"path","query","fragment"都不要:scheme://authority
其中有一些簡(jiǎn)單的規(guī)則
- path可以有多個(gè),每個(gè)用/連接悦穿,比如
scheme://authority/path1/path2/path3?query#fragment
- query參數(shù)可以帶有對(duì)應(yīng)的值攻礼,也可以不帶,如果帶對(duì)應(yīng)的值用=表示栗柒,如:
scheme://authority/path1/path2/path3?id = 1#fragment
礁扮,這里有一個(gè)參數(shù)id技肩,它的值是1 - query參數(shù)可以有多個(gè)灾螃,每個(gè)用&連接
scheme://authority/path1/path2/path3?id=1&name =張三&old#fragment
這里有三個(gè)參數(shù):
參數(shù)1:id胧卤,其值是:1
參數(shù)2:name逝嚎,其值是:張三
參數(shù)3:old,沒(méi)有對(duì)它賦值渠概,所以它的值是null - 在android中逼侦,除了scheme谚攒、authority是必須要有的曙痘,其它的幾個(gè)path芳悲、query立肘、fragment,它們每一個(gè)可以選擇性的要或不要名扛,但順序不能變谅年,比如:
其中"path"可不要:scheme://authority?query#fragment
其中"path"和"query"可都不要:scheme://authority#fragment
其中"query"和"fragment"可都不要:scheme://authority/path
"path","query","fragment"都不要:scheme://authority
做一個(gè)簡(jiǎn)單的例子匹配
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
另外還有兩個(gè)常用的屬性:getPathSegments()
淋纲,getQueryParameter(String key)
List<String> getPathSegments() 會(huì)將整個(gè)path路徑保存下來(lái),并以/符號(hào)作為分隔符
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): 則是根據(jù)key來(lái)獲取對(duì)應(yīng)的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中,即使針對(duì)某一個(gè)KEY不對(duì)它賦值是允許的院究,但在利用getQueryParameter()獲取該KEY對(duì)應(yīng)的值時(shí),獲取到的是null;
匹配URI跳轉(zhuǎn)
我們回過(guò)頭看看之前寫(xiě)的路徑的匹配方式
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
上述這個(gè)字符串來(lái)說(shuō),我希望的是跳轉(zhuǎn)的test
對(duì)照的Activity中,并且攜帶參數(shù)name=555
.
如果用全URL匹配的話,我們可能需要做的是在目標(biāo)Activity上加上類似的標(biāo)注
@Route("route://test?name=555")
這樣跳轉(zhuǎn)是能跳轉(zhuǎn)了,不過(guò)如何攜帶參數(shù)又成了問(wèn)題本涕。
這時(shí)再回去看看URI的結(jié)構(gòu)劃分,取其中一種適用性比較廣泛的如下
[scheme:][//host:port][path][?query][#fragment]
其中
[scheme:][//host:port][path]
這部分就足夠表示一個(gè)唯一的界面資源了业汰,后面的參數(shù)只是區(qū)分當(dāng)前界面所需要顯示的數(shù)據(jù)是那些而已。
就比如一個(gè)UserInfoActivity
不管你傳入的userId
是什么,你所在的view都是UserInfoActivity
菩颖。
所以在匹配一個(gè)外部的URL的時(shí)候,需要匹配的是URL路徑部分,即
[scheme:][//host:port][path]
這一部分
順著個(gè)思路,來(lái)改寫(xiě)RouteDemo中的代碼样漆。
增加一個(gè)checkUrlPath
函數(shù)
//Uri的標(biāo)準(zhǔn)格式 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;
}
}
這里還是采用了一個(gè)簡(jiǎn)單粗暴的方式,當(dāng)scheme
,host
,path
三者都相等的時(shí)候,我們認(rèn)為匹配上了,這里沒(méi)提到port
是因?yàn)槁酚芍型ǔJ褂貌坏?code>port這個(gè)屬性,如果需要的話,可以getAuthority()
去獲取晦闰。
匹配到了path之后,還需要一個(gè)步驟,就是把我們需要的參數(shù)傳遞過(guò)去放祟。使用
是getQueryParameterNames()
和getQueryParameter(queryParameterName)
具體實(shí)現(xiàn)如下
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的函數(shù)
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 {
//通過(guò)反射調(diào)用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的標(biāo)準(zhǔn)格式 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;
}
}
}