OkHttp3工具使用实例细节及注意点

目录

一、简介

二、使用实例

三、使用细节

1.错误使用示例

2.正确使用示例

2.1 比较粗暴的方式

2.2 正确的方式


一、简介

开发中我们肯定会遇到一个系统需要调用另外一个系统接口的情况,此时如果让我们自己来手写整个调用工具,想必是非常麻烦的。

OkHttp3就是为了解决这样场景的一个默认高效的HTTP工具包。它有以下特点:

  1. HTTP/2支持允许对同一主机的所有请求共享一个套接字;
  2. 连接池减少了请求延迟(如果HTTP/2不可用);
  3. 透明的GZIP压缩了下载文件的大小;
  4. 响应缓存完全避免了网络中的重复请求。

当网络出现问题时,OkHttp会不会中断:它会从常见的连接问题中悄悄地恢复过来。如果服务有多个IP地址,第一个连接失败,OkHttp将尝试备用地址。这对于IPv4+IPv6和托管在冗余数据中心中的服务是必要的。OkHttp支持现代TLS特性(TLS 1.3、ALPN、证书固定)。可以将其配置为回退以实现广泛的连接。

使用OkHttp很容易。它的请求/响应API是用构建器Builder和不变性设计的。它同时支持同步阻塞调用和异步调用。

二、使用实例

自己写的一个使用实例代码:

public class HttpUtils {

    private static final char QUESTION_MARK = '?';
    private static final char EQUALS_SIGN = '=';
    private static final char AND_SIGN = '&';
    private static final MediaType JSON_UTF8 = 
            MediaType.get("application/json; charset=utf-8");
    private static final String EMPTY_JSON = "{}";

    private static final OkHttpClient CLIENT = new OkHttpClient();

    /**
     * 使用GET方法请求url,返回string
     *
     * @param url
     * @param param
     * @return
     * @throws Exception
     */
    public static String getStringReturnFromMap(String url, 
            Map<String, Object> param) throws Exception {
        return executeClientCall(buildGetRequest(url, param));
    }

    /**
     * 使用GET方法请求url,返回map
     *
     * @param url
     * @param param
     * @return
     * @throws Exception
     */
    public static Map<String, Object> getMapReturnFromMap(String url, 
            Map<String, Object> param) throws Exception {
        String result = getStringReturnFromMap(url, param);
        if (StringUtils.isEmpty(result)) {
            return new HashMap<>(2);
        }
        return JSONObject.parseObject(result, Map.class);
    }

    /**
     * 使用POST方法请求url,返回string
     *
     * @param url
     * @param param
     * @return
     * @throws Exception
     */
    public static String postStringReturnFromMap(String url, 
            Map<String, Object> param) throws Exception {
        return executeClientCall(buildPostRequest(url, 
                CollectionUtils.isEmpty(param) ? 
                        EMPTY_JSON : JSONObject.toJSONString(param)));
    }

    /**
     * 使用POST方法请求url,返回map
     *
     * @param url
     * @param param
     * @return
     * @throws Exception
     */
    public static Map<String, Object> postMapReturnFromMap(String url, 
            Map<String, Object> param) throws Exception {
        String result = postStringReturnFromMap(url, param);
        if (StringUtils.isEmpty(result)) {
            return new HashMap<>(2);
        }
        return JSONObject.parseObject(result, Map.class);
    }

    /**
     * 构造GET请求
     *
     * @param url
     * @param param
     * @return
     */
    private static Request buildGetRequest(String url, 
            Map<String, Object> param) {
        if (CollectionUtils.isEmpty(param)) {
            return new Request.Builder().url(url).build();
        }

        // 判断?
        url = url.charAt(url.length() - 1) == QUESTION_MARK ? 
                url : url + QUESTION_MARK;
        StringBuilder getUrl = new StringBuilder(url);
        // 拼装GET参数
        param.forEach((k, v) -> getUrl.append(k).append(EQUALS_SIGN)
                .append(v).append(AND_SIGN));
        url = getUrl.substring(0, getUrl.length() - 1);
        return new Request.Builder().url(url).build();
    }

    /**
     * 构造POST请求
     *
     * @param url
     * @param json
     * @return
     */
    private static Request buildPostRequest(String url, String json) {
        return new Request.Builder().url(url)
                .post(RequestBody.create(JSON_UTF8, json)).build();
    }

