Android-OkHttp源码分析、拦截器

基本使用

OkHttpGitHub

OkHttp官网

现在4.0以后基于Kotlin…
我们今天来看看3.14.2版,基于java…
kotlin目前博主不熟 - -!

添加依赖

implementation(“com.squareup.okhttp3:okhttp:3.14.2”)

代码使用

我们先来看看基本使用

建造者模式:一般在构建对象的过程需要配置很多很多的参数时,可以使用建造者模式来构建实体对象。

  OkHttpClient client = new OkHttpClient();
        //建造者模式:一般在
        Request request = new Request.Builder()
                .url(url)
                .get()  //默认为GET请求,可以不写
                .build();
        final Call call = client.newCall(request);
        try {
               //call.execute();//同步
               call.enqueue(new Callback() {	//异步
                @Override
                public void onFailure(Call call, IOException e) {
                    
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {

                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

源码分析

第一步:构建 OkHttpClient

第一步:我们先来看这个建造者模式在构建 OkHttpClient(new OkHttpClient)的时候做的事情

无参构造方法:内部自己new了一个Builder对象,并传入到了无修饰符的构造方法中;
有参构造方法:内部将个传入进来的Builder对象进行取值,并赋值到类中的成员变量中;
无修饰符的情况姑且称为default,访问范围是package,就是同一个包中的类可访问

....
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  ....

  public OkHttpClient() {
  //在我们没有传入Builder对象的时候自己去new了一个,并传入到了下面这一个构造方法中
    this(new Builder());
  }

 OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = Util.platformTrustManager();
      this.sslSocketFactory = newSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    if (sslSocketFactory != null) {
      Platform.get().configureSslSocketFactory(sslSocketFactory);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.callTimeout = builder.callTimeout;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;

    if (interceptors.contains(null)) {
      throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
      throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
  }

我们再来看看OkHttpClient里面的Builder静态内部类
来看看Builder的两个构造方法

上面OkHttpClient构造方法中new了一个无参的Builer构造方法,这个无参构造方法里面进行了默认值的设置,上面有参构造方法中就讲这些默认参数设置到了自己的成员变量中

  public static final class Builder {
    //调度器
    Dispatcher dispatcher;
    //代理类:默认有三种代理模式DIRECT(直连),HTTP(http代理),SOCKS(SOCKS代理)
    @Nullable Proxy proxy;
    //协议集合,协议类,用来表示使用的协议版本,比如:http/1.0、http1.1、spdy/3.1、h2等
    List<Protocol> protocols;
    //连接规范,用于配置Socket连接层。对于Https,还能配置安全传输层协议(TLS)版本和密码套件
    List<ConnectionSpec> connectionSpecs;
    //拦截器,可以监听、重写和重试请求等
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    //代理选择类,默认不适用代理,即使用直连方式,当前我们可以使用自定义指定URL使用某种代理,类似代理软件的PAC功能
    ProxySelector proxySelector;
    //Cookie 的保存获取
    CookieJar cookieJar;
    //缓存类,内部使用了DiskLruCache磁盘缓存来进行管理缓存
    @Nullable Cache cache;
    //内置缓存
    @Nullable InternalCache internalCache;
    //Socket的抽象创建工厂,通过createSocket来创建Socket
    SocketFactory socketFactory;
    //安全套接层工厂,HTTPS相关,用于创建SSLSocket,一般配置HTTPS证书
    //对于不受信任的证书一般会提示 javax.net.ssl.SSLHandshakeException异常
    @Nullable SSLSocketFactory sslSocketFactory;
    @Nullable CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    //连接池:我们通常将一个客户端和服务端和连接抽象为一个connection,
    //而每一个connection都会被存放在ConnectionPool中,由它进行统一管理
    //例如有一个相同的http请求产生时,connection就可以得到复用
    ConnectionPool connectionPool;
    //域名解析系统
    Dns dns;
    //是否遵循SSL重定向
    boolean followSslRedirects;
    //是否重定向
    boolean followRedirects;
    //失败是否重新连接
    boolean retryOnConnectionFailure;
    //回调超时时长
    int callTimeout;
    //连接超时时长
    int connectTimeout;
    //读取超时时长
    int readTimeout;
    //写入超时时长
    int writeTimeout;
    //与WebSocket有关,为了保持长连接,我们必须间隔一段时间发送一个ping指令进行保活
    int pingInterval;
	//无参的时候,做了一下默认值初始化
    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
	//有参OkHttpClient的时候,将这些值进行了赋值
    Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
      this.protocols = okHttpClient.protocols;
      this.connectionSpecs = okHttpClient.connectionSpecs;
      this.interceptors.addAll(okHttpClient.interceptors);
      this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
      this.eventListenerFactory = okHttpClient.eventListenerFactory;
      this.proxySelector = okHttpClient.proxySelector;
      this.cookieJar = okHttpClient.cookieJar;
      this.internalCache = okHttpClient.internalCache;
      this.cache = okHttpClient.cache;
      this.socketFactory = okHttpClient.socketFactory;
      this.sslSocketFactory = okHttpClient.sslSocketFactory;
      this.certificateChainCleaner = okHttpClient.certificateChainCleaner;
      this.hostnameVerifier = okHttpClient.hostnameVerifier;
      this.certificatePinner = okHttpClient.certificatePinner;
      this.proxyAuthenticator = okHttpClient.proxyAuthenticator;
      this.authenticator = okHttpClient.authenticator;
      this.connectionPool = okHttpClient.connectionPool;
      this.dns = okHttpClient.dns;
      this.followSslRedirects = okHttpClient.followSslRedirects;
      this.followRedirects = okHttpClient.followRedirects;
      this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
      this.callTimeout = okHttpClient.callTimeout;
      this.connectTimeout = okHttpClient.connectTimeout;
      this.readTimeout = okHttpClient.readTimeout;
      this.writeTimeout = okHttpClient.writeTimeout;
      this.pingInterval = okHttpClient.pingInterval;
    }

   ......

    public OkHttpClient build() {
      return new OkHttpClient(this);
    }
  }

第二步:创建Request对象

第二步:创建Request对象,也是建造者模式构建出来的

new Request.Builder()的时候也是在初始化一些成员变量;
无参构造初始化了一个默认方法,一个请求头的Builder对象
有参构造的时候讲传入的参数进行赋值到成员变量中

 public static class Builder {
    @Nullable HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable RequestBody body;
    Map<Class<?>, Object> tags = Collections.emptyMap();

    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.tags = request.tags.isEmpty()
          ? Collections.emptyMap()
          : new LinkedHashMap<>(request.tags);
      this.headers = request.headers.newBuilder();
    }

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

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

      return url(HttpUrl.get(url));
    }

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

 
    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

    public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

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

    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    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 using {@code Object.class} as a key. */
    public Builder tag(@Nullable Object tag) {
      return tag(Object.class, tag);
    }

  
    public <T> Builder tag(Class<? super T> type, @Nullable T tag) {
      if (type == null) throw new NullPointerException("type == null");

      if (tag == null) {
        tags.remove(type);
      } else {
        if (tags.isEmpty()) tags = new LinkedHashMap<>();
        tags.put(type, type.cast(tag));
      }

      return this;
    }

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

第三步:创建Call对象

第三步:创建Call对象

final Call call = client.newCall(request)
newCall这个方法是OkHttpClient的方法

 @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

然后跟进RealCall.newRealCall去看看。
Call对象是个接口。
RealCall是Call对象的实现类,上面newRealCall返回的是这个Call的实现类。
newRealCall方法传入了OkHttpClient、Request对象、是否说使用WebSocket。
然后创建了RealCall对象,并返回了RealCall对象。

final class RealCall implements Call {

	......
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }
}
	......

第四步:发起请求(同步)

第四步:发起请求(同步)

execute():同步 一般用的比较少的…
getResponseWithInterceptorChain()方法的执行,我们留到后面来看,因为同步和异步最终都会调用这个方法…
只不过异步是创建了一个线程池,在线程池中执行了该方法的调用

接下来我们来看看这个同步的方法
因为Call是个接口,所以我们来看看他的实现类RealCall里面这个同步的方法实现;

 @Override public Response execute() throws IOException {
 	//同步锁
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
    //最终调用了OkHttpClient对象调度器(分发)的executed方法
      client.dispatcher().executed(this);
      //晚点我们再来看看这个方法的执行
      return getResponseWithInterceptorChain();
    } finally {
      client.dispatcher().finished(this);
    }
  }

进入到调度器(分发)的executed方法看看;
将RealCall 对象传入进来,并添加到了队列集合当中;
到这一步这个executed方法就执行完了

//队列集合
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  synchronized void executed(RealCall call) {
  //将我们这个RealCall对象添加到队列集合当中
    runningSyncCalls.add(call);
  }

第四步:发起请求(异步)

第四步:发起请求(异步)

enqueue():异步

接下来我们看看异步请求这个方法的调用
因为Call是个接口,所以我们来看看他的实现类RealCall里面这个异步的方法实现;

client.dispatcher().enqueue(new AsyncCall(responseCallback));
同样这里还是调用的调度器(分发)里面的方法;
但是这里调用的是异步的方法,并传入了OkHttp里面自定义的Runnable的接口实现类,并传入了我们自己实现的接口,等待回调;

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    //创建了一个子线程,将传入进来的回调传入到子线程,调用调度器(分发)的enqueue方法,把这个穿件出来的子线程传入进去了
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

我们来看看AsyncCall这个在RealCall中的内部类NamedRunnable的子类
NamedRunnable 是实现了Runnable接口的,也就是创建了个子线程。

 final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;
    private volatile AtomicInteger callsPerHost = new AtomicInteger(0);

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

    ......
  }

client.dispatcher().enqueue(new AsyncCall(responseCallback));
再来看看这个调度器里面的enqueue方法

//子线程队列
 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

void enqueue(AsyncCall call) {
    synchronized (this) {
    //将外面创建的子线程,添加到了子线程集合队列当中
      readyAsyncCalls.add(call);

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    //调用了自己的方法
    promoteAndExecute();
  }

promoteAndExecute()来看看这个方法内部
将上面添加的子线程集合进行了过滤,放入到了局部子线程集合中
再次遍历过滤后的子线程集合,创建线程池传入到,子线程的executeOn方法中。

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));
	//创建了一个局部子线程集合
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
    //拿到了子线程的迭代器,进行迭代
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
		//超过了设置的最大请求数
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        //超过了设置的主机最大容量 host
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        //过滤掉了一些请求后,添加到了局部变量的子线程集合中去
        executableCalls.add(asyncCall);
        
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
	//遍历过滤后的请求集合队列
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      //将创建出来的线程池传入到子线程的executeOn方法中
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }
	//创建了线程池
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
    //创建线程池
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

