OkHttp源码探究(一)---基本使用

吐槽

自己也要进入安卓全面学习状态了,这块网络请求之前自己用的一点也不好,写项目的时候发行自己各种问题和后台交互的时候,而且自己源码什么的还没开始看,就很尴尬,就拿这个okhttp当自己第一个阅读的源码吧。

本文思维导图

//以后写东西的时候都先拿思维导图构建好思路
这里写图片描述

本文前的准备什么的,我之前写的博客里面有
之前写的博客

Get请求

同步的方法

先看整体的代码
同步GET的意思是一直等待http请求, 直到返回了响应. 在这之间会阻塞进程, 所以通过get不能在Android的主线程中执行, 否则会报错.

private void initSyncData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Request request = new Request.Builder().url("http://www.baidu.com").build();
                    Response response = client.newCall(request).execute();
                    result = response.body().string();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText(result);
                            Log.d("233","sync");
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

1.先实例化okhttp,构建一个request,使用的是get方式,放入一个url地址就可以了,也可以通过Request.Builder设置更多的参数。
2.然后通过client发起一个请求,放入队列。等待任务完成,在Callback中取结果。
3.通过response.body().string()获取返回来的字符串。
这个body()其实就是ResponseBody的对象

先看
第一句

OkHttpClient client=new OkHttpClient(); //创建一个okhttpClient实例

然后我进入OkHttpClient这个类的源码时候,,,,我直接懵逼了,都什么鬼啊啊啊啊,看的我一脸懵逼,然后我还是继续坚持看下去吧。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
      Protocol.HTTP_2, Protocol.HTTP_1_1);

  static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
      ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
      ...........
      final Dispatcher dispatcher;
  final @Nullable Proxy proxy;
  final List<Protocol> protocols;
  final List<ConnectionSpec> connectionSpecs;
  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;
  final EventListener.Factory eventListenerFactory;
  final ProxySelector proxySelector;
  final CookieJar cookieJar;
  final @Nullable Cache cache;
  final @Nullable InternalCache internalCache;
  final SocketFactory socketFactory;
  final @Nullable SSLSocketFactory sslSocketFactory;
  final @Nullable CertificateChainCleaner certificateChainCleaner;
  final HostnameVerifier hostnameVerifier;
  final CertificatePinner certificatePinner;
  final Authenticator proxyAuthenticator;
  final Authenticator authenticator;
  final ConnectionPool connectionPool;
  ..........

第二步
这个总算看到网址了,这块就和网络请求HTTP这块有关系了

Request request = new Request.Builder().url(url) .build(); //创建一个Request实例

先看下Request类的源码,这块终于能看懂了,这个不就是HTTP下的请求报文哈哈哈,啦啦啦
http协议请求消息体分为4部分请求行、请求头部、空行和请求数据
请求行又包含请求方法,请求地址,请求协议
这里写图片描述

public final class Request {
  final HttpUrl url;//请求地址
  final String method;//请求方法
  final Headers headers;//请求头
  final @Nullable RequestBody body;//请求体
  final Object tag;//取消http请求的标志

  private volatile CacheControl cacheControl; // Lazily initialized.
  ..........

但是我们发现为啥没有请求协议呢,,,难道图有毒吗
因为HTTP版本的问题
前, Http/1.1在全世界大范围的使用中, 直接废弃跳到http/2肯定不现实. 不是每个用户的浏览器都支持http/2的, 也不是每个服务器都打算支持http/2的, 如果我们直接发送http/2格式的协议, 服务器又不支持, 那不是挂掉了! 总不能维护一个全世界的网站列表, 表示哪些支持http/2, 哪些不支持?
为了解决这个问题, 从稍高层次上来说, 就是为了更方便地部署新协议, HTTP/1.1 引入了 Upgrade 机制. 这个机制在 RFC7230 的「6.7 Upgrade」这一节中有详细描述.
简单说来, 就是先问下你支持http/2么? 如果你支持, 那么接下来我就用http/2和你聊天. 如果你不支持, 那么我还是用原来的http/1.1和你聊天

所以OkHttp使用了请求协议的协商升级, 无论是1.1还是2, 都先只以1.1来发送, 并在发送的信息头里包含协议升级字段. 接下来就看服务器是否支持协议升级了. OkHttp使用的协议升级字段是ALPN, 如果有兴趣, 可以更深入的查阅相关资料.

第三步:

Response response= client.newCall(request).execute();

既然有请求的,肯定有返回响应的,进入Response这个类
这里写图片描述

public final class Response implements Closeable {
  final Request request;//持有的请求
  final Protocol protocol;//请求协议
  final int code;//响应码
  final String message;//描述的信息
  final @Nullable Handshake handshake;//SSL/TLS握手协议验证时的信息
  final Headers headers;//响应头
  final @Nullable ResponseBody body;//响应体
  final @Nullable Response networkResponse;
  final @Nullable Response cacheResponse;
  final @Nullable Response priorResponse;
  final long sentRequestAtMillis;
  final long receivedResponseAtMillis;

