美文网首页
okhttp原理解析之整体流程

okhttp原理解析之整体流程

作者: jxiang112 | 来源:发表于2021-12-20 17:24 被阅读0次

建议先对HTTP有个大概的了解:HTTP概述

okhttp原理解析之整体流程
okhttp原理解析之cookie
okhttp 缓存解析
[okhttp Dns解析](# 待续)
[okhttp 连接解析](# 待续)
[okhttp http解析](# 待续)
[okhttp https解析](# 待续)
[okhttp io解析](# 待续)

涉及网络开发,避免不了HTTP的,而Android中广泛使用的便是okhttp库了,为什么要用okhttp?本人觉得主要是:

  • 实现了HTTP的各种特性:dns、连接复用、http、https、缓存、cookie等
  • 扩展性强:可以添加拦截器,对请求或者响应均可进行拦截添加自己的处理逻辑
  • 性能增强:使用okio对io进行了性能上的优化

本文便是okhttp原理的解析的开篇,也是“okhttp整体流程解析”文章。
okhttp使用构建的设计模式通过build来构建okhttp实例,接着使用责任链设计模式对拦截器逐一执行它们的职责。

okhttp实例构建

okhttp提供OkHttpClient作为客户端,也是okhttp请求的入口,通过OkHttpClient中静态类Builder来构建,Builder类提供各种方法来设置okhttp的各种属性,最终通过build方法来创建OkHttpClient,并将Builder中的各种属性赋值给OkHttpClient,大体代码如下:

public static final class Builder {
    public Builder() {
            //设置默认的http请求分派器
            this.dispatcher = new Dispatcher();
            //设置默认的支持的http协议版本
            this.protocols = OkHttpClient.DEFAULT_PROTOCOLS;
           //设置默认的支持的tls版本
            this.connectionSpecs = OkHttpClient.DEFAULT_CONNECTION_SPECS;
            //设置默认的事件监听器,用于观测okhttp每个阶段的事务,一般用于开发阶段
            this.eventListenerFactory = EventListener.factory(EventListener.NONE);
            //设置代理选择器
            this.proxySelector = ProxySelector.getDefault();
           //设置没有cookie
            this.cookieJar = CookieJar.NO_COOKIES;
           //设置默认的socket工厂类
            this.socketFactory = SocketFactory.getDefault();
           //设置域名验证类
            this.hostnameVerifier = OkHostnameVerifier.INSTANCE;
            //设置默认的证书认证
            this.certificatePinner = CertificatePinner.DEFAULT;
            //设置无代理身份认证
            this.proxyAuthenticator = Authenticator.NONE;
           //设置无身份认证
            this.authenticator = Authenticator.NONE;
           //设置连接池
            this.connectionPool = new ConnectionPool();
            //设置dns使用系统层的dns
            this.dns = Dns.SYSTEM;
            //设置允许ssl重连
            this.followSslRedirects = true;
            //设置允许重连
            this.followRedirects = true;
            //设置运行连接失败时重连
            this.retryOnConnectionFailure = true;
            //设置连接超时为10秒
            this.connectTimeout = 10000;
            //设置读取超时为10秒
            this.readTimeout = 10000;
            //设置发送超时为10秒
            this.writeTimeout = 10000;
            //设置ping间隔为0秒
            this.pingInterval = 0;
        }
   //设置连接超时时间
    public OkHttpClient.Builder connectTimeout(long timeout, TimeUnit unit) {
            this.connectTimeout = Util.checkDuration("timeout", timeout, unit);
            return this;
        }
    ....
    //添加建立连接前的拦截器
    public OkHttpClient.Builder addInterceptor(Interceptor interceptor) {
            if (interceptor == null) {
                throw new IllegalArgumentException("interceptor == null");
            } else {
                this.interceptors.add(interceptor);
                return this;
            }
        }
     //添加建立连接后的拦截器
    public OkHttpClient.Builder addNetworkInterceptor(Interceptor interceptor) {
            if (interceptor == null) {
                throw new IllegalArgumentException("interceptor == null");
            } else {
                this.networkInterceptors.add(interceptor);
                return this;
            }
        }

    ...
    //构建okhttpclient实例
    public OkHttpClient build() {
            return new OkHttpClient(this);
        }
}

OkHttpClient(OkHttpClient.Builder builder) {
        this.dispatcher = builder.dispatcher;
        this.proxy = builder.proxy;
        ...
    }

OkHttpClient的构建过程其实就是设置Builder的各种属性(这些属性与OkHttpClient是一一对应的),再通过build方法new一个OkHttpClient,在OkHttpClient的构建方法中,将builder的各种一一对应的设置给OkHttpClient的各个属性。这种构建方式可以确保构建OkHttpClient实例的属性不被修改(OkHttpClient的属性是读公共,写是私有的),确保了安全性。

okhttp的http请求流程

构建OkHttpClient之后,接着调用OkHttpClient的newCall方法得到网络请求的Call类

public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false);
}

newCall返回的call的实现类RealCall。

1、发起请求

okhttp网络请求分为同步请求和异步请求,通过RealCall来调用

1.1、同步请求

RealCall的execute方法是同步请求

public Response execute() throws IOException {
        synchronized(this) {
            //断言不能重复发起请求
            if (this.executed) {
                throw new IllegalStateException("Already Executed");
            }

            this.executed = true;
        }
        //当前堆栈追踪
        this.captureCallStackTrace();
       //通知开始请求事件
        this.eventListener.callStart(this);

        Response var2;
        try {
           //client即OkhttpClient,调用Dispatcher分配器executed方法进行同步调度
            this.client.dispatcher().executed(this);
           //进入责任链网络请求模式,并得到网络请求结果
            Response result = this.getResponseWithInterceptorChain();
            if (result == null) {
                throw new IOException("Canceled");
            }

            var2 = result;
        } catch (IOException var7) {
            this.eventListener.callFailed(this, var7);
            throw var7;
        } finally {
            this.client.dispatcher().finished(this);
        }
        //返回请求结果
        return var2;
    }

同步请求将请求放入Dispatcher的同步请求队列,然后调用getResponseWithInterceptorChain(责任链模式)完整HTTP请求,最终得到响应对象Response并返回调用者,同步请求整体即完成。具体怎么实现后续内容和章节会一一介绍。

1.2、异步请求

RealCall的enqueue方法是同步请求

public void enqueue(Callback responseCallback) {
        synchronized(this) {
             //断言不能重复发起请求
            if (this.executed) {
                throw new IllegalStateException("Already Executed");
            }

            this.executed = true;
        }
        //当前堆栈追踪
        this.captureCallStackTrace();
        //通知开始请求事件
        this.eventListener.callStart(this);
        //client即OkhttpClient,调用Dispatcher分配器enqueue方法进行异步调度
        this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
    }

异步请求构建一个异步请求RealCall.AsyncCall,并通过Dispatcher 来调度。此时异步并未完成HTTP的整体流程,请继续查看后续内容。

3、dispatcher分派器调度网络请求

不论同步还是异步,都使用到Dispatcher进行调度,我们来看看Dispatcher怎么实现同步与异步的调度:

public final class Dispatcher {
    //最大请求数
    private int maxRequests = 64;
   //每个域名运行并发数
    private int maxRequestsPerHost = 5;
   //空闲回调
    @Nullable
    private Runnable idleCallback;
   //线程池
    @Nullable
    private ExecutorService executorService;
    //异步情况下:等待调度的请求队列
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque();
    //异步情况下:运行中请求队列
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque();
    //同步情况下:运行中请求队列
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque();

   ......

   //异步请求
    synchronized void enqueue(AsyncCall call) {
        if (this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
          //异步队列正在运行的请求数 < 最大请求数  并且  当前请求域名下的并发请求数 < 每个域名最大并发请求数
           //将请求任务加入异步运行中队列
            this.runningAsyncCalls.add(call);
            //将请求任务加入线程池中调度执行
            this.executorService().execute(call);
        } else {
            //异步队列正在运行的请求数 > 最大请求数  或者  当前请求域名下的并发请求数 > 每个域名最大并发请求数
            //将请求任务加入异步等待调度队列
            this.readyAsyncCalls.add(call);
        }

    }
    
    //取消网络请求
    public synchronized void cancelAll() {
        Iterator var1 = this.readyAsyncCalls.iterator();

        AsyncCall call;
        while(var1.hasNext()) {
            //遍历异步等待中队列,调用cancel进行取消
            call = (AsyncCall)var1.next();
            call.get().cancel();
        }

        var1 = this.runningAsyncCalls.iterator();

        while(var1.hasNext()) {
            //遍历异步进行中队列,调用cancel进行取消
            call = (AsyncCall)var1.next();
            call.get().cancel();
        }

        var1 = this.runningSyncCalls.iterator();

        while(var1.hasNext()) {
           //遍历同步进行中队列,调用cancel进行取消
            RealCall call = (RealCall)var1.next();
            call.cancel();
        }

    }
    
    //调度异步等待队列
    private void promoteCalls() {
        if (this.runningAsyncCalls.size() < this.maxRequests) {
           //当前异步运行中的请求数  < 最大请求数
            if (!this.readyAsyncCalls.isEmpty()) {
                //异步等待队列不为空
                Iterator i = this.readyAsyncCalls.iterator();

                do {
                    //遍历异步等待队列
                    if (!i.hasNext()) {
                        return;
                    }
                    //等待的请求
                    AsyncCall call = (AsyncCall)i.next();
                    if (this.runningCallsForHost(call) < this.maxRequestsPerHost) {
                        //等待的请求的域名并发请求数  < 每个域名最大并发请求数
                        //将等待请求call从异步等待队列中移到异步运行中队列
                        i.remove();
                        this.runningAsyncCalls.add(call);
                        //将等待请求call加入线程池中调度执行
                        this.executorService().execute(call);
                    }
                } while(this.runningAsyncCalls.size() < this.maxRequests);

            }
        }
    }
    
    //技术异步请求的域名当前并发请求数
    private int runningCallsForHost(AsyncCall call) {
        int result = 0;
        Iterator var3 = this.runningAsyncCalls.iterator();

        while(var3.hasNext()) {
            //遍历异步运行中队列
            //正运行中的异步请求
            AsyncCall c = (AsyncCall)var3.next();
            if (c.host().equals(call.host())) {
                //如果参数call与c同域名
                //则同域名并发请求数 + 1
                ++result;
            }
        }

        return result;
    }
    //同步请求
    synchronized void executed(RealCall call) {
        //直接将请求加入同步请求队列
        this.runningSyncCalls.add(call);
    }
    
    //异步请求结束
    void finished(AsyncCall call) {
        this.finished(this.runningAsyncCalls, call, true);
    }
    //同步请求结束
    void finished(RealCall call) {
        this.finished(this.runningSyncCalls, call, false);
    }
    //请求结束处理函数
   //calls:对哪个队列进行移除操作
   //call:哪个请求结束
   //promoteCalls:是否需要调度下一个等待的请求,异步需要,同步不需要
    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) {
                //如果需要调度下一个等待的请求(即如果是异步)
                //调度下一个等待的请求
                this.promoteCalls();
            }
            //计算运行中的数量
            runningCallsCount = this.runningCallsCount();
            idleCallback = this.idleCallback;
        }

        if (runningCallsCount == 0 && idleCallback != null) {
            //如果没有运行中的请求,且空闲处理器不为空,调用空闲处理器
            idleCallback.run();
        }

    }

    ......
}

