如何動(dòng)態(tài)改變Retrofit的base url和rest版本詳解
概述
隨著Google對(duì)HttpClient 摒棄,和Volley的逐漸沒(méi)落,OkHttp開(kāi)始異軍突起,而Retrofit則對(duì)okHttp進(jìn)行了強(qiáng)制依賴。
Retrofit是由Square公司出品的針對(duì)于Android和Java的類型安全的Http客戶端,
如果看源碼會(huì)發(fā)現(xiàn)其實(shí)質(zhì)上就是對(duì)okHttp的封裝,使用面向接口的方式進(jìn)行網(wǎng)絡(luò)請(qǐng)求,利用動(dòng)態(tài)生成的代理類封裝了網(wǎng)絡(luò)接口請(qǐng)求的底層,
其將請(qǐng)求返回javaBean,對(duì)網(wǎng)絡(luò)認(rèn)證 REST API進(jìn)行了很好對(duì)支持此,使用Retrofit將會(huì)極大的提高我們應(yīng)用的網(wǎng)絡(luò)體驗(yàn)。
REST
既然是RESTful架構(gòu),那么我們就來(lái)看一下什么是REST吧。
REST(REpresentational State Transfer)是一組架構(gòu)約束條件和原則。
RESTful架構(gòu)都滿足以下規(guī)則:
(1)每一個(gè)URI代表一種資源;
(2)客戶端和服務(wù)器之間,傳遞這種資源的某種表現(xiàn)層;
(3)客戶端通過(guò)四個(gè)HTTP動(dòng)詞,對(duì)服務(wù)器端資源進(jìn)行操作,實(shí)現(xiàn)”表現(xiàn)層狀態(tài)轉(zhuǎn)化”。
下面話不多說(shuō)了,來(lái)開(kāi)始本文的正文吧
1. 需求與前提
base url
默認(rèn)base url: https://cloud.devwiki.net
測(cè)試版 url : https://dev.devwiki.net
私有云版本url: https://private.devwiki.net
rest 版本
- /rest/v1/
- /rest/v2/
- /rest/v3/
需求點(diǎn)
- 大部分接口使用 cloud host, 部分接口使用 private host
- 大部分接口使用 rest/v3 版本, 部分接口使用 v2, v1版本.
- 每個(gè)host 都有可能存在 rest v1, v2, v3的接口
2. 實(shí)現(xiàn)思路
okhttp 可以添加攔截器, 可在發(fā)起訪問(wèn)前進(jìn)行攔截, 通常我們會(huì)在 攔截器中統(tǒng)一添加 header, 比如:
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());
}
}
同理我們也可以在所有請(qǐng)求中添加統(tǒng)一的uuid 或者 key 進(jìn)行防劫持或者認(rèn)證. 比如:
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);
那么, 同樣我們可以再攔截器中進(jìn)行host 和 path的替換, 那么怎么替換呢?
3. 實(shí)現(xiàn)過(guò)程
3.1 定義host 類型和 rest 版本
host類型:
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 版本:
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";
}
設(shè)置一個(gè)默認(rèn)的 host 和 rest 版本, 然后在需要更改host和rest 版本的請(qǐng)求接口處添header, 根據(jù)header設(shè)置來(lái)變更.
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 的可選值:
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;
}
然后是解析:
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 + "對(duì)應(yīng)的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);
}
}
為了能動(dòng)態(tài)設(shè)置host, 我們需要一個(gè)map來(lái)存儲(chǔ)host 類型和值.
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 + "對(duì)應(yīng)的value:"
+ value + "不合法!");
}
hostMap.put(name, value);
} else {
throw new NoSuchElementException("沒(méi)有找到已經(jīng)定義的Host名稱:" + name + ",請(qǐng)先在" +
"net.devwiki.manager.UrlConstants.Host中定義!");
}
}
//根據(jù)host 獲取值
public String getHost(String name) {
if (!hostMap.containsKey(name)) {
throw new NoSuchElementException("沒(méi)有找到已經(jīng)定義的Host名稱:" + name + ",請(qǐng)先在" +
"net.devwiki.manager.UrlConstants.Host中定義!");
}
return hostMap.get(name);
}
這樣就可以動(dòng)態(tài)替換host 和 rest版本了.
4.測(cè)試運(yùn)行
測(cè)試代碼:
private static void testRequest() {
BaiduRest rest = new BaiduRest();
testDefault(rest);
testChangeHost(rest);
testChangePath(rest);
testChangeHostPath(rest);
}
測(cè)試運(yùn)行結(jié)果:
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
結(jié)果按照設(shè)置進(jìn)行了host 和 rest 的變更.
5. 項(xiàng)目代碼
項(xiàng)目代碼地址: Dev-Wiki/OkHttpDemo (本地下載)
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- 簡(jiǎn)略分析Android的Retrofit應(yīng)用開(kāi)發(fā)框架源碼
- Android app開(kāi)發(fā)中Retrofit框架的初步上手使用
- android Retrofit2+okHttp3使用總結(jié)
- 詳解RxJava2 Retrofit2 網(wǎng)絡(luò)框架簡(jiǎn)潔輕便封裝
- Android Retrofit 中文亂碼問(wèn)題的解決辦法
- 為Retrofit統(tǒng)一添加post請(qǐng)求的默認(rèn)參數(shù)的方法
- Android網(wǎng)絡(luò)請(qǐng)求框架Retrofit詳解
- Android如何通過(guò)Retrofit提交Json格式數(shù)據(jù)
- Retrofit實(shí)現(xiàn)圖文上傳至服務(wù)器
- 詳解Retrofit2.0 公共參數(shù)(固定參數(shù))
相關(guān)文章
使用log4j2自定義配置文件位置和文件名(附log4j2.xml配置實(shí)例)
這篇文章主要介紹了使用log4j2自定義配置文件位置和文件名(附log4j2.xml配置實(shí)例),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
MybatisPlus創(chuàng)建時(shí)間不想用默認(rèn)值的問(wèn)題
MybatisPlus通過(guò)FieldFill注解和MpMetaObjectHandler類支持自動(dòng)填充字段功能,特別地,可以設(shè)置字段在插入或更新時(shí)自動(dòng)填充創(chuàng)建時(shí)間和更新時(shí)間,但在特定場(chǎng)景下,如導(dǎo)入數(shù)據(jù)時(shí),可能需要自定義創(chuàng)建時(shí)間2024-09-09
@Value如何獲取yml和properties配置參數(shù)
這篇文章主要介紹了@Value如何獲取yml和properties配置參數(shù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
springboot集成springCloud中g(shù)ateway時(shí)啟動(dòng)報(bào)錯(cuò)的解決
這篇文章主要介紹了springboot集成springCloud中g(shù)ateway時(shí)啟動(dòng)報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java局部?jī)?nèi)部類原理與用法實(shí)例分析
這篇文章主要介紹了Java局部?jī)?nèi)部類原理與用法,結(jié)合實(shí)例形式分析了Java局部?jī)?nèi)部類功能、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-09-09
Java 堆內(nèi)存與棧內(nèi)存詳細(xì)介紹
這篇文章主要介紹了Java 堆內(nèi)存與棧內(nèi)存詳細(xì)介紹的相關(guān)資料,這里對(duì)java 的堆內(nèi)存和棧內(nèi)存進(jìn)行了詳細(xì)的分析,需要的朋友可以參考下2016-11-11
POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析
這篇文章主要介紹了POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java游戲俄羅斯方塊的實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了Java游戲俄羅斯方塊的實(shí)現(xiàn)實(shí)例的相關(guān)資料,這里實(shí)現(xiàn)簡(jiǎn)單的俄羅斯方塊幫助大家學(xué)習(xí)理解基礎(chǔ)知識(shí),需要的朋友可以參考下2017-08-08
Spring Boot如何配置內(nèi)置Tomcat的maxPostSize值
這篇文章主要介紹了Spring Boot如何配置內(nèi)置Tomcat的maxPostSize值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