    /**
     * 执行request
     *
     * @param request
     * @return
     * @throws Exception
     */
    private static String executeClientCall(Request request) 
            throws Exception{
        Response response = CLIENT.newCall(request).execute();

        String bodyString;
        if (response.isSuccessful() && response.body() != null && 
            !StringUtils.isEmpty(bodyString = response.body().string())) {
            return bodyString;
        }

        return null;
    }
}

三、使用细节

OkHttp3使用时有个坑需要特别注意,因为OkHttp3使用时每个OkHttpClient类里面是维护了一个ConnectionPool线程池的,因此使用时没必要为该类去再使用池管理它。特别注意:使用时每一个接口链接不能使用一次OkHttpClient就实例化一遍,这样并发量大的时候很容易造成Socket套接字用完以及内存溢出导致宕机。

1.错误使用示例

错误的使用方法:

private static String executeClientCall(Request request) throws Exception{
    Response response = new OkHttpClient().newCall(request).execute();
    String bodyString;
    if (response.isSuccessful() && response.body() != null 
        && !StringUtils.isEmpty(bodyString = response.body().string())) {
        return bodyString;
    }
    return null;
}

每次调用都创建一个新的OkHttpClient对象,这样使用时JVM的状态如下图:

这张图是使用JVM自带的JConsole可视化得到的,程序模拟了500个线程每个线程只请求1000次的情景,可以明显的看到线程数量和CPU占有率是分三个阶段的:

  1. 第一个阶段为刚开始请求时,程序还尚能处理的过来,但是当OkHttpClient实例化的数量过多以及其对象存活时间(默认五分钟)尚未结束依然保留着Connection链接,占用套接字资源,导致线程和CPU占用率会突然升高;
  2. 第二个阶段便是CPU占用率突然降到0,线程数量也趋于平缓,此时由于套接字资源全部被占用,且没有被释放,会导致无法访问到外面的机器,连接超时,因此CPU占用率会完全降下来,而线程数却保持不变;
  3. 当第一批的OkHttpClient实例由于超时被处理后,第二批便会开始执行,但是此时Socket套接字资源已经被完全占用完,没有新的资源,此时就会一直抛异常No buffer space available (maximum connections reached?): connect甚至是Address Alreay in use异常。导致不停打印异常栈,再加上刚刚的资源没有被完全释放,最终导致了CPU占用率飙升,况且到此为止,程序处理的数量才完成了30/500,如果一直下去服务器宕机是必然的的。

2.正确使用示例

2.1 比较粗暴的方式

会造成内存溢出进而宕机是因为每个Connection存活的时间实在是太长了,因此占用了很多的系统资源,那么我们直接把每个Connection的时间改成1s,是不是就能解决这个问题了呢?

使用代码如下:

private static String executeClientCall(Request request) throws Exception{
    Response response = new OkHttpClient()
            .newBuilder()
            .connectionPool(new ConnectionPool(5, 1, TimeUnit.SECONDS))
            .build().newCall(request).execute();
    String bodyString;
    if (response.isSuccessful() && response.body() != null 
        && !StringUtils.isEmpty(bodyString = response.body().string())) {
        return bodyString;
    }
    return null;
}

每次依然是创建一个新的OkHttpClient,只是存活时间改成了1s,效果图如下:

可以看到,这次运行了4min钟CPU占用率一直在50%左右,而线程数经过第一次的飙升到2500+之后伺候就一直没超过1000,最关键的是执行完1/5左右的请求数之后,系统虽然也被占用了很多资源,但是机器还能够稳定运行,至少不会宕机,因此这种策略还是可取的。但是这种方式还是会造成Address already in use以及套接字缓存数量被用光的异常。

2.2 正确的方式

要正确使用OkHttp3也很简单:将OkHttpClient换成单例存在工具类中即可。如下:

private static final OkHttpClient CLIENT = new OkHttpClient();
private static String executeClientCall(Request request) throws Exception{
    Response response = CLIENT.newCall(request).execute();
    String bodyString;
    if (response.isSuccessful() && response.body() != null 
        && !StringUtils.isEmpty(bodyString = response.body().string())) {
        return bodyString;
    }
    return null;
}

效果图如下:

可以看到500个线程,每个线程1000次请求对于这种使用方式而言完全就是小问题,1min+完成,并且CPU占用率一直维持在15%左右,线程也是在45个左右,效率和性能可以说是非常高。

后话:由此不难看出,程序的性能问题可以说大部分都是和代码细节相关的,谁能想到把一个类变成单例模式后便能够取得如此巨大的性能突破?这次对OkHttp3的性能摸索可以说让我意识到了代码优化以及平时编码的习惯都是从小慢慢积累的,这才是真正的细节决定成败啊。

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: OKHttp3 是一个开源的用于HTTP请求的工具类库,是Square公司开发的。它构建在OkIO库的基础上,并且提供了强大且灵活的功能,用于进行网络请求和数据交互。 OKHttp3 支持GET、POST、PUT、DELETE等常用的HTTP请求方法,可以方便地与Web服务器进行数据交互。它提供了丰富的功能,如连接池管理、请求重试、请求拦截器、响应拦截器等,能够满足各种复杂的网络请求需求。 使用OKHttp3工具类进行网络请求也十分简单。首先,我们需要创建一个OKHttpClient实例,可以设置一些配置,如连接超时时间、读取超时时间等。然后,我们可以通过OKHttpClient生成一个Request对象,设置请求的URL、请求方法以及请求体等。接下来,使用这个Request对象来创建一个Call对象,再调用execute或enqueue方法发送请求。最后,我们可以通过Response对象获取到服务器返回的响应数据。 除了基本的网络请求功能,OKHttp3还支持多线程并发请求、文件上传和下载、WebSocket等高级特性。它的设计易于扩展和定制,可以与其他框架(如RxJava、Retrofit)配合使用,进一步简化网络请求的操作。 总之,OKHttp3是一个功能强大、灵活易用的工具类库,能够帮助我们轻松进行网络请求和数据交互。无论是简单的GET请求还是复杂的多线程请求,OKHttp3都能够胜任,并且具备良好的性能和稳定性。 ### 回答2: OkHttp3 是一个开源的基于 JavaHTTP 客户端工具类库,它提供了简洁的 API 接口和高效的网络通信能力。 首先,OkHttp3 内部实现了连接池和请求复用功能,可以重用之前的连接和请求,从而减少网络连接和请求的开销,提高网络性能。它还支持 HTTP/2 和 SPDY,能够多路复用多个请求,提升并发访问效率。 OkHttp3 的 API 设计简洁易用,提供了丰富的功能,如异步请求、同步请求、文件上传、文件下载、Cookie 管理等。我们可以通过建立 OkHttpClient 对象,设置一些配置信息,如超时时间、拦截器等,然后通过创建 Request 对象来发起请求,并通过回调方式获取服务器返回的响应。 使用 OkHttp3,我们可以轻松地处理各种类型的网络请求,包括 GET、POST、PUT、DELETE 等,并可以通过设置 Header、Body 参数来自定义请求内容。OkHttp3 还提供了缓存机制,我们可以通过配置缓存策略,减少对服务器的频繁请求,同时也可以配置自定义的拦截器,对请求和响应进行处理和修改。 另外,OkHttp3 还支持自动设置代理、支持网络请求的重试和重定向,以及支持自定义的证书校验等安全性功能。 总之,OkHttp3 是一个功能强大、易用且高效的网络请求工具类库,广泛应用于 Android 和 Java 开发中。它提供了丰富的功能和高性能的网络通信能力,帮助我们方便快捷地发起各种类型的网络请求,并处理返回的响应数据。 ### 回答3: okhttp3 是一个流行的开源网络请求框架,主要用于在 Android 平台上发送和接收 HTTP 请求。 okhttp3 工具类可以帮助我们更方便地使用和管理 okhttp3,提供了一系列简化了的方法和功能,使我们能够更高效地进行网络请求的处理。 首先,okhttp3 工具类可以帮助我们创建 OkHttpClient 实例,这是 okhttp3 中的核心对象,用于发送和接收请求。我们可以通过设置不同的配置参数,如超时时间、连接数限制等,来满足不同的需求。 接下来,okhttp3 工具类提供了简化了的方法,如 GET、POST 等,用于发送不同类型的请求。我们只需提供请求的地址和参数,工具类就会自动构建请求对象,并将响应结果以回调方式返回。 此外,okhttp3 工具类还支持多线程并发请求的功能。我们可以通过设置线程池来同时发送多个请求,从而提高并发处理能力。 okhttp3 工具类还提供了拦截器的功能,可用于在发送和接收请求的过程中进行一些自定义的操作,如参数加密、日志记录等。我们可以通过自定义拦截器来实现这些功能,并将其添加到 OkHttpClient 实例中。 总的来说,okhttp3 工具类提供了一系列简化了的方法和功能,使我们能够更方便地使用 okhttp3 进行网络请求。它大大简化了我们的开发流程,减少了代码量,并且具有高效和可靠的性能。无论是在 Android 开发中,还是在其他需要进行网络请求的场景中,okhttp3 工具类都是一个值得推荐的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值