dispatcher对应同步请求而言,只是将请求放入同步队列,便于后续的取消、完成等操作。
dispatcher对异步请求而言,需要等待队列和运行队列来完成异步调度,在同一域名并发数不超额且总请求数不超额的情况下,调度运行队列,将请求放入线程池运行。
dispatcher主要用于处理并发性能的,如果没来一个http请求就马上发起请求,如果数量足够多,甚至可以占满cpu的开销,所以才有了dispatcher的分派处理器。这个特性是对异步而言,而同步并只能通过线程池来减缓cpu开销。

4、责任链模式实现HTTP请求

同步请求时将请求加入dispatcher的同步队列之后,接着调用getResponseWithInterceptorChain进入责任链完成HTTP的请求。
异步请求时将请求加入dispatcher的异步队列之后,会回调RealCall.AsyncCall的run方法,而RealCall.AsyncCall继承自NamedRunnable:

public abstract class NamedRunnable implements Runnable {
   ......
    public final void run() {
       ......

        try {
            //调用execute方法
            this.execute();
        } finally {
            Thread.currentThread().setName(oldName);
        }

    }

    protected abstract void execute();
}

RealCall.AsyncCall实现NamedRunnable的execute方法:

protected void execute() {
            boolean signalledCallback = false;

            try {
                //进入责任链模式完成HTTP请求
                Response response = RealCall.this.getResponseWithInterceptorChain();
                if (RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
                    //请求已取消
                    signalledCallback = true;
                    //回调onFailure方法告知请求已取消
                    this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
                } else {
                    signalledCallback = true;
                    //回调onResponse方法告知请求已完成
                    this.responseCallback.onResponse(RealCall.this, response);
                }
            } 
            ......
        }