来看看AsyncCall .executeOn方法的执行;
记得上面调用时传入了一个创建的额线程池吗?
executeOn方法传入了线程池;
在这个方法内部调用了线程器的execute方法,进行执行了改子线程,也就会调用子线程的run方法

 final class AsyncCall extends NamedRunnable {

 void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
      //执行线程池,传入的是this,当前这个子线程,也就是会调用到当前这个子线程的run方法。
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }
  }

父类(NamedRunnable )里面的run方法执行,
父类里面的run方法中,先获取了下当前的线程名,设置了新的线程名,然后执行了execute()抽象方法,最后又将旧的线程名设置回去了。
最终调用的是execute()抽象方法,我们还得回溯到子类当中去;

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
    //调用了抽象方法,我们还得去看看子类的实现
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

AsyncCall 子类的execute抽象方法的实现…

final class AsyncCall extends NamedRunnable {
	......
	
	@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
      //获取了Response 对象
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        //还记得这个responseCallback吗?  这里也就是我们传入进来的CallBack实现...也就是在这里我们将Response回调出去了
        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);
      }
    }
    ......
}

接下来我们看看getResponseWithInterceptorChain()这个方法
还记得上面同步也调用了这个方法吗?
只不过上面的同步方法调用时没有在线程池中执行,执行完的结果直接return出去了,而这个异步是创建了线程池,在线程池中执行了这个方法…并将结果通过CallBack回调出去了…

