繼上一篇 手把手帶你擼一個路由(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。
比如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;
}
}
}
複製程式碼