异步请求最终也是调用getResponseWithInterceptorChain进入责任链模式完成HTTP请求,并使用responseCallback回调成功或者失败。自此异步整体流程完成,具体怎么实现后续内容和章节将一一介绍。

我们看看RealCall.getResponseWithInterceptorChain:

//责任链模式完成HTTP请求
Response getResponseWithInterceptorChain() throws IOException {
        List<Interceptor> interceptors = new ArrayList();
        //优先加入自定义的网络连接前的拦截器,默认是空的
        interceptors.addAll(this.client.interceptors());
        //添加okhttp自己的重连拦截器
        interceptors.add(this.retryAndFollowUpInterceptor);
        //添加okhttp的桥接拦截器(主要是cookie拦截处理)
        interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
        //添加okhttp自己的缓存拦截器
        interceptors.add(new CacheInterceptor(this.client.internalCache()));
        //添加okhttp自己的连接拦截器
        interceptors.add(new ConnectInterceptor(this.client));
        if (!this.forWebSocket) {
            //不是websocket情况下,添加自定义的网络连接之后的拦截器,默认是空的
            interceptors.addAll(this.client.networkInterceptors());
        }
        //添加okhttp自己的网络交互连接器(即发送最终请求、接收原始响应等)
        interceptors.add(new CallServerInterceptor(this.forWebSocket));
        //创建责任链
        Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
       //发起责任链式处理器
        return chain.proceed(this.originalRequest);
    }

