如何动态改变Retrofit的base url和rest版本详解

概述

随着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, 比如:

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 进行防劫持或者认证. 比如:

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类型:

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";
}

设置一个默认的 host 和 rest 版本, 然后在需要更改host和rest 版本的请求接口处添header, 根据header设置来变更.

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 + "对应的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 类型和值.

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.测试运行

测试代码:

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 (本地下载)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android Retrofit 中文乱码问题的解决办法

    Android Retrofit 中文乱码问题的解决办法 使用retrofit和rxjava,提交数据时需注意,当数据中有中文时,传到后台,可能会是乱码,需处理: 解决: 1.GET请求改成POST; 2.参数Field改成Query 3.加上@FormUrlEncoded 如下: @FormUrlEncoded @POST("/test/test") Call<Response> register(@Field("name") String name)

  • Android如何通过Retrofit提交Json格式数据

    本文将介绍如何通过retrofit库post一串json格式的数据.首先post的json数据格式如下: { "Id": "string", "DeviceId": "string", "Name": "string", "SumDistance": 0, "RouteNo": "string", "SumPoints

  • Android网络请求框架Retrofit详解

    介绍: Retrofit 是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求.本文使用Retrofit2.0.0版本进行实例演示. 使用Retrofit可以进行GET,POST,PUT,DELETE等请求方式. 同步请求:需要在子线程中完成,会阻塞主线程. Response response = call.execute().body(); 异步请求:请

  • 为Retrofit统一添加post请求的默认参数的方法

    最近在学习使用Retrofit,并尝试将之引入到现有的项目中来.大家都知道,在Http请求中我们使用 Content-Type 来指定不同格式的请求信息: APP_FORM_URLENCODED("application/x-www-form-urlencoded"), APP_JSON("application/json"), APP_OCTET_STREAM("application/octet-stream"), MULTIPART_FOR

  • Android app开发中Retrofit框架的初步上手使用

    Retrofit 2.0 先来说一下Retrofit 2.0版本中一些引人注意的地方. 在Retrofit 2.0中,最大的改动莫过于减小库的体积,首先,Retrofit 2.0去掉了对所有的HTTP客户端的兼容,而钟情于OkHttpClient一个,极大地减少了各种适配代码,原因一会儿说;其次,拆库,比如将对RxJava的支持设置为可选(需要额外引入库):再比如将各个序列化反序列化转换器支持设置为可选(需要额外引入库).于2.0抛弃HttpClient和HttpURLConnection,为了

  • 简略分析Android的Retrofit应用开发框架源码

    面对一个项目,对于Android应用开发框架的选择,我想过三种方案: 1.使用Loader + HttpClient + GreenDao + Gson + Fragment,优点是可定制性强,由于使用Google家自己的Loader和LoaderManager,代码健壮性强. 缺点是整套代码学习成本较高,使用过程中样板代码较多,(比如每一个Request都需要产生一个新类) 2.Volley,作为Google在IO大会上得瑟过的一个网络库,其实不算什么新东西(2013 IO发布),使用较为简单

  • Retrofit实现图文上传至服务器

    前言:现在大多数的项目中都涉及图片+文字上传了,下面请详见实现原理: 开发环境:AndroidStudio 1.引入依赖: compile 'com.squareup.retrofit2:retrofit:2.1.0'   2.网络权限: <uses-permission android:name="android.permission.INTERNET" />   3.创建上传对象OkHttpClient : private static final OkHttpClie

  • 详解Retrofit2.0 公共参数(固定参数)

    本文主要介绍了Retrofit2.0 公共参数(固定参数),分享给大家,具体如下: 请先阅读: Retrofit 动态参数(非固定参数.非必须参数)(Get.Post请求) 在实际项目中,对于有需要统一进行公共参数添加的网络请求,可以使用下面的代码来实现: RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(ctx).setRequestInterceptor(new RequestInterceptor() { @O

  • android Retrofit2+okHttp3使用总结

    使用前准备 Build.gradle文件配置 dependencies配置 compile 'com.squareup.retrofit2:retrofit:2.0.0' compile 'com.squareup.retrofit2:converter-gson:2.0.0' compile 'com.squareup.okhttp3:logging-interceptor:3.2.0' 网络框架搭建 服务创建类封装(HTTP): public class ServiceGenerator {

  • 详解RxJava2 Retrofit2 网络框架简洁轻便封装

    前言 RxJava2.Retrofit2火了有一段时间了,前段时间给公司的项目引入了这方面相关的技术,在此记录一下相关封装的思路. 需求 封装之前要先明白需要满足哪些需求. RxJava2衔接Retrofit2 Retrofit2网络框架异常的统一处理 兼容fastjson(可选) RxJava2内存泄漏的处理 异步请求加入Loading Dialog 依赖 implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' implementation

随机推荐