OkHttp简单剖析

前言

OkHttp框架当下非常火热,使用这么久了也没怎么认真研究过!这几天抽空整理了一下,分享出来给大家一些参考吧!
复制代码

官方地址

http://square.github.io/okhttp/

https://github.com/square/okhttp
复制代码

版本

    目前最新版本v3.10.0
    
    这里为什么要画蛇添足的强调一下版本呢?因为OkHttp3.4.1版本之前和nginx的v1.13.6之后版本有冲突,
会导致请求失败。如果有小伙伴遇到这个问题,不妨试试将OkHttp框架升一下,应该就能解决这个问题了。
(笔者当时可是查了好久才发现的,真是被坑惨了!)
   
    具体原因,可以参考以下链接:
    https://trac.nginx.org/nginx/ticket/1397
    https://github.com/PhilippC/keepass2android/issues/44
复制代码

使用

官方示例

    简单瞅瞅官方的示例,无外乎是构建Request然后拿到Response。
复制代码
    //GET A URL
    //This program downloads a URL and print its contents as a string. Full source.
    
    OkHttpClient client = new OkHttpClient();
    
    String run(String url) throws IOException {
          Request request = new Request.Builder()
              .url(url)
              .build();
          //同步请求
          Response response = client.newCall(request).execute();
          return response.body().string();
    }
    
    
    
    //POST TO A SERVER
    //This program posts data to a service. Full source.
    
    public static final MediaType JSON
        = MediaType.parse("application/json; charset=utf-8");
    
    OkHttpClient client = new OkHttpClient();
    
    String post(String url, String json) throws IOException {
          RequestBody body = RequestBody.create(JSON, json);
          Request request = new Request.Builder()
              .url(url)
              .post(body)
              .build();
          //同步请求
          Response response = client.newCall(request).execute();
          return response.body().string();
    } 
复制代码

实际使用

    上面已经标注了,官方示例使用的是同步请求的方式,意味着你必须放在线程中取执行。实际使用中,
我们还需要对OkHttpClient配置相关参数和进行异步请求操作。
复制代码

配置具体如下:

   OkHttpClient.Builder builder = new OkHttpClient.Builder();
   //设置连接超时
   builder.connectTimeout(time, TimeUnit.SECONDS);
   //设置读取超时
   builder.readTimeout(time, TimeUnit.SECONDS);
   //允许重定向
   builder.followRedirects(true);
   //增加cookie(这个后面也可以单独讲)
   builder.cookieJar(cookiesManager);
   //添加拦截器(这个后面单独讲)
   builder.addInterceptor(new Interceptor());
   ……(此处省略很多属性,如果需要去看文档吧!)
   OkHttpClient mOkHttpClient = builder.build();
复制代码
    我们项目中使用,一般都要设计成单例模式,这样才能体现出OkHttp的优势。原文如下:
    OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse
it for all of your HTTP calls. This is because each client holds its own connection pool 
and thread pools. Reusing connections and threads reduces latency and saves memory. 
Conversely, creating a client for each request wastes resources on idle pools.

    那么问题来了,假如我要针对个别请求修改配置肿么办?别方,官网也给出了答案:
    You can customize a shared OkHttpClient instance with {@link #newBuilder()}. This 
builds a client that shares the same connection pool, thread pools, and configuration. 
Use the builder methods to configure the derived client for a specific purpose.
代码如下:
复制代码
       OkHttpClient eagerClient = mOkHttpClient.newBuilder()
               .readTimeout(500, TimeUnit.MILLISECONDS) //修改读取超时,其他配置不变
               .build();
复制代码

异步请求如下:

    Call call = mOkHttpClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //失败的回调
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            //成功的回调
        }
      });
复制代码
    下面我们重点讲一下成功的回调,Response这个对象他就很神奇了。稳住,我们能赢!!!
我们一般Http接口请求的目标都是Json对象,它的
    解析如下:
复制代码
    String content = response.body().string();
复制代码
    这个content就是接口返回的数据,继续进行Json解析就OK了。老铁,没毛病呀!的确这样的
流程并没有毛病,but你如果是一个有追求的人,你可能会这样写
复制代码
    @Override
        public void onResponse(Call call, Response response) throws IOException {
            //成功的回调
            String content = response.body().string();
            ……
            中间省略一堆自定义的骚操作
            ……
            //通过封装的接口回调给外面的方法
            youcustomInterface.onBack(response)
        }
复制代码
    老铁,这一波封装很稳。But,你会发现你在外部方法进行response.body().string()的时候程
序FC了!错误如下:
    
    **java.lang.IllegalStateException: closed**
    
    什么原因呢?是时候展现一波真正的源码了:

    先看看body()方法,注释解释道ResponseBody对象只能消费一次,什么叫做消费一次呢?咱们
