OkHttp

OKhttp

简介


OKhttp是square公司出品的,它是一个高效的HTTP客户端,(Retrofit中的http通信实现也是基于OKhttp的)它有以下默认特性:

  1. HTTP/2 support allows all requests to the same host to share a socket.
  2. Connection pooling reduces request latency (if HTTP/2 isn’t available).
  3. Transparent GZIP shrinks download sizes.
  4. Response caching avoids the network completely for repeat requests.

基本使用

1.添加依赖

目前最新的okhttp的version是3.13.1

dependencies {

   implementation 'com.squareup.okhttp3:okhttp:3.13.1'

}

2.GET方式

(1)同步请求,关键调用okhttpClient.execute()

    private void GetOKhttp() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //创建OkhttpClient
                OkHttpClient okHttpClient = new OkHttpClient();
                //创建request
                Request request = new Request.Builder()
                        .url("http://www.baidu.com")
                        .build();
                //同步网络请求
                try {
                    okhttp3.Response response = okHttpClient.newCall(request).execute();
                    //若成功返回
                    if(response.isSuccessful()){
                        ok_string = response.body().toString();
                        handler.obtainMessage(1).sendToTarget();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

(2)异步请求,关键调用okhttpClient.enqueue

    private void GetOKhttp() {
        //创建OkHttpClient实例
        OkHttpClient okHttpClient = new OkHttpClient();
        //创建Request对象
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .build();
        //获取Call接口
        okhttp3.Call call = okHttpClient.newCall(request);
        //异步网络请求
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                Log.i("xw", "11111:" + response.isSuccessful());
            }
        });
    }

同步请求是在主线程中进行的,因此若网络请求耗时我们需要将其放在线程中去处理;而异步请求,因为回调方法中就是在子线程中实现的,因此无需另起线程。

3.POST请求

post请求也是有同步和异步的分别。我们这里就以异步情况为例:

    private void PostOKhttp() {
        //创建OkhttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient();
        //创建表单对象
        FormBody.Builder builder = new FormBody.Builder();
        //键值对输入
        builder.add("key","123123123");
        builder.add("cityname","上海");
        //创建request参数,其中需要添加pos方式
        Request request = new Request.Builder()
                .post(builder.build())
                .url("http://api.avatardata.cn/?")
                .build();
        //创建Call接口
        okhttp3.Call call = okHttpClient.newCall(request);
        //异步请求
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                Log.i("xw","fail!");
            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                Log.i("xw","sucess!" + response.code());
                if (response.isSuccessful()){
                }
            }
        });
    }

post方法接收的参数是RequestBody 对象。可以传递如下几种:

FormBody对象

FormBody是RequestBody的子类。多用于传递键值对。

public final class FormBody extends RequestBody {
  private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");

  private final List<String> encodedNames;
  private final List<String> encodedValues;