getResponseWithInterceptorChain()
这个方法才是做出真正的响应,返回了Response对象;
1、创建了拦截器集合;
2、添加了各种拦截器(包括最先添加的用户自己定义的拦截器);
3、最终调用了 chain.proceed(originalRequest);得到一个Response 对象将其返回出去了;
等会儿在来看看proceed这个方法的调用

Response getResponseWithInterceptorChain() throws IOException {
    // 创建了拦截器集合,添加了各种拦截器
    List<Interceptor> interceptors = new ArrayList<>();
    //用户添加的全局拦截器  也就是我们自己自定义的拦截器
    interceptors.addAll(client.interceptors());
    //错误、重定向拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //桥接拦截器,桥接应用层与网络层,添加必要的头
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存处理的拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //连接拦截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
    //通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效
      interceptors.addAll(client.networkInterceptors());
    }
    //真正访问服务器的拦截器
    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 response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      //将其返回出去了
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

时序图

下面来看看上面同步和异步的时序图
在这里插入图片描述

拦截器(分析)

接下来我们来看看OkHttp里面的拦截器
Interceptor 是一个接口
既然是个接口,肯定在框架里面会有接口的实现
是否还记得getResponseWithInterceptorChain() 这个方法?
方法中创建了一个拦截器集合,往里面添加了很多拦截器…

