Analyzing the source of OkHttp (一)

Note:
Since OkHttp is a big framework, Therefore I will separately discuss and walk you through it. Before analyzing the source of OkHttp, I will show you what advantages does OkHttp provide and That’s why We might should use it and not to use other frameworks.

Advantages of OkHttp:

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

In short, OkHttp is an easily, faster, flexibly framework for doing requests. Since android 4.4, Google uses OkHttp to do requests in HttpUrlConnection internally. In other words, When you use HttpUrlConnection APIs to do requests, Actually, You are using OkHttp. Anyhow, Chooses OkHttp to help you to deal with network is a good idea. In this case, We should learn How does it work. If do so, We can use it perfectly and easily, or even improving it. Finally, We even might can build a new framework which better than it. Anyway, Let us start to discuss it separately.

At first, I will illustrate How does it work.

Illustrates How does OkHttp work

Secondly, I will show you the simplest example How do you do a request with coding:

Get Request

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 Request

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();
}

These examples are copying from OkHttp. Anyway, As you can see, No matter whether you are doing Get or Post request, You have firstly to create an object of OkHttpClient class, which creates an object of Builder internally and passing it to another Constructor with a Builder parameter. In here, OkHttp uses the “Builder Pattern” to create a flexible constructor. If you don’t know what is that, You can learn it from this link: Builder Pattern in Java .

In the next, I will not to analyze the detail of What Builder does But discuss the Request class. Likes the OkHttpClient, It’s no matter whether what ‘s method you choosing, You have to create an object of Request Before you do a request. Anyway, The Request class is a simple class so that let’s see the entirely class code. Note that: I already deleted some unnecessary setter and getter methods to simplify the class code.

/**
 - An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 - immutable.
 */
public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }


  public @Nullable String header(String name) {
    return headers.get(name);
  }

  public List<String> headers(String name) {
    return headers.values(name);
  }

  public @Nullable RequestBody body() {
    return body;
  }

  public Builder newBuilder() {
    return new Builder(this);
  }

  /**
 - Returns the cache control directives for this response. This is never null, even if this
 - response contains no {@code Cache-Control} header.
   */
  public CacheControl cacheControl() {
    CacheControl result = cacheControl;
    return result != null ? result : (cacheControl = CacheControl.parse(headers));
  }

  public boolean isHttps() {
    return url.isHttps();
  }

  @Override public String toString() {
    return "Request{method="
        + method
        + ", url="
        + url
        + ", tag="
        + (tag != this ? tag : null)
        + '}';
  }

  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
     * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
     */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace web socket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
     * https}.
     */
    public Builder url(URL url) {
      if (url == null) throw new NullPointerException("url == null");
      HttpUrl parsed = HttpUrl.get(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the header named {@code name} to {@code value}. If this request already has any headers
     * with that name, they are all replaced.
     */
    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

    /**
     * Adds a header with {@code name} and {@code value}. Prefer this method for multiply-valued
     * headers like "Cookie".
     *
     * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
     * OkHttp may replace {@code value} with a header derived from the request body.
     */
    public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

    public Builder removeHeader(String name) {
      headers.removeAll(name);
      return this;
    }

    /** Removes all headers on this builder and adds {@code headers}. */
    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    /**
     * Sets this request's {@code Cache-Control} header, replacing any cache control headers already
     * present. If {@code cacheControl} doesn't define any directives, this clears this request's
     * cache-control headers.
     */
    public Builder cacheControl(CacheControl cacheControl) {
      String value = cacheControl.toString();
      if (value.isEmpty()) return removeHeader("Cache-Control");
      return header("Cache-Control", value);
    }

    public Builder get() {
      return method("GET", null);
    }

    public Builder head() {
      return method("HEAD", null);
    }

    public Builder post(RequestBody body) {
      return method("POST", body);
    }

    public Builder delete(@Nullable RequestBody body) {
      return method("DELETE", body);
    }

    public Builder delete() {
      return delete(Util.EMPTY_REQUEST);
    }

    public Builder put(RequestBody body) {
      return method("PUT", body);
    }

    public Builder patch(RequestBody body) {
      return method("PATCH", body);
    }

    public Builder method(String method, @Nullable RequestBody body) {
      if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null && !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null && HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }

    /**
     * Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag
     * is unspecified or null, the request is canceled by using the request itself as the tag.
     */
    public Builder tag(Object tag) {
      this.tag = tag;
      return this;
    }

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}

It’s necessary to notice the comment of class, which tells us what If the body field is null or immutable, the instances of this class will be immutable too. Thus when you create an instance of Request, you should care about this rule. There are six fields in the class, So let’s discuss them one by one.

The list of fields:

  1. The url field is an instance of HttpUrl class, which can be used to create a uniform resource locator (URL) and be used to compose and decompose internet addresses.
  2. The method field is a method(way) for doing a request, such as Get, Post, Delete, Put and so on.
  3. The headers field is an instance of Headers class in which you can put some messages what the server wants, such as your client’s version, token and so on. There are two points you should notice(copying from the comment of Headers class):

    • This class trims whitespace from values. It never returns values with leading or trailing
      whitespace
    • Instances of this class are immutable. Use Builder(an inner class of Headers) to create instances.
  4. The cacheControl field is an instance of CacheControl class, which be used to store a cache of a request, and retrieves datas from backup when the conditions are satisfied with. and as you can see in the codes, This field is decorated by ‘volatile’ specifier. So that read and write will both doing in the main memory not only doing in the cache of cpu. In other words, the value of this field will keep updated every time when you read or write it. If you still can’t understand it, Here is an article for learning it: Volatitle In Java.

  5. The tag field is simply for tagging a request, clients can cancel a request according to a tag, if the tag is null, the request will cancel itself. In other words, when you post a request on onCreate() of Activity method and destroy your Activity immediately. In this case, The request may should be canceled. Therefore, you can cancel that request by using a tag. Here is an example showing you how to cancel a request by using a tag:

  /**
     * Cancel a request by using tag If this request is still requesting or waiting for posting.
     * @param tag: A tag for marking a request.
     */
    @Override
    public void cancelCallWithTag(Object tag) {

        OkHttpClient okHttpClient = getOkHttpClient();

        if (okHttpClient != null && tag != null) {

            for (Call call : okHttpClient.dispatcher().queuedCalls()) {
                if (call.request().tag().equals(tag))
                    call.cancel();
            }

            for (Call call : okHttpClient.dispatcher().runningCalls()) {
                if (call.request().tag().equals(tag))
                    call.cancel();
            }
        }

    }
  1. 6.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值