interceptors和networkInterceptors的区别在于:interceptors在网络连接强处理请求,最后处理响应;networkInterceptors在网络连接之后处理请求,先用interceptors处理响应。
RealInterceptorChain是责任链的实例,它内部通过按序(优先调度排在前面)一一调度interceptors的拦截器,再包装成RealInterceptorChain进行调度完成HTTP请求和HTTP响应。

public final class RealInterceptorChain implements Chain {
    .....

    public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call, EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
        //拦截器列表
        this.interceptors = interceptors;
        //请求建立的连接
        this.connection = connection;
        //流量分配管理器
        this.streamAllocation = streamAllocation;
        //http版本请求与响应处理器
        this.httpCodec = httpCodec;
        //调度第几个拦截器,默认是0,即从interceptors的第一个开始调度
        this.index = index;
        //请求内容
        this.request = request;
       //请求对象
        this.call = call;
        //事件监听
        this.eventListener = eventListener;
        //连接超时时间
        this.connectTimeout = connectTimeout;
         //接收(读)超时时间
        this.readTimeout = readTimeout;
         //发送(写)超时时间
        this.writeTimeout = writeTimeout;
    }

    ......
    //调度链的职责
    public Response proceed(Request request) throws IOException {
        return this.proceed(request, this.streamAllocation, this.httpCodec, this.connection);
    }
    //调度链的职责
    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
        if (this.index >= this.interceptors.size()) {
            //断言执行第几个拦截器不能超过拦截器的总个数
            throw new AssertionError();
        } else {
            //请求次数加1
            ++this.calls;
            if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
                 //如果已建立连接,且连接的域名和端口号和请求的域名和端口号不一样,抛出异常
                throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must retain the same host and port");
            } else if (this.httpCodec != null && this.calls > 1) {
                //如果已连接,且请求次数超过一次,抛出异常
                throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must call proceed() exactly once");
            } else {
                //创建下一个需要执行的任务链
                RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request, this.call, this.eventListener, this.connectTimeout, this.readTimeout, this.writeTimeout);
                //取出本次任务链需要处理的拦截器
                Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);
                //通过执行拦截器,再执行传入的下一个任务链,直至完成HTTP请求,并得到响应内容
                Response response = interceptor.intercept(next);
                if (httpCodec != null && this.index + 1 < this.interceptors.size() && next.calls != 1) {
                    //如果已连接,且下一个要执行的拦截器存在,且下一个要执行拦截器已执行过,则抛出异常
                    throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once");
                } else if (response == null) {
                    //如果响应为空,抛出异常
                    throw new NullPointerException("interceptor " + interceptor + " returned null");
                } else if (response.body() == null) {
                    //如果响应没有body排除异常
                    throw new IllegalStateException("interceptor " + interceptor + " returned a response with no body");
                } else {
                    //返回响应
                    return response;
                }
            }
        }
    }
}