package okhttp3;
......

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

OkHttp当中分为哪两类别?
1、应用拦截器addInterceptor():关注对象是request请求对象;
2、网络拦截器addNetworkInterceptor():关注的是发起请求,请求过程中,以及请求的数据之后;可以去做重定向,以及请求失败了去做重试;

在getResponseWithInterceptorChain()中添加完了拦截器集合之后
创建了一个RealInterceptorChain()拦截器,并将拦截器集合都传入进去了
RealInterceptorChain实现了implements Interceptor.Chain接口,
最终调用 Response response = chain.proceed(originalRequest);得到Response对象

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

上面通过RealInterceptorChain调用了proceed方法,我们跟进去看看proceed方法中的代码,里面各种判断,不符合的都会抛出异常;我们来看主要的代码;
这里面又new了一个自己,并将index+1,还记得是传入了拦截器集合吗?这里一个一个的将上面添加的拦截器取了出来,拦截器调用了intercept方法获取Response对象;
注:
结合拦截器的实现类中的intercept方法去看,这里是取出当前的拦截器的同时,准备new了个RealInterceptorChain对象,并将上一个拦截器获取到的数据传递到了下一个RealInterceptorChain对象中,方便在拦截器实现类中调用新的对象的proceed方法,拿到集合中的下一个拦截器。可以说是这里只是在做个标记,我这么理解
责任链模式

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, transmitter, exchange);
  }

  public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
	......
    calls++;

	......

    // 自己创建了自己,然后将下表index+1;
    //我们结合拦截器的实现类中的intercept方法去看,大概就明白了
    //准备new了个RealInterceptorChain对象,并将上一个拦截器获取到的东西传递到了下一个RealInterceptorChain对象中,方便在拦截器实现类中调用新的对象的proceed方法,拿到集合中的下一个拦截器。可以说是这里只是在做个标记,我这么理解
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
        //获取了集合中当前下标的拦截器
    Interceptor interceptor = interceptors.get(index);
    //拦截器调用了intercept方法,将刚刚new出来的自己传入进去了,获取到Response对象
    Response response = interceptor.intercept(next);
    
	......

    return response;
  }

既然interceptor拦截器是个接口,所以我们得去看看他的实现类
看看这个intercept方法的实现
看到中间这句 response = realChain.proceed(request, transmitter, null);
realChain记得是传入进来的new了新的RealInterceptorChain 方法,又调用了RealInterceptorChain 类中的proceed这个方法

public final class RetryAndFollowUpInterceptor implements Interceptor {
	......
//刚刚是拦截器调用了intercept这个方法  外部RealInterceptorChain 的proceed方法中调用的,传入了一个新的RealInterceptorChain对象,记住是新的,创建时将index下标+了1
@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 {
      //又调用到了RealInterceptorChain 类中的proceed方法,记得吗?
      //不过是新new的RealInterceptorChain 对象的proceed方法
        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();
        }
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      Request followUp = followUpRequest(response, route);

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
  }
	......
}