  FormBody(List<String> encodedNames, List<String> encodedValues) {

JSON对象/File对象

这里关键就是在于MediaType的设置。查看源码MediaType类可以看到:

public final class MediaType {
  private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
  private static final String QUOTED = "\"([^\"]*)\"";
  private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
  private static final Pattern PARAMETER = Pattern.compile(
      ";\\s*(?:" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + "))?");

JSON对象:

        //数据类型为JSON类型
        MediaType json = MediaType.get("application/json;charset=utf-8");
        //需要上传的json数据
        String jsonString = "{\"username\":\"lisi\",\"nickname\":\"李四\"}";
        //创建requestBody对象
        RequestBody requestBody = RequestBody.create(json,jsonString);

File对象:

        //数据类型为File类型
        MediaType fileType = MediaType.get("File/*");
        //需要上传的File对象
        File file = new File(getExternalCacheDir().toString());
        //创建requestBody对象
        RequestBody requestBody = RequestBody.create(fileType,file);

MultipartBody传递多类型参数

查看MultipartBody也是RequestBody的子类,他的关键方法为:

    /** Add a part to the body. */
    public Builder addPart(RequestBody body) {
      return addPart(Part.create(body));
    }

    /** Add a part to the body. */
    public Builder addPart(@Nullable Headers headers, RequestBody body) {
      return addPart(Part.create(headers, body));
    }

    /** Add a form data part to the body. */
    public Builder addFormDataPart(String name, String value) {
      return addPart(Part.createFormData(name, value));
    }

    /** Add a form data part to the body. */
    public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) {
      return addPart(Part.createFormData(name, filename, body));
    }

    /** Add a part to the body. */
    public Builder addPart(Part part) {
      if (part == null) throw new NullPointerException("part == null");
      parts.add(part);
      return this;
    }

我们可以传递键值对或者键值对和另一个RequestBody一起:

    MultipartBody multipartBody =new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("groupId",""+groupId)//添加键值对参数
    .addFormDataPart("title","title")
    .addFormDataPart("file",file.getName(),RequestBody.create(MediaType.parse("file/*"), file))//添加文件
    .build();

自定义的RequestBody

这里就需要我们自定义一个子类继承RequestBody,需要重写contentType()、contentLength()、writeTo()。

        //自定义RequestBody,重写writeTo
        RequestBody body = new RequestBody() {
            @Override
            public MediaType contentType() {
                return null;
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                FileInputStream fio= new FileInputStream(new File(getExternalCacheDir().toString()));
                byte[] buffer = new byte[1024];
                if(fio.read(buffer) != -1){
                    sink.write(buffer);
                }
            }
        };

可以看到在writeTo()方法中传递的参数是BufferedSkin,这是Okio包里输入流,有write方法可写入。

4.拦截器(Interceptor)

这是Okhttp库中最核心的地方。首先Okhttp的拦截器加载是有序的,他是按照顺序从上到下来加载的。为什么?且看源码:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //用户自定义的interceptor
    interceptors.addAll(client.interceptors());
    //RetryAndFollowUpInterceptor
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //BridgeInterceptor
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //CacheInterceptor
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //ConnectInterceptor
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
			//用户自定义的networkInterceptors
      interceptors.addAll(client.networkInterceptors());
    }
    //CallServerInterceptor
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //开始
      Response response = chain.proceed(originalRequest);

RetryAndFollowUpInterceptor

这个拦截器主要是用于重定向:

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }

BridgeInterceptor

这个拦截器主要是用于添加请求头header的,其中如果有Accept-Encoding:gzip 字段的时候还会去做压缩处理。

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    
    ....
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();

CacheInterceptor

这个拦截器主要是用于缓存请求并且可以将响应写入缓存。

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

上面完成之后会去获取响应response,然后去判断缓存是否为空,不为空的话会将response存入到缓存中;否则执行下一个拦截器

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

ConnectInterceptor

这个拦截器的作用是打开与目标服务器的连接,然后执行下一个拦截器:

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    return realChain.proceed(request, transmitter, exchange);
  }

CallServerInterceptor

这个拦截器是链路中的最后一个拦截器,他的作用是向服务器进行网络请求:

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        if (request.body() instanceof DuplexRequestBody) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      exchange.noRequestBody();
    }

    if (!(request.body() instanceof DuplexRequestBody)) {
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

其实有点类似网络分层的情况,每层处理自身的实现即可。每个Interceptor处理自己的实现并不会去影响,同时这也形成了链路复用。

流程图

Okhttp 的方法调用流程大致如下(以异步请求为例):

补充

1.有报出BootstrapMethodError

    03-12 10:06:10.596 21989 21989 E AndroidRuntime: java.lang.BootstrapMethodError: Exception from call site #7 bootstrap method

    03-12 10:06:10.596 21989 21989 E AndroidRuntime:        at okhttp3.internal.Util.<clinit>(Util.java:87)

    03-12 10:06:10.596 21989 21989 E AndroidRuntime:        at okhttp3.internal.Util.immutableList(Util.java:234)

    03-12 10:06:10.596 21989 21989 E AndroidRuntime:        at okhttp3.OkHttpClient.<clinit>(OkHttpClient.java:127)

    03-12 10:06:10.596 21989 21989 E AndroidRuntime:        at okhttp3.OkHttpClient$Builder.<init>(OkHttpClient.java:482)

原因:jdk中的方法不存在导致error

解决方案:
1. 将依赖的OKhttp的版本号降低;

2. 在build.gradle中添加:

   compileOptions {

       sourceCompatibility JavaVersion.VERSION_1_8

       targetCompatibility JavaVersion.VERSION_1_8

   }

2.Okhttp资料

https://square.github.io/okhttp/  :官方地址
https://github.com/square/okhttp : 官方github地址

最后,有不足的地方还希望大佬们多指教指正。感谢。

你可能感兴趣的:(Android,技术)