责任链实现类RealInterceptorChain其实就是按下按序(优先调度排在前面)一一调度interceptors的拦截器,最终完整HTTP的请求,每个拦截器会被包装成责任链实现类,每个拦截器都各司其职完成各种的职责,再调用下一个职责直至完成HTTP请求,我们来看看默认情况下第一个责任链拦截器RetryAndFollowUpInterceptor:

//chain:下一个责任
public Response intercept(Chain chain) throws IOException {
        //取出请求内容
        Request request = chain.request();
        //转为真实的下一个责任
        RealInterceptorChain realChain = (RealInterceptorChain)chain;
        //从下一个责任中获取请求对象
        Call call = realChain.call();
        //从下一个责任中获取监听对象
        EventListener eventListener = realChain.eventListener();
        //创建流量分派对象
        this.streamAllocation = new StreamAllocation(this.client.connectionPool(), this.createAddress(request.url()), call, eventListener, this.callStackTrace);
        //需要继续发起请求的数量(失败重连、重定向等)
        int followUpCount = 0;
        //先去的响应
        Response priorResponse = null;

        while(!this.canceled) {
            //请求未取消
            //释放连接
            boolean releaseConnection = true;

            Response response;
            try {
                //调用下一个责任,完成下一个下一个责任的任务,并从中得到响应
                response = realChain.proceed(request, this.streamAllocation, (HttpCodec)null, (RealConnection)null);
                 //取消释放连接
                releaseConnection = false;
            } catch (RouteException var16) {
                //出现路由异常
                if (!this.recover(var16.getLastConnectException(), false, request)) {
                    //当前异常不可覆盖修正,则抛出异常
                    throw var16.getLastConnectException();
                }
                //当前异常可覆盖修正,继续循环内容
                //取消释放连接
                releaseConnection = false;
                continue;
            } catch (IOException var17) {
                //出现IO异常
                boolean requestSendStarted = !(var17 instanceof ConnectionShutdownException);
                if (!this.recover(var17, requestSendStarted, request)) {
                    //当前异常不可覆盖修正,则抛出异常
                    throw var17;
                }
                //当前异常可覆盖修正,继续循环内容
                //取消释放连接
                releaseConnection = false;
                continue;
            } finally {
                if (releaseConnection) {
                    //释放连接
                    //调用流量分配的streamFailed处理相关异常
                    this.streamAllocation.streamFailed((IOException)null);
                    //调用流量分配进行相关资源的释放:io、连接等
                    this.streamAllocation.release();
                }

            }
            
            if (priorResponse != null) {
                 //父级响应不为空,重新构建响应对象
                response = response.newBuilder().priorResponse(priorResponse.newBuilder().body((ResponseBody)null).build()).build();
            }
            //构建新需要继续发起请求的请求内容
            Request followUp = this.followUpRequest(response);
            if (followUp == null) {
                //无需继续发起请求
                if (!this.forWebSocket) {
                   //非websocket,则释放相关资源
                    this.streamAllocation.release();
                }
                //返回响应对象
                return response;
            }
            //关闭响应对象body
            Util.closeQuietly(response.body());
            //需要继续发起请求数 + 1
            ++followUpCount;
            if (followUpCount > 20) {
                //如果需要继续发起请求数 > 20 个
                //释放相关资源并抛出异常,表示请求无法完成
                this.streamAllocation.release();
                throw new ProtocolException("Too many follow-up requests: " + followUpCount);
            }

            if (followUp.body() instanceof UnrepeatableRequestBody) {
                //如果需要继续发起请求的请求内容 是不可重复的
                //释放相关资源并抛出异常,一般是服务器需要将一些空闲的连接关闭
                this.streamAllocation.release();
                throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
            }

            if (!this.sameConnection(response, followUp.url())) {
                //如果响应对象和需要继续发起请求的连接不是同一个
                //释放相关资源
                this.streamAllocation.release();
                //重新构建一个流量分配器
                this.streamAllocation = new StreamAllocation(this.client.connectionPool(), this.createAddress(followUp.url()), call, eventListener, this.callStackTrace);
            } else if (this.streamAllocation.codec() != null) {
                //如果流量分配器的HTTP请求和响应处理器不为空,抛出异常,表示当前响应正在关闭其body
                throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?");
            }
            //设置需要继续发起请求的请求内容为下一次请求的请求内容
            request = followUp;
            //设置父亲响应对象
            priorResponse = response;
        }
        //循环正常接收,表示请求已取消
        //释放相关资源
        this.streamAllocation.release();
        //抛出请求已取消异常
        throw new IOException("Canceled");
    }