  private volatile CacheControl cacheControl; // Lazily initialized.
  .........

而且这块newCall(request)点进去发现它是OkHTTPClient.java里面的一个方法
这里写图片描述
然后继续进去看源码,进入Call这个,发现这个是个接口

public interface Call extends Cloneable {
  /** Returns the original request that initiated this call. */
  Request request();
.........
  void enqueue(Callback responseCallback);

   boolean isExecuted();

  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

第四步:

String message=response.body().string();//得到返回的内容

然后我们进入这个方法里面看下,就很简单的返回
这里写图片描述

然后我们整体看下同步请求的过程

  • OkHttpClient实现了Call.Factory接口, 是Call的工厂类
  • Call负责发送执行请求和读取响应.
  • Request代表Http请求, 通过Request.Builder辅助类来构建.
  • client.newCall(request)通过传入一个http request, 返回一个Call调用. 然后执行execute()方法, 同步获得
  • Response代表Http请求的响应. response.body()是ResponseBody类, 代表响应体, 可以通过responseBody.string()获得字符串的表达形式, 或responseBody.bytes()获得字节数组的表达形式, 这两种形式都会把文档加入到内存. 也可以通过responseBody.charStream()responseBody.byteStream()返回流来处理.

注意点:
响应体的string()方法对于小文档来说十分方便高效. 但是如果响应体太大(超过1MB), 应避免使用 string()方法, 因为它会将把整个文档加载到内存中.
对于超过1MB的响应body, 应使用流的方式来处理响应body. 这和我们处理xml文档的逻辑是一致的, 小文件可以载入内存树状解析, 大文件就必须流式解析.

异步请求

异步GET是指在另外的工作线程中执行http请求, 请求时不会阻塞当前的线程, 所以可以在Android主线程中使用.
当响应可读时回调Callback接口. 当响应头准备好后, 就会调用Callback接口, 所以读取响应体时可能会阻塞. OkHttp现阶段不提供异步api来接收响应体
先看下源写的异步请求的代码

 /**
     * 异步请求
     */
    private void initAsyncGet(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                Request request = new Request.Builder().url("http://www.baidu.com").build();
                client.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        // call   是一个接口,  是一个准备好的可以执行的request  可以取消,对位一个请求对象,只能单个请求
                        Log.d("233","请求失败");
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        /**
                         * 通过拿到response这个响应请求,然后通过body().string(),拿到请求到的数据
                         *这里最好用string()  而不要用toString()
                         * toString()每个类都有的,是把对象转换为字符串
                         * string()是把流转为字符串
                         */
                        result = response.body().string();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {

                            }
                        });

                    }
                });
            }
        }).start();
    }

然后我们又不断的进去看源码瞅瞅,发现了一些同步请求和异步请求的相同的地方和不同的地方,然后我们根据这个不同就进去看下源码分析下。

从请求开始处理的分析

上面我们刚看来下同步和异步的请求的方式,我们看下他们直接的请求的时候写的方式有点不一样,所以我们就从这块进去看下
先看下同步请求的方式:

 Request request = new Request.Builder().url("http://www.baidu.com").build();
 Response response = client.newCall(request).execute();

进入client.newClall()源方法发现:

 /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

返回的是RealCall类的一个方法

然后我们看下异步请求的方法:

 Request request = new Request.Builder().url("http://www.baidu.com").build();
