okhttp3之GET,POST源码角度分析

前言

1.okhttp现在使用越来越广泛,许多第三方的框架里面需要网络的都使用了okhttp,比如bmob等,由于个人项目原因最近一段时间用volley比较多,所以对okhttp有点生疏了,现在我们来重温习一遍okhttp。
2.个人认为volley适用于小型个人项目,如果是较大项目还是动用okhttp吧。
3.本篇文章是基于okhttp3.8.0版本源码来讲述。
4.导入声明:(
implementation ‘com.squareup.okhttp3:okhttp:3.8.0’
implementation ‘com.squareup.okio:okio:1.12.0’

这里同时需要导入一个okio的包,有不熟悉的朋友就会问了,我用的是okhttp3为什么还要导入一个okio的,其实这就是好像是okhttp3它里面用到这个包,我们需要一并导入给它使用。而okio是干嘛用的呢,这里我给出一些介绍。

okio基本特性
(1)紧凑的封装 是对Java IO/NIO 的一个非常优秀的封装,绝对的“短小精焊”,不仅支持文件读写,也支持Socket通信的读写。
(2) 使用简单 不用区分字符流或者字节流,也不用记住各种不同的输入/输出流,统统只有一个输入流Source和一个输出流Sink。
(3)API丰富 其封装了大量的API接口用于读/写字节或者一行文本
(4)读写速度快 这得益于其优秀的缓冲机制和处理内存的技巧,使I/O在缓冲区得到更高的复用处理,从而尽量减少I/O的实际发生。


>okio支撑机制 (1)超时机制 在读/写时增加了超时机制,且有同步与异步之分。 (2)缓冲机制 读/写都是基于缓冲来来实现,尽量减少实际的I/O。 (3)压缩机制 写数据时,会对缓冲的每个Segment进行压缩,避免空间的浪费。当然,这是其内部的优化技巧,提高内存利用率。 (4)共享机制 主要是针对 Segment 而言的,对于不同的 buffer 可以共享同一个 Segment。这也是其内部的优化技巧。

因为okhttp3里面也会用到读写,所以使用了这个库。看完上面关于okio的介绍你大概知道我们为什么需要同时导入okio的包吧。

正文

首先我们说一下GET,POST的同步异步请求
不管是GET同步异步或者是POST同步异步他们都需要创建一个OkHttpClient对象,这个对象很重要,我们要发送网络请求,它就必不可少。
我们构建了一个请求信息Request对象,而OkHttpClient对象就是承载着这个请求对象进行性网络请求操作。
(比如你还不明白的话,就这样打个比喻OkHttpClient对象是一个快递员,Request对象就是一个快递,没快递员你送个鬼快递)
我们发一个请求也只需要这俩个对象是不是很容易理解,一个包裹一个快递员就够了。

  • ##调用代码

下面先看一下调用代码,我们再来说它们的源码的实现原理
这里如果进行同步请求,一定要自己开启一个子线程给它,不然会出错。为什么要开线程,我们都知道啊,网络是耗时操作,你不开个子线程把它放在主线程中跑那不是扯淡嘛。别开我们下面的异步线程我们没开子线程给它跑,你就以为它在主线程跑了,它其实还是弄了一个线程池。

  • ###GET同步:
        String url = "https://www.baidu.com";
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = client.newCall(request);
		//使用
		new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Response response = call.execute();
                            System.out.println("GET同步请求:"+response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
  • ###GET异步:
    这里异步的话我们就注册回调接收它返回的数据,它回调的方法也很简单,一个失败一个成功。写法与同步也是类似,处理接受数据的方式有点区别,但是底层实现差距是不大的。
        String url = "https://www.baidu.com";
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = client.newCall(request);
		//使用
		call.enqueue(new Callback() {
                    //请求失败执行的方法
                    @Override
                    public void onFailure(Call call, IOException e) {
                    }
                    //请求成功执行的方法
                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        String data = response.body().string();
                        System.out.println("GET异步请求:"+data);
                    }
                });

POST和GET这里不同点就在于我们需要再构建一个RequestBody对象来承载我们的请求数据,接着上一个比喻,我们进一步比喻可以这样理解: OkHttpClient是快递员 Request是一个包裹 而 RequestBody就是我们包裹上的商家信息(比如说加急等信息)

  • ###POST同步
        OkHttpClient client = new OkHttpClient();
        String url = "http://47.94.255.194:3222/get_all_article";
        RequestBody formBody = new FormBody.Builder()
                .add("blog_id", "7")
                .add("page","0")
                .add("number","10")
                .build();
        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();
		//使用
		new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Response response = call.execute();
                            System.out.println("POST同步请求:"+response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
  • ###POST异步
        OkHttpClient client = new OkHttpClient();
        String url = "http://47.94.255.194:3222/get_all_article";
        RequestBody formBody = new FormBody.Builder()
                .add("blog_id", "7")
                .add("page","0")
                .add("number","10")
                .build();
        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();
		//使用
		call.enqueue(new Callback() {
                    //请求失败执行的方法
                    @Override
                    public void onFailure(Call call, IOException e) {
                    }
                    //请求成功执行的方法
                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        String data = response.body().string();
                        System.out.println("POST异步请求:"+data);
                    }
                });

上面我们就把常用的网络请求都概述完毕,这里我们也只讲关于get,post基本网络操作。断点下载这些我们下次有空再去唠叨。
上面我们提到同步和异步的使用区别,同步我们需要自己去开线程,异步则不需要,异步okhttp弄了一个线程池,最后依旧是调用了同步的方法,所以俩者是没有本质的区别的,这里我们来看一下源码:
这是我们异步调用的enqueue方法,这里新建了一个AsyncCall,并加入dispatcher的待执行队列。在dispatcher的线程池执行到AsyncCall.executeOn()

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

看一下AsyncCall的源码:
这里给出的不是完整的源码,省略号那里省去了一部分,不过我们要看的就是execute,看到没有异步里面出现了我们之前同步调用的方法。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    ...

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

这里我们已说明了同步异步其实底层实现是一样的。

  • ##okhttp拦截器特点

拦截器顾名思义就是拦截的作用,那么eokhttp为什么会使用拦截器这种方式,作用又是干嘛?
我们先来看一下okhttp源码中的拦截器使用代码:

 // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));、

这里源码中定义一个集合保存所有拦截器,总共七个拦截器,其中有个forWebSocket参数,我们默认传false,代码如下,所以这里会进去判断,使用七个拦截器。那么这七个拦截的有什么作用呢?

  1. interceptors这个是外部配置的拦截器,就是自定义自己的拦截器。
  2. retryAndFollowUpInterceptor 失败重试,重定向。
  3. BridgeInterceptor 转换用户请求,转换服务器的响应。
  4. CacheInterceptor 读取缓存,修改缓存。
  5. ConnectInterceptor 与服务器建立连接。
  6. networkInterceptors 外部配置的network拦截器。
  7. CallServerInterceptor 发起网络请求,接受数据。
  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

上面我们说几个拦截器,那么在okhttp中,它是怎么执行这些拦截器的呢?
在我们的okhttp中有一个Interceptor接口定义,其实RealInterceptorChain就实现了Interceptor接口。也就是在RealInterceptorChain中我们执行所有拦截器,下面给出源码,源码也很容易看懂,即使取出集合中配置的拦截器,然后执行,直到执行完毕。

 // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

下面给出拦截器的执行流程图。

那么,至此我们整个okhttp发送一个GET或者POST请求就完毕了,这里我们介绍了okhttp发送网络请求的一个流程。

如果你有时间我建议你再自己去看一下CacheInterceptor拦截器的源码以及CallServerInterceptor拦截器对缓存的处理
这里由于篇幅原因我简单提及一下,我们如果的第一次请求a网络资源,我们okhttp会发送氢气去请求这些资源,并且把资源保存起来,当我们第二次再去请求这个网站的时候,我们okhttp会先检查本地有没有这个网站的资源,如果没有则请求,如果有则检查缓存资源是否过期即使服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比,如果有有资源修改了则就返回最新的资源,状态码是200,如果没有进行修改则返回304。

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

一定要注意okhttp拦截器是一个u型,从1到7,返回也要从7到1。

如果上面http不是很了解,建议去脑补一些http方面的技术。
http参考文章

发布了20 篇原创文章 · 获赞 3 · 访问量 433

猜你喜欢

转载自blog.csdn.net/weixin_41078100/article/details/103006884
今日推荐