配置

设置 BaseUrl

HTTP http = HTTP.builder()
        .baseUrl("http://api.demo.com")    // 设置 BaseUrl
        .build();
1
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请求
1
2
3
4
5
6

在配置了BaseUrl之后,如有特殊请求任务,仍然可以使用全路径的方式,一点都不妨碍:

http.sync("https://www.baidu.com").get();
1

支持动态 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();
1
2
3
4
5

该配置默认 影响所有回调,另 全局监听 不受此影响,更多实现细节可参考 安卓-回调线程切换 章节。

默认报文体类型

HTTP http = HTTP.builder()
        .bodyType(OkHttps.JSON)                 // 修改默认报文体类型为 JSON
        .build();
1
2
3

默认编码

HTTP http = HTTP.builder()
        .charset(StandardCharsets.UTF_16)       // 修改默认编码,不修改默认值为 UTF-8
        .build();
1
2
3

预处理器

预处理器(Preprocessor)可以让我们在请求发出之前对请求本身做一些改变,但与OkHttp的拦截器(Interceptor)不同:预处理器可以让我们 异步 处理这些问题。

在预处理器中,我们通常可以:

  1. 统一(或根据标签条件)添加请求头(可同步或异步执行)
  2. 统一(或根据标签条件)添加请求参数(可同步或异步执行)
  3. 其它自定义的管理(比如 请求日志、生命周期绑定 等)

并行预处理器

例如,当我们想为请求任务自动添加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();
1
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();
1
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();
1
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();
1
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);
1
2
3

在 Websocket 通讯中,也是如此:

http.webSocket("/redpacket/status") // 监听红包的领取状态
        .setOnMessage((WebSocket ws,Message msg) -> {
            // 无论该接口响应的是 JSON 还是 XML,都可以反序列化成功
            Status status = msg.toBean(Status.class);
        })
        .listen();
1
2
3
4
5
6

正序列化

另外,如果要在请求发出阶段正向序列化参数,则需要指定bodyType参数告诉 OkHttps 你想使用哪一个消息转换器,如:

Order order = createOrder();
http.async('/orders')           // 提交订单
        .bodyType("json")
        .setBodyPara(order)     // 以 JSON 格式序列化 Order 对象
        .post();
1
2
3
4
5

或者 XML:

Order order = createOrder();
http.async('/orders')           // 提交订单
        .bodyType("xml")
        .setBodyPara(order)     // 以 XML 格式序列化 Order 对象
        .post();
1
2
3
4
5

在 Websocket 里,也是同样的方法:

http.webSocket("/chat") 
        .bodyType("json")
        .setOnOpen((WebSocket ws,HttpResult res) -> {
            Hello hello = getHello();
            ws.send(hello);     // 以 JSON 格式序列化 Hello 对象
        })
        .listen();
1
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();
1
2
3
4
5
6
7
8
9
10

默认序列化类型

然而大多数情况下,我们都使用一种消息转换器,比如 json,这时候你可以为bodyType配置一个默认值:

HTTP http = HTTP.builder()
        // 修改 bodyType 的默认值为 json,若不修改,则默认是 form
        .bodyType("json");      
        .addMsgConvertor(new MyJsonMsgConvertor());
        .build();
1
2
3
4
5

提示

只有请求报文体的正向序列化才会受到bodyType的影响,对于响应报文体的反向序列化则不受其影响。

表单序列化

OkHttps 不但能做 JSON 或 XML 的序列化,并且还能做表单参数的序列化,并且自带了一个FormConvertor,它可以为任意DataConvertorMsgConvertor的父接口)赋予表单序列化的能力,例如你已经实现了一个MyJsonDataConvertor,可以这么做:

// 这里并不要求是一个 JSON 转换器,XML 也可以
// 有啥就给啥,效果是一样的
DataConvertor convertor = new MyJsonDataConvertor();

HTTP http = HTTP.builder()
        .addMsgConvertor(new MsgConvertor.FormConvertor(convertor));
        .build();
1
2
3
4
5
6
7

这样,你再做表单请求时,就可以直接扔进一个对象,它将自动完成序列化的事情:

Order order = createOrder();
http.async('/orders')           // 提交订单
        .bodyType("form")
        .setBodyPara(order)     // 以 表单 格式序列化 Order 对象
        .post();
1
2
3
4
5

一般情况下,如果你有实现了一个MsgConvertor,我们推荐把它和FormConvertor都添加进去,例如:

MsgConvertor convertor = new MyJsonMsgConvertor();

HTTP http = HTTP.builder()
        .addMsgConvertor(convertor);
        .addMsgConvertor(new MsgConvertor.FormConvertor(convertor));
        .build();
1
2
3
4
5
6

如果你添加了官方提供的MsgConvertor依赖包(例如:okhttps-jackson等),在构建实例时,还可以这样直接注入:

HTTP.Builder builder = HTTP.builder()
// 自动完成依赖中的 MsgConvertor 和 FormConvertor 的注入
ConvertProvider.inject(builder);
HTTP http = builder.build();
1
2
3
4

TIP

如果你使用的是OkHttpsHttpUtils工具类,它们都会自动配置MsgConvertorFormConvertor,无需手动配置

在 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();
1
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();
1
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();
1
2
3
4
5
6
7
8
9
10

全局回调监听与拦截器的异同:

  • 拦截器可以添加多个,全局回调监听分三种,每种最多添加一个
  • 拦截器处的理时机在请求前和响应后,全局回调监听只在响应后,并且晚于拦截器
  • 全局回调监听可以 阻断(return false)某个请求的具体回调,而拦截器不能

WARNING

如果你开发的是安卓应用,我们强烈建议你添加 全局异常监听,这样当你在某个请求中忘记使用OnExceptionnothrow,而它又发生了超时或网络异常时,不至于让程序崩溃。

全局下载监听

HTTP http = HTTP.builder()
        .downloadListener((HttpTask<?> task, Download download) -> {
            // 所有下载在开始之前都会先走这里
            Ctrl ctrl = download.getCtrl();         // 下载控制器
            
        })
        .build();
1
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();
1
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();
1
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();
1
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();
1
2
3
4
5
6
7
8
9

SSL(Https | WSS)

参见:有疑必看 > 支持 SSL(HTTPS)吗? 章节。

参见:有疑必看 > 支持 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);
        });
    });
}
1
2
3
4
5
6
7
8
9
10
11