client.newCall(request).enqueue(new Callback() 

然后我们看下还是会调用newCall()这个方法emmmm

所以,其实返回的时候,是返回一个RealCall类

所以,综上可得,RealCall:真正的请求执行者。

然后我们进入RealCall的源代码:
//里面好多都删了了

final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

   private boolean executed;

  private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

  @Override public Response execute() throws IOException {
    synchronized (this) {
      ..............
        }
    @Override protected void execute() {
      .......................
}

仔细发现这个类里面有execute()方法和enqueue()方法
从它的构造方法可得:传过来一个OkHttpClient对象和一个originalRequest(我们创建的Request)

然后观察execute()方法和enqueue()方法里面发现一个神奇的共同点

先看enqueue()方法的源码

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

再看execute()这个方法

@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 {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

然后我们发现这两个方法里面client.dispatcher().enqueue(new AsyncCall(responseCallback));和 client.dispatcher().finished(this);这两句很显眼啊

所以,发现最终的请求控制是由dispatcher()进行的

  • 利用 client.dispatcher().executed(this) 来进行实际执行,dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一,它的文档说自己是异步 HTTP请求的执行策略,现在看来,同步请求它也有掺和。
  • 调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作
  • 最后还要通知 dispatcher 自己已经执行完毕。
    dispatcher 这里我们不过度关注,在同步执行的流程中,涉及到 dispatcher 的内容只不过是告知它我们的执行状态,比如开始执行了(调用 executed),比如执行完毕了(调用 finished),在异步执行流程中它会有更多的参与。
    真正发出网络请求,解析返回结果的,还是 getResponseWithInterceptorChain

Dispather任务调度

点Dispather这个类进去发现有好多的英语emmmmmm还是先看下吧
主要变量:

public final class Dispatcher {
  private int maxRequests = 64;//最大并发请求数
  private int maxRequestsPerHost = 5;//每个主机的最大请求数
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;//消费者线程池

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//将要异步请求队列

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在异步请求的队列

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//正在运行的同步请求

然后再看下Dispatcher的构造方法

public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
  • Dispatcher有两个构造方法,可以自己设定的线程池,如果没有,就用默认创建的线程池
  • 当RealCall调用enqueue其实是调用Dispatcher类的enqueue方法

然后我们再看下Dispatcher类的enqueue方法的源代码

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

然后发现这段代码很有意思

  • 当正在运行的异步请求数量小于64并且正在运行的请求主机数小于5的时候,把请求加入 runningAsyncCalls中并在线程池里面,否则就加入等待队列里面
  • 这个函数传进来的是一个AsyncCall类对象,它是RealCall的内部类,内部也实现了execute方法

然后我们继续进去看下AsyncCall类里面的execute方法

@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 {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

反正不论怎么样都会调用 client.dispatcher().finished(this);这句代码
感觉这句代码看到很多次了
然后我们点进去看下finished(this)这个方法

 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

发现finished()方法将这次请求从ruanningAsyncCalls移除这个请求
然后执行promoteCalls();方法,我们进去看下这个方法

 private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
  • 如果正在运行的异步线程队列大于最大并发请求数的时候,返回
  • 如果准备进行异步请求的队列是空的话也返回
  • 最关键的一点是readyAsyncCalls取出一个请求加入runningAsyncCalls队列里面,然后线程池进行处理

然后我们再返回再看下AsyncCall的execute()方法

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
        。。。。。。。。。。。。
          }

getResponseWithInterceptorChain()返回了Response,这块在请求网络 获得response。

然后,我们综上可得,,,,getResponseWithInterceptorChain()才是真正发送网络请求和解析返回结果的emmmmm

然后,这一篇先到这,写不动了写不动了,看的人脑壳疼

总结

先把上面总结的流程整理下:
1先是把异步和同步的请求的时候的过程走了一遍,然后发现这个RealCall这个类
2然后再这个类里面发现请求又是由Dispatcher类进行调度的
3然后在这个Dispatcher类里面有准备请求队列和正在请求队列,发行传入的是AsyncCall这个类的对象
4AsyncCall这个类发现它也有execute()方法,不论怎样都会执行 client.dispatcher().finished(this)这段代码
5然后进去看了下finished()方法,又从这个方法进入promoteCalls方法,在promoteCalls方法里面,会把准备请求队列的取出一个请求扔进正在请求的队列,然后交给线程池处理
6最后重新返回AsyncCall类的execute方法。发现其实getResponseWithInterceptorChain方法返回了Response,它才是真正的请求者

所以,综上可得,getResponseWithInterceptorChain()才是真正发送网络请求和解析返回结果的emmmmm
真的是脑壳痛啊啊啊啊

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
使用Okhttp进行文件上传时,需要注意设置请求头的Content-Type为multipart/form-data。这是因为在文件上传时,需要将文件数据以多部分的形式进行传输。\[1\]你可以使用Okhttp3库来实现这个功能。首先,你需要引入Okhttp3的jar包,可以通过在pom.xml文件中添加以下依赖来引入Okhttp3库: ```xml <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.0.0</version> </dependency> ``` 接下来,你可以使用Okhttp3的RequestBody类来创建一个请求体,然后将文件数据添加到请求体中。在创建RequestBody时,你需要指定Content-Type为multipart/form-data。这样后台才能正确解析请求并接收到数据。\[2\]如果你在设置请求头的Content-Type后仍然无法成功上传文件,可能是因为你没有正确设置RequestBody的Content-Type。你可以尝试在创建RequestBody时设置Content-Type为multipart/form-data,这样可以确保请求体的Content-Type与请求头中的Content-Type一致,从而解决上传文件的问题。\[3\] #### 引用[.reference_title] - *1* *3* [通过Okhttp3 post方式上传文件 Content-Type=multipart/form-data](https://blog.csdn.net/qq_15327175/article/details/130533804)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SpringBoot2.1.x,okhttp3网络请求之MultipartFile方式上传文件,multipart/form-data表单多文件+多参数](https://blog.csdn.net/p812438109/article/details/107943319)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值