RetryAndFollowUpInterceptor的职责主要是构建StreamAllocation(流量分配,用户处理建立连接相关控制),接着就调用下一个责任执行下一个任务,再完成HTTP请求并得到响应对象,再对响应对象进行处理(RetryAndFollowUpInterceptor对Response的职责主要是确定是否需要继续发起请求,如果需要就按要求发起新的请求,如果不需要要么返回Response要么抛出异常)。

具体其他责任链的职责是什么,是okhttp其他章节进行解析的。

总结

okhttp的HTTP请求总体流程是使用OkhttpClient.Builder构建okhttp客户端OkhttpClient,调用OkhttpClient的newCall构建Http请求对象类RealCall,RealCall的execute实现同步请求,RealCall的enqueue实现异步请求。不论同步还是异步,最终都会通过责任链模式完成HTTP请求。
同步请求与异步请求的主要区别:

  • 同步不需要线程池调度请求,而异步需要线程池调度(同步需要调用者自行线程池管理,防止过多占用CPU资源)
  • 同步没有最大请求数和同一域名最大并发数的限制,而异步有
  • 同步是在调用时直接返回响应结果,异步是通过回调方法回调成功或者失败

相关文章

网友评论

      本文标题:okhttp原理解析之整体流程

      本文链接:https://www.haomeiwen.com/subject/aanafrtx.html