ConnectInterceptor 拦截器

我们大致来看一个拦截器ConnectInterceptor 拦截器

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

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

newExchange方法调用
先进行了同步锁,做了一些异常判断;

 Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    synchronized (connectionPool) {
      if (noMoreExchanges) {
        throw new IllegalStateException("released");
      }
      if (exchange != null) {
        throw new IllegalStateException("cannot make a new request because the previous response "
            + "is still open: please call response.close()");
      }
    }
	//获取到时HTTP1还是HTT2的链接方式
	//对请求和响应去设置了各种信息,跟进去能看到设置了各种超时时间,在find这方法里面一步步跟进去
	//会看到个RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
	//一步步跟进去会进入到RealConnection类中的connect链接的方法
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    synchronized (connectionPool) {
      this.exchange = result;
      this.exchangeRequestDone = false;
      this.exchangeResponseDone = false;
      return result;
    }
  }

通过上面调用这个转换器的find方法我们跟进来看看这个里面
获取一个有效的链接,尝试从复用池中去拿

final class ExchangeFinder {
//看名字是个连接池
  private final RealConnectionPool connectionPool;

public ExchangeCodec find(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
      //获取到超时一些超时设置
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
    //获取连接  核心代码  调用了自己内部的findHealthyConnection方法
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      return resultConnection.newCodec(client, chain);
    } catch (RouteException e) {
      trackFailure();
      throw e;
    } catch (IOException e) {
      trackFailure();
      throw new RouteException(e);
    }
  }
 private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
    //
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        candidate.noNewExchanges();
        continue;
      }

      return candidate;
    }
  }
  //获取到了一个有效的连接
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    RealConnection releasedConnection;
    Socket toClose;
    //connectionPool  连接池
    synchronized (connectionPool) {
      if (transmitter.isCanceled()) throw new IOException("Canceled");
      hasStreamFailure = false; // This is a fresh attempt.

      // Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new exchanges.
      releasedConnection = transmitter.connection;
      toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
          ? transmitter.releaseConnectionNoEvents()
          : null;

      if (transmitter.connection != null) {
        // We had an already-allocated connection and it's good.
        result = transmitter.connection;
        releasedConnection = null;
      }

      if (result == null) {
        // 尝试先从复用池里面去拿
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        } else if (nextRouteToTry != null) {
          selectedRoute = nextRouteToTry;
          nextRouteToTry = null;
        } else if (retryCurrentRoute()) {
          selectedRoute = transmitter.connection.route();
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
	//这里是如果已经拿到了这个有效连接的话,我们就直接返回出去了
      return result;
    }

    // If we need a route selection, make one. This is a blocking operation.
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    List<Route> routes = null;
    synchronized (connectionPool) {
      if (transmitter.isCanceled()) throw new IOException("Canceled");

      if (newRouteSelection) {
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        routes = routeSelection.getAll();
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        }
      }

      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // 如果最终都没找到可以使用的链接的话,就自己去新增一次链接
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      connectingConnection = null;
      // Last attempt at connection coalescing, which only occurs if we attempted multiple
      // concurrent connections to the same host.
      if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
        // We lost the race! Close the connection we created and return the pooled connection.
        result.noNewExchanges = true;
        socket = result.socket();
        result = transmitter.connection;
      } else {
        connectionPool.put(result);
        transmitter.acquireConnectionNoEvents(result);
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }
}

看到这行 connectSocket(connectTimeout, readTimeout, call, eventListener);
调用了自己类中的connectSocket方法
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);最终调用的是Socket连接

public final class RealConnection extends Http2Connection.Listener implements Connection {
 ......

 public void connect(int connectTimeout, int readTimeout, int writeTimeout,
 	......
          connectSocket(connectTimeout, readTimeout, call, eventListener);
	......
	}
	......
private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    //设置超时
    rawSocket.setSoTimeout(readTimeout);
    try {
    //连接Socket
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }

