配置
设置 BaseUrl
HTTP http = HTTP.builder()
.baseUrl("http://api.demo.com") // 设置 BaseUrl
.build();
2
3
该配置全局生效,在配置了BaseUrl
之后,具体的请求便可以省略BaseUrl
部分,使得代码更加简洁,例如:
http.sync("/users").get() // http://api.demo.com/users
http.sync("/auth/signin") // http://api.demo.com/auth/signin
.addBodyPara("username", "Jackson")
.addBodyPara("password", "xxxxxx")
.post(); // POST请求
2
3
4
5
6
在配置了BaseUrl
之后,如有特殊请求任务,仍然可以使用全路径的方式,一点都不妨碍:
http.sync("https://www.baidu.com").get();
支持动态 BaseUrl 吗?
有些同学可能在网上看到某些网络封装框架声明支持 动态 BaseUrl 或者说 动态域名,那 OkHttps 支持吗?
答:OkHttps 是 不会 提供在同一个HTTP
实例上可以动态设置BaseUrl
的功能的。
为什么呢?
答:因为这完全是一种缺陷设计,我们都知道在 Java 中多线程开发很常见,如果 A 线程正兴高采烈的准备向某个地址发起请求时,而 B 线程突然修改了BaseUrl
,那 A 线程不就悲剧了么。所以类似 Retrofit 这些设计成熟的框架都不会提供这种功能的。
那我的应用中确实要用到多个域名,该怎么办呢?
答:可以构建多个HTTP
实例,不同的实例负责不同的域名,如果每个域名的请求都比较少,就是域名多,那便只用一个HTTP
实例,请求较少的域名使用全路径访问即可。
回调执行器
OkHttps 默认所有回调都在 IO线程 执行,如何想改变执行回调的线程时,可以配置回调执行器。例如在Android里,让所有的回调函数都在 UI 线程执行,则可以在构建HTTP
时配置如下:
HTTP http = HTTP.builder()
.callbackExecutor((Runnable run) -> {
runOnUiThread(run); // 在UI线程执行
})
.build();
2
3
4
5
该配置默认 影响所有回调,另 全局监听 不受此影响,更多实现细节可参考 安卓-回调线程切换 章节。
默认报文体类型
HTTP http = HTTP.builder()
.bodyType(OkHttps.JSON) // 修改默认报文体类型为 JSON
.build();
2
3
默认编码
HTTP http = HTTP.builder()
.charset(StandardCharsets.UTF_16) // 修改默认编码,不修改默认值为 UTF-8
.build();
2
3
预处理器
预处理器(Preprocessor
)可以让我们在请求发出之前对请求本身做一些改变,但与OkHttp
的拦截器(Interceptor
)不同:预处理器可以让我们 异步 处理这些问题。
在预处理器中,我们通常可以:
- 统一(或根据标签条件)添加请求头(可同步或异步执行)
- 统一(或根据标签条件)添加请求参数(可同步或异步执行)
- 其它自定义的管理(比如 请求日志、生命周期绑定 等)
并行预处理器
例如,当我们想为请求任务自动添加Token
头信息,而Token
只能通过异步方法requestToken
获取时,这时使用Interceptor
就很难处理了,但可以使用预处理器轻松解决:
HTTP http = HTTP.builder()
.addPreprocessor((PreChain chain) -> {
HttpTask<?> task = chain.getTask(); // 获得当前的HTTP任务
if (!task.isTagged("Auth")) { // 根据标签判断该任务是否需要Token
chain.proceed();
return;
}
requestToken((String token) -> { // 异步获取 Token
task.addHeader("Token", token); // 为任务添加头信息
chain.proceed(); // 继续当前的任务
});
})
.build();
2
3
4
5
6
7
8
9
10
11
12
13
和Interceptor
一样,Preprocessor
也可以添加多个。他们之间的区别如下:
拦截器与预处理器的区别
- 拦截器只能处理同步操作,预处理器支持处理异步操作
- 拦截器都是并行处理请求,预处理器支持串行处理(详见6.5章节)
- 拦截器处理时机在请求前和响应后,预处理器只在请求前,并且先于拦截器执行。关于响应后,OkHttps还提供了全局回调监听(详见6.6章节)
并行预处理器还可以实现更多功能,比如 安卓-生命周期绑定、安卓-自动加载框 等。
串行预处理器(TOKEN问题最佳解决方案)
普通预处理器都是可并行处理的,然而有时我们希望某个预处理器同时只处理一个任务。比如 当Token
过期时我们需要去刷新获取新Token
,而刷新Token
这个操作只能有一个任务去执行,因为如果n
个任务同时执行的话,那么必有n-1
个任务刚刷新得到的Token
可能就立马失效了,而这是我们所不希望的。
为了解决这个问题,OkHttps 提供了串行预处理器,它可以让 HTTP 任务排好队,一个一个地进入预处理器:
HTTP http = HTTP.builder()
.addSerialPreprocessor((PreChain chain) -> {
HttpTask<?> task = chain.getTask();
if (!task.isTagged("Auth")) {
chain.proceed();
return;
}
// 检查过期,若需要则刷新Token
requestTokenAndRefreshIfExpired((String token) -> {
task.addHeader("Token", token);
chain.proceed(); // 调用此方法前,不会有其它任务进入该处理器
});
})
.build();
2
3
4
5
6
7
8
9
10
11
12
13
14
串行预处理器实现了让HTTP任务排队串行处理的功能,但值得一提的是:它并没有因此而阻塞任何线程!
注意
由于 串行预处理器 只能一个一个的处理任务,所以在上述中的requestTokenAndRefreshIfExpired
方法里,若发起网络请求,一定要跳过串行预处理器 [可使用skipSerialPreproc()
方法],例如:
http.async("/oauth/refresh-token")
.skipSerialPreproc() // 跳过串行预处理器
.addBodyPara("refreshToken", "xxxxxx")
.setOnResponse((HttpResult result) -> {
})
.post();
2
3
4
5
6
7
否则就会发生两个 HTTP 任务相互等待谁也执行不了的问题。
如果你使用的是 v1.x 的版本,则可以使用HTTP
实例的request(Request request)
方法发起原生请求,这样也不经过任何预处理器。
关于 TOKEN 的更多处理细节,请参考 安卓-最佳实践 章节。
消息转换器
OkHttps 自 v2.0 后开始支持自定义消息转换器,并且可以添加多个,例如:
HTTP http = HTTP.builder()
.addMsgConvertor(new MyJsonMsgConvertor());
.addMsgConvertor(new MyXmlMsgConvertor());
.build();
2
3
4
配置了消息转换器后,HTTP
实例便具有了序列化和反序列化这些格式数据的能力。
okhttps-gson
里提供了GsonMsgConvertor
okhttps-jackson
里提供了JacksonMsgConvertor
okhttps-fastjson
里提供了FastjsonMsgConvertor
okhttps-xml
里提供了XmlMsgConvertor
建议
我们推荐不想折腾的同学直接使用我们提供的 OkHttps
工具类,因为它会自动发现依赖并配置好这些消息转换器。
反序列化
例如下例中,无论该接口响应的是 JSON 还是 XML,都可以反序列化成功:
// 无论该接口响应的是 JSON 还是 XML,都可以反序列化成功
List<Order> orders = http.sync('/orders')
.get().getBody().toList(Order.class);
2
3
在 Websocket 通讯中,也是如此:
http.webSocket("/redpacket/status") // 监听红包的领取状态
.setOnMessage((WebSocket ws,Message msg) -> {
// 无论该接口响应的是 JSON 还是 XML,都可以反序列化成功
Status status = msg.toBean(Status.class);
})
.listen();
2
3
4
5
6
正序列化
另外,如果要在请求发出阶段正向序列化参数,则需要指定bodyType
参数告诉 OkHttps 你想使用哪一个消息转换器,如:
Order order = createOrder();
http.async('/orders') // 提交订单
.bodyType("json")
.setBodyPara(order) // 以 JSON 格式序列化 Order 对象
.post();
2
3
4
5
或者 XML:
Order order = createOrder();
http.async('/orders') // 提交订单
.bodyType("xml")
.setBodyPara(order) // 以 XML 格式序列化 Order 对象
.post();
2
3
4
5
在 Websocket 里,也是同样的方法:
http.webSocket("/chat")
.bodyType("json")
.setOnOpen((WebSocket ws,HttpResult res) -> {
Hello hello = getHello();
ws.send(hello); // 以 JSON 格式序列化 Hello 对象
})
.listen();
2
3
4
5
6
7
但如果你在 Websocket 通讯到某一个阶段后,突然想换另外一种格式来发送数据了,你还可以这样:
http.webSocket("/chat")
.bodyType("json")
.setOnOpen((WebSocket ws,HttpResult res) -> {
Hello hello = getHello();
ws.send(hello); // 以 JSON 格式序列化 Hello 对象
ws.msgType("xml") // 切换为 XML 格式
ws.send(hello); // 以 XML 格式序列化 Hello 对象
})
.listen();
2
3
4
5
6
7
8
9
10
默认序列化类型
然而大多数情况下,我们都使用一种消息转换器,比如 json,这时候你可以为bodyType
配置一个默认值:
HTTP http = HTTP.builder()
// 修改 bodyType 的默认值为 json,若不修改,则默认是 form
.bodyType("json");
.addMsgConvertor(new MyJsonMsgConvertor());
.build();
2
3
4
5
提示
只有请求报文体的正向序列化才会受到bodyType
的影响,对于响应报文体的反向序列化则不受其影响。
表单序列化
OkHttps 不但能做 JSON 或 XML 的序列化,并且还能做表单参数的序列化,并且自带了一个FormConvertor
,它可以为任意DataConvertor
(MsgConvertor
的父接口)赋予表单序列化的能力,例如你已经实现了一个MyJsonDataConvertor
,可以这么做:
// 这里并不要求是一个 JSON 转换器,XML 也可以
// 有啥就给啥,效果是一样的
DataConvertor convertor = new MyJsonDataConvertor();
HTTP http = HTTP.builder()
.addMsgConvertor(new MsgConvertor.FormConvertor(convertor));
.build();
2
3
4
5
6
7
这样,你再做表单请求时,就可以直接扔进一个对象,它将自动完成序列化的事情:
Order order = createOrder();
http.async('/orders') // 提交订单
.bodyType("form")
.setBodyPara(order) // 以 表单 格式序列化 Order 对象
.post();
2
3
4
5
一般情况下,如果你有实现了一个MsgConvertor
,我们推荐把它和FormConvertor
都添加进去,例如:
MsgConvertor convertor = new MyJsonMsgConvertor();
HTTP http = HTTP.builder()
.addMsgConvertor(convertor);
.addMsgConvertor(new MsgConvertor.FormConvertor(convertor));
.build();
2
3
4
5
6
如果你添加了官方提供的MsgConvertor
依赖包(例如:okhttps-jackson
等),在构建实例时,还可以这样直接注入:
HTTP.Builder builder = HTTP.builder()
// 自动完成依赖中的 MsgConvertor 和 FormConvertor 的注入
ConvertProvider.inject(builder);
HTTP http = builder.build();
2
3
4
TIP
如果你使用的是OkHttps
或HttpUtils
工具类,它们都会自动配置MsgConvertor
和FormConvertor
,无需手动配置
在 v2.0.0.RC 版本中FormConvertor
的名字是MsgConvertor.FormMsgConvertor
底层配置
OkHttps 提供的每个消息转换器都支持深度配置,比如:
GsonMsgConvertor
在构造函数里可接受一个配置好的Gson
对象。JacksonMsgConvertor
在构造函数里可接受一个配置好的ObjectMapper
对象。XmlMsgConvertor
在构造函数里可接受一个配置好的DocumentBuilderFactory
对象。- 你还可以自己实现一个
MsgConvertor
全局监听
全局回调监听
全局回调是实际开发中经常需要的功能,比如对服务器响应的状态码进行统一处理等,同时 OkHttps 的全局回调还具有 回调阻断 的功能:
HTTP http = HTTP.builder()
.responseListener((HttpTask<?> task, HttpResult result) -> {
// 所有异步请求(包括 WebSocket)响应后都会走这里
// 返回 true 表示继续执行 task 的 OnResponse 回调,
// 返回 false 则表示不再执行,即 阻断
return true;
})
.completeListener((HttpTask<?> task, State state) -> {
// 所有异步请求(包括 WebSocket)执行完都会走这里
// 返回 true 表示继续执行 task 的 OnComplete 回调,
// 返回 false 则表示不再执行,即 阻断
return true;
})
.exceptionListener((HttpTask<?> task, IOException error) -> {
// 所有异步请求(包括 WebSocket)发生异常都会走这里
// 返回 true 表示继续执行 task 的 OnException 回调,
// 返回 false 则表示不再执行,即 阻断
return true;
})
.build();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
回调阻断 其实是日常开发中比较常见的需求,比如:
只有当接口响应的状态码在 [200, 300) 之间时,应用才做具体的业务处理,其它则一律向用户提示接口返回的错误信息,则可以这么做:
HTTP http = HTTP.builder()
.responseListener((HttpTask<?> task, HttpResult result) -> {
if (result.isSuccessful()) {
return true; // 继续接口的业务处理
}
showApiMsgToUser(result); // 向用户展示接口的错误信息
return false; // 阻断
})
.build();
2
3
4
5
6
7
8
9
然后具体的请求,就可以专注于业务处理,而不需担心接口的状态出错,比如:
Order order = createOrder(); // 订单信息
http.async('/orders') // 提交订单
.setBodyPara(order)
.setOnResponse((HttpResult result) -> {
// 进入该回调,则表示订单已提交成功,就可以放心的做接下来的业务处理
// 比如去发起支付:
startPay(result.getBody().toBean(PayInfo.class));
})
.post();
2
3
4
5
6
7
8
9
10
全局回调监听与拦截器的异同:
- 拦截器可以添加多个,全局回调监听分三种,每种最多添加一个
- 拦截器处的理时机在请求前和响应后,全局回调监听只在响应后,并且晚于拦截器
- 全局回调监听可以 阻断(return false)某个请求的具体回调,而拦截器不能
WARNING
如果你开发的是安卓应用,我们强烈建议你添加 全局异常监听,这样当你在某个请求中忘记使用OnException
或nothrow
,而它又发生了超时或网络异常时,不至于让程序崩溃。
全局下载监听
HTTP http = HTTP.builder()
.downloadListener((HttpTask<?> task, Download download) -> {
// 所有下载在开始之前都会先走这里
Ctrl ctrl = download.getCtrl(); // 下载控制器
})
.build();
2
3
4
5
6
7
配置 OkHttpClient
与其他封装 OkHttp3 的框架不同,OkHttps 并不会遮蔽 OkHttp3 本身就很好用的功能。
连接池(ConnectionPool)
HTTP http = HTTP.builder()
.config((OkHttpClient.Builder builder) -> {
// 配置连接池 最大空闲 10 个连接(不配置默认为 5),保活间隔 5 分钟
builder.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
})
.build();
2
3
4
5
6
超时时间(Timeout)
超时时间可分三种:
- 连接超时时间:客户端连上服务器的最大时间;
- 写入超时时间:客户端在连上服务器后再向服务器写入请求数据的最大时间;
- 读取超时时间:客户端在写完请求数据后等待服务器响应数据的最大时间。
它们可通过如下方式配置:
HTTP http = HTTP.builder()
.config((OkHttpClient.Builder builder) -> {
// 连接超时时间(默认10秒)
builder.connectTimeout(10, TimeUnit.SECONDS);
// 写入超时时间(默认10秒)
builder.writeTimeout(10, TimeUnit.SECONDS);
// 读取超时时间(默认10秒)
builder.readTimeout(10, TimeUnit.SECONDS);
})
.build();
2
3
4
5
6
7
8
9
10
拦截器(Interceptor)
HTTP http = HTTP.builder()
.config((OkHttpClient.Builder builder) -> {
// 添加拦截器
builder.addInterceptor((Chain chain) -> {
Request request = chain.request();
// 必须同步返回,拦截器内无法执行异步操作
return chain.proceed(request);
});
})
.build();
2
3
4
5
6
7
8
9
10
例如添加一个日志拦截器:
HTTP http = HTTP.builder()
.config((OkHttpClient.Builder builder) -> {
// 需添加依赖:com.squareup.okhttp3:logging-interceptor:3.14.9
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BASIC);
// 添加日志拦截器
builder.addInterceptor(logging);
})
.build();
2
3
4
5
6
7
8
9
SSL(Https | WSS)
参见:有疑必看 > 支持 SSL(HTTPS)吗? 章节。
Cookie 自动管理
参见:有疑必看 > 支持 Cookie 吗? 章节。
代理(Proxy)
参见:有疑必看 > 支持代理(Proxy)吗? 章节。
缓存(Cache)
参见:有疑必看 > 支持缓存(Cache)吗? 章节。
工具类(OkHttps)
工具类的配置请参见 起步 > 配置 OkHttps 章节。
工具类 OkHttps
可 高度自定义,前文讲述的任何关于 HTTP
实例的自定义配置,都可以在 配置类 的 with(HTTP.Builder builder)
方法中做同样的配置,比如配置拦截器:
@Override
public void with(HTTP.Builder builder) {
// 这里的 builder 对象就相当于前文各种配置中的 HTTP.builder() 返回的对象
builder.config(b -> {
// 添加拦截器
b.addInterceptor((Chain chain) -> {
Request request = chain.request();
return chain.proceed(request);
});
});
}
2
3
4
5
6
7
8
9
10
11