概述
隨著Google對HttpClient 摒棄,和Volley的逐漸沒落,OkHttp開始異軍突起,而Retrofit則對okHttp進行了強制依賴。
Retrofit是由Square公司出品的針對于Android和Java的類型安全的Http客戶端,
如果看源碼會發現其實質上就是對okHttp的封裝,使用面向接口的方式進行網絡請求,利用動態生成的代理類封裝了網絡接口請求的底層,
其將請求返回javaBean,對網絡認證 REST API進行了很好對支持此,使用Retrofit將會極大的提高我們應用的網絡體驗。
REST
既然是RESTful架構,那么我們就來看一下什么是REST吧。
REST(REpresentational State Transfer)是一組架構約束條件和原則。
RESTful架構都滿足以下規則:
(1)每一個URI代表一種資源;
(2)客戶端和服務器之間,傳遞這種資源的某種表現層;
(3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現”表現層狀態轉化”。
下面話不多說了,來開始本文的正文吧
1. 需求與前提
base url
默認base url: https://cloud.devwiki.net
測試版 url : https://dev.devwiki.net
私有云版本url: https://private.devwiki.net
rest 版本
- /rest/v1/
- /rest/v2/
- /rest/v3/
需求點
- 大部分接口使用 cloud host, 部分接口使用 private host
- 大部分接口使用 rest/v3 版本, 部分接口使用 v2, v1版本.
- 每個host 都有可能存在 rest v1, v2, v3的接口
2. 實現思路
okhttp 可以添加攔截器, 可在發起訪問前進行攔截, 通常我們會在 攔截器中統一添加 header, 比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class HeaderInterceptor implements Interceptor { private static final String ENCODING_GZIP = "gzip" ; private static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8" ; private static final String HEADER_CONTENT_TYPE = "Content-Type" ; private static final String HEADER_ACCEPT_TYPE = "application/json" ; private static final String HEADER_CONTENT_ENCODING = "Content-Encoding" ; private final static String CHARSET = "UTF-8" ; @Override public Response intercept(Chain chain) throws IOException { Request originRequest = chain.request(); Request.Builder newBuilder = originRequest.newBuilder(); newBuilder.addHeader( "Accept" , HEADER_ACCEPT_TYPE); newBuilder.addHeader( "Accept-Charset" , CHARSET); newBuilder.addHeader( "Accept-Encoding" , ENCODING_GZIP); newBuilder.addHeader( "Accept-Language" , Locale.getDefault().toString().replace( "_" , "-" )); newBuilder.addHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); return chain.proceed(newBuilder.build()); } } |
同理我們也可以在所有請求中添加統一的uuid 或者 key 進行防劫持或者認證. 比如:
1
2
3
4
5
6
7
8
9
10
11
12
|
Request originRequest = chain.request(); if (paramsMap != null ) { HttpUrl originUrl = originRequest.url(); HttpUrl.Builder newBuilder = originUrl.newBuilder(); for (String key : paramsMap.keySet()) { newBuilder.addEncodedQueryParameter(key, paramsMap.get(key)); } HttpUrl newUrl = newBuilder.build(); Request newRequest = originRequest.newBuilder().url(newUrl).build(); return chain.proceed(newRequest); } return chain.proceed(originRequest); |
那么, 同樣我們可以再攔截器中進行host 和 path的替換, 那么怎么替換呢?
3. 實現過程
3.1 定義host 類型和 rest 版本
host類型:
1
2
3
4
5
6
7
8
9
10
11
|
interface HostName { String CLOUD = "CLOUD" ; String PRIVATE = "PRIVATE" ; String DEV = "DEV" ; } interface HostValue { String CLOUD = "https://www.baidu.com" ; String PRIVATE = "https://private.bidu.com" ; String DEV = "https://dev.baidu.com" ; } |
rest 版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
interface RestVersionCode { String EMPTY = "EMPTY" ; String V1 = "V1" ; String V2 = "V2" ; String PRIVATE = "PRIVATE" ; } /** * path 前綴值 */ interface RestVersionValue { String EMPTY = "" ; String V1 = "rest/v1" ; String V2 = "rest/v2" ; String PRIVATE = "rest/private" ; } |
設置一個默認的 host 和 rest 版本, 然后在需要更改host和rest 版本的請求接口處添header, 根據header設置來變更.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
interface BaiduApiService { @GET ( "s" ) Observable<Response<Object>> search( @Query ( "wd" )String wd); @GET ( "s" ) @Headers ({UrlConstants.Header.REST_VERSION_V1}) Observable<Response<Object>> searchChangePath( @Query ( "wd" )String wd); @GET ( "s" ) @Headers ({UrlConstants.Header.HOST_DEV}) Observable<Response<Object>> searchChangeHost( @Query ( "wd" )String wd); @Headers ({UrlConstants.Header.HOST_PRIVATE, UrlConstants.Header.REST_VERSION_PRIVATE}) @GET ( "s" ) Observable<Response<Object>> searchChangeHostPath( @Query ( "wd" )String wd); } |
header 的可選值:
1
2
3
4
5
6
7
8
9
10
11
12
|
interface Header { String SPLIT_COLON = ":" ; String HOST = "HostName" ; String HOST_CLOUD = HOST + SPLIT_COLON + HostName.CLOUD; String HOST_PRIVATE = HOST + SPLIT_COLON + HostName.PRIVATE; String HOST_DEV = HOST + SPLIT_COLON + HostName.DEV; String REST_VERSION = "RestVersion" ; String REST_VERSION_V1 = REST_VERSION + SPLIT_COLON + RestVersionCode.V1; String REST_VERSION_V2 = REST_VERSION + SPLIT_COLON + RestVersionCode.V2; String REST_VERSION_PRIVATE = REST_VERSION + SPLIT_COLON + RestVersionCode.PRIVATE; String REST_VERSION_EMPTY = REST_VERSION + SPLIT_COLON + RestVersionCode.EMPTY; } |
然后是解析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
class RequestInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originRequest = chain.request(); HttpUrl originUrl = originRequest.url(); HttpUrl.Builder newBuilder; String hostType = originRequest.header(UrlConstants.Header.HOST); System.out.println( "hostType:" + hostType); if (hostType != null && hostType.length() > 0 ) { String hostValue = UrlManager.getInstance().getHost(hostType); HttpUrl temp = HttpUrl.parse(hostValue); if (temp == null ) { throw new IllegalArgumentException(hostType + "對應的host地址不合法:" + hostValue); } newBuilder = temp.newBuilder(); } else { newBuilder = new HttpUrl.Builder() .scheme(originUrl.scheme()) .host(originUrl.host()) .port(originUrl.port()); } String restVersion = originRequest.header(UrlConstants.Header.REST_VERSION); System.out.println( "restVersion:" + restVersion); if (restVersion == null ) { restVersion = UrlConstants.RestVersionCode.V2; } String restValue = UrlManager.getInstance().getRest(restVersion); if (restValue.contains( "/" )) { String[] paths = restValue.split( "/" ); for (String path : paths) { newBuilder.addEncodedPathSegment(path); } } else { newBuilder.addEncodedPathSegment(restValue); } for ( int i = 0 ; i < originUrl.pathSegments().size(); i++) { newBuilder.addEncodedPathSegment(originUrl.encodedPathSegments().get(i)); } newBuilder.encodedPassword(originUrl.encodedPassword()) .encodedUsername(originUrl.encodedUsername()) .encodedQuery(originUrl.encodedQuery()) .encodedFragment(originUrl.encodedFragment()); HttpUrl newUrl = newBuilder.build(); System.out.println( "newUrl:" + newUrl.toString()); Request newRequest = originRequest.newBuilder().url(newUrl).build(); return chain.proceed(newRequest); } } |
為了能動態設置host, 我們需要一個map來存儲host 類型和值.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
private Map<String, String> hostMap; private Map<String, String> restMap; private UrlManager() { hostMap = new HashMap<>( 16 ); for (UrlConstants.Host host : UrlConstants.Host.values()) { hostMap.put(host.getName(), host.getValue()); } restMap = new HashMap<>(); for (UrlConstants.Rest rest : UrlConstants.Rest.values()) { restMap.put(rest.getVersion(), rest.getValue()); } } //更新host 的值 public void setHost(String name, String value) { if (hostMap.containsKey(name)) { HttpUrl httpUrl = HttpUrl.parse(value); if (httpUrl == null ) { throw new IllegalArgumentException( "要存入的Host " + name + "對應的value:" + value + "不合法!" ); } hostMap.put(name, value); } else { throw new NoSuchElementException( "沒有找到已經定義的Host名稱:" + name + ",請先在" + "net.devwiki.manager.UrlConstants.Host中定義!" ); } } //根據host 獲取值 public String getHost(String name) { if (!hostMap.containsKey(name)) { throw new NoSuchElementException( "沒有找到已經定義的Host名稱:" + name + ",請先在" + "net.devwiki.manager.UrlConstants.Host中定義!" ); } return hostMap.get(name); } |
這樣就可以動態替換host 和 rest版本了.
4.測試運行
測試代碼:
1
2
3
4
5
6
7
8
9
10
11
|
private static void testRequest() { BaiduRest rest = new BaiduRest(); testDefault(rest); testChangeHost(rest); testChangePath(rest); testChangeHostPath(rest); } |
測試運行結果:
ostType:null
restVersion:null
newUrl:https://www.baidu.com/rest/v2/s?wd=123
九月 07, 2018 11:36:58 上午 okhttp3.internal.platform.Platform log
信息: --> GET https://www.baidu.com/rest/v2/s?wd=123 http/1.1
九月 07, 2018 11:36:58 上午 okhttp3.internal.platform.Platform log
信息: <-- 302 Found https://www.baidu.com/rest/v2/s?wd=123 (83ms, 154-byte body)
九月 07, 2018 11:36:58 上午 okhttp3.internal.platform.Platform log
信息: --> GET http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf http/1.1
九月 07, 2018 11:36:58 上午 okhttp3.internal.platform.Platform log
信息: <-- 200 OK http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf (46ms, unknown-length body)
hostType:DEV
restVersion:null
newUrl:https://dev.baidu.com/rest/v2/s?wd=123
九月 07, 2018 11:36:58 上午 okhttp3.internal.platform.Platform log
信息: --> GET https://dev.baidu.com/rest/v2/s?wd=123 http/1.1
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: <-- 302 Found https://dev.baidu.com/rest/v2/s?wd=123 (154ms, 154-byte body)
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: --> GET http://developer.baidu.com/error.html http/1.1
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: <-- 301 Moved Permanently http://developer.baidu.com/error.html (18ms, 73-byte body)
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: --> GET https://developer.baidu.com/error.html http/1.1
hostType:null
restVersion:V1
newUrl:https://www.baidu.com/rest/v1/s?wd=123
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: <-- 200 OK https://developer.baidu.com/error.html (157ms, unknown-length body)
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: --> GET https://www.baidu.com/rest/v1/s?wd=123 http/1.1
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: <-- 302 Found https://www.baidu.com/rest/v1/s?wd=123 (46ms, 154-byte body)
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: --> GET http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf http/1.1
九月 07, 2018 11:36:59 上午 okhttp3.internal.platform.Platform log
信息: <-- 200 OK http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf (54ms, unknown-length body)
hostType:PRIVATE
restVersion:PRIVATE
newUrl:https://private.bidu.com/rest/private/s?wd=123
結果按照設置進行了host 和 rest 的變更.
5. 項目代碼
項目代碼地址: Dev-Wiki/OkHttpDemo
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:http://blog.devwiki.net/index.php/2018/09/08/change-retrofit-base-url-and-rest-version.html