    // The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
    // More details:
    // https://github.com/square/okhttp/issues/3245
    // https://android-review.googlesource.com/#/c/271775/
    try {
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    } catch (NullPointerException npe) {
      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
        throw new IOException(npe);
      }
    }
  }
 
}

我们看看最后调用到Platform类中的connectSocket方法

public class Platform {
  public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout)
      throws IOException {
      //Socket连接  address  ip]端口都在里面
    socket.connect(address, connectTimeout);
  }
}

时序图

来看看这个连接拦截器的时序图
发现类好多,但是都是单一职责,方便进行扩展
在这里插入图片描述

复用池

我们来大致看下这个复用池
看看这里面创建了一个线程池

public final class RealConnectionPool {
	//创建了一个线程池
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
}

复用池(线程池)

复用与一次响应过程不一样,一次响应式每次响应后都会去释放该链接;
复用池是在多长时间内,会进行复用,不用每次去重新打开链接,会去复用该链接;
阻塞时的连接,有链接过来时会被唤醒;
复用是需要ip和端口一样的情况下才会进行复用;
连接类型需要使用长连接Connection(请求头)

在这里插入图片描述

CallServerInterceptor拦截器

再来看看最后这个拦截器:interceptors.add(new CallServerInterceptor(forWebSocket));
最后执行到这个拦截器的时候,我们来看看它里面的intercept方法实现,最终他没有在调用proceed这个方法了吧
也就是到这个拦截器执行完的时候,最后通过所有拦截器的信息去获取最终的Response对象了,所以在添加连接器集合的时候这个是在最后添加的拦截器

来看看这个拦截器拦截的方法里面做了啥…
writeRequestHeaders写入了请求头

public final class CallServerInterceptor implements Interceptor {
@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().isDuplex()) {
          // 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() == null || !request.body().isDuplex()) {
      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);

    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
}
public final class Exchange {
//写入请求头数据  上面这个拦截器调用的方法
  public void writeRequestHeaders(Request request) throws IOException {
    try {
    //监听回调  开始写入请求头
      eventListener.requestHeadersStart(call);
      //这个codec是最终的请求方式吧,对吧
      // HTTP1   HTT2     写入请求头
      codec.writeRequestHeaders(request);
      //回调监听,请求头数据写入完成了
      eventListener.requestHeadersEnd(call, request);
    } catch (IOException e) {
      eventListener.requestFailed(call, e);
      trackFailure(e);
      throw e;
    }
  }
}

看到没有,这个请求方式调用写入的方法,区分了http1、http2
在这里插入图片描述

这个是Http1里面最终拼接请求头的方法

/**
  *
  *Http1  的方式
  */
public final class Http1ExchangeCodec implements ExchangeCodec {
  ......
    private final BufferedSink sink;
  ......
 //写入请求头请求数据 
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }
  //发射数据   记得Socket的发射数据吗flush
    @Override public synchronized void flush() throws IOException {
      if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
      //最终肯定是调用了OutputStream.flush()
      sink.flush();
    }
 }

这里是HTTP2拼接请求头的方法

@Override public void writeRequestHeaders(Request request) throws IOException {
    if (stream != null) return;

    boolean hasRequestBody = request.body() != null;
    List<Header> requestHeaders = http2HeadersList(request);
    stream = connection.newStream(requestHeaders, hasRequestBody);
    // We may have been asked to cancel while creating the new stream and sending the request
    // headers, but there was still no stream to close.
    if (canceled) {
      stream.closeLater(ErrorCode.CANCEL);
      throw new IOException("Canceled");
    }
    stream.readTimeout().timeout(chain.readTimeoutMillis(), TimeUnit.MILLISECONDS);
    stream.writeTimeout().timeout(chain.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
  }

RealConnection这个对象是有效连接,前面一系列操作就是为了得到这个有效链接,尝试从复用池去获取,如果没有,就会去创建新的Socket链接

HttpURLConnection是用的Socket;
OkHttp里面也是使用Socket来发送请求,但是他跳过了Android封装的HttpURLConnection;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值