往下看看string()方法
复制代码
     /**
       * Returns a non-null value if this response was passed to {@link Callback#onResponse}
       * or returned from {@link Call#execute()}. Response bodies must be {@linkplain 
       * ResponseBody closed} and may be consumed only once.
       *
       * <p>This always returns null on responses returned from {@link #cacheResponse}, {@link
       * #networkResponse}, and {@link #priorResponse()}.
       */
      public @Nullable ResponseBody body() {
        return body;
      }
复制代码
    string()方法进行一次流读取操作,值得注意的是在finally里面做了一个关闭的操作,往下看看
复制代码
      /**
       * Returns the response as a string decoded with the charset of the Content-Type header. 
       * If that header is either absent or lacks a charset, this will attempt to decode the 
       * response body in accordance to <a href="https://en.wikipedia.org/wiki/Byte_order_mark">
       * its BOM</a> or UTF-8.
       * Closes {@link ResponseBody} automatically.
       *
       * <p>This method loads entire response body into memory. If the response body is very 
       * large this may trigger an {@link OutOfMemoryError}. Prefer to stream the response 
       * body if this is a possibility for your response.
       */
      public final String string() throws IOException {
        BufferedSource source = source();
        try {
          Charset charset = Util.bomAwareCharset(source, charset());
          return source.readString(charset);
        } finally {
          Util.closeQuietly(source);
        }
      }
复制代码
    这里貌似也没有看出来什么,只是关闭操作,那么我们继续往后看看
复制代码
      /**
        * Closes {@code closeable}, ignoring any checked exceptions. Does nothing if
        * {@code closeable} is null.
        */
      public static void closeQuietly(Closeable closeable) {
        if (closeable != null) {
          try {
            closeable.close();
          } catch (RuntimeException rethrown) {
            throw rethrown;
          } catch (Exception ignored) {
          }
        }
      }

复制代码
    重头戏来了,看方法的注释,这个close()方法用来关闭流并释放资源,如果流已经关闭再
次调用这个方法将是无效的。这里就可以看出来,流被关闭后,你再读取数据就会抛出
IllegalStateException的异常。
复制代码
    /**
     * Closes this stream and releases any system resources associated
     * with it. If the stream is already closed then invoking this
     * method has no effect.
     *
     * <p> As noted in {@link AutoCloseable#close()}, cases where the
     * close may fail require careful attention. It is strongly advised
     * to relinquish the underlying resources and to internally
     * <em>mark</em> the {@code Closeable} as closed, prior to throwing
     * the {@code IOException}.
     *
     * @throws IOException if an I/O error occurs
     */
    public void close() throws IOException;
复制代码
    废了老大劲了,终于看明白了,然后你以为结束了?To young to simple!还是sting()方法,
他的注释上还有一句提示 This method loads entire response body into memory. If the 
response body is very large this may trigger an {@linkOutOfMemoryError}. Prefer to
stream the response body if this is a possibility for your response.
    这句话很重要,他的意思是string()去读取数据的时候,stream流不能太大,不然会导致内
存溢出。为什么要强调这个呢?因为OKHttp还有下载的功能,而你又使用的是单例模式,所以你
搭建框架的时候一定要做好区分。
复制代码

Interceptor 拦截器

    为什么要单独讲一下这个呢?因为他比较有意思呗!首先拦截器分为应用拦截和网络拦截,
我们这里只讲应用拦截。应用拦截器的嵌入流程如下图所示:
复制代码

    可以拦截请求,也可以拦截响应,根据就提需要来定制。
    
    一个请求的拦截:
复制代码
class RequestInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request originalRequest = chain.request();
            Request requestWithUserAgent = originalRequest.newBuilder()
                    .header("User-Agent", "your ua")//添加UA
                    .build();
            return chain.proceed(requestWithUserAgent);
        }
    }
复制代码
    一个响应的拦截:
复制代码
class ResponseInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            return chain.proceed(request);
        }
    }
复制代码
    这里重点讲一下响应拦截,为什么呢?因为上文提到过,你关于response.body().string()的使用会出问题。
复制代码
class ResponseInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            String bodyMsg = response.body().string();
            ……
            一系列骚操作
            ……
            MediaType mediaType = response.body().contentType();
            //这边重新构建了一个response body,否则的话你在后面的响应无法继续解析了
            ResponseBody responseBody = ResponseBody.create(mediaType, bodyMsg);
            return response.newBuilder().body(responseBody).build();
        }
    }
复制代码
    最后补上一段,这个应用拦截器都是针对全局的,而你又使用的是单例模式,所以在使用过程当中一定要引起注意。复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值