OKHttp原码分析(八)之必须明白的几个问题

一,概述

截止到上一篇blog,OKHttp的源码分析已结束,由于篇幅有限,个人能力不足,分析不够细致,也不够完美,希望小伙们多多指正。

这篇blog以带着问题找答案的方式再次回顾下OKHttp的源码。

首先提出以下几个问题:

  1. Okhttp的异步请求是怎么开启子线程的?
  2. 异步请求时回调方法在哪被调用的?回调方法执行在哪个线程?
  3. Okhttp上传文件的原理。
  4. OKHttp下载文件的原理,是否支持断点续传?
  5. Okhttp的网络请求的本质。
  6. socket中不区分响应头和响应体,为什么底层封装socket的OKHttp就区分了响应头和响应体?

二,Okhttp的异步请求是怎么开启子线程的?

这个问题 答案主要看OKHttp原码分析(一)中的第六点:RealCall的enqueue方法实现异步请求。

在RealCall的enqueue方法中调用了Dispatcher的enqueue方法,在Dispatcher的enqueue方法中有下面这行代码:
executorService().execute(call);
这行代码中的executorService()方法得到一个线程池对象,然后调用线程池对象的execute方法,此时call对象的run方法就行在了子线程中。

executorService()方法的源码是:

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

由源码可知此时创建了一个ThreadPoolExecutor对象。这个线程池的特点是:
1,核心线程数为0。
2,最大线程数是Integer的最大值,这里可以理解为无限多。
3,非核心线程存活时间为60秒。

总结:okhttp的异步请求是在Dispatcher的enqueue方法中使用线程池开启的子线程。

三,异步请求时回调方法在哪被调用的?回调方法执行在哪个线程?

这个问题 还需要看OKHttp原码分析(一)中的第六点:RealCall的enqueue方法实现异步请求。

RealCall的enqueue方法的源码如下:

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

我们看下AsyncCall类。
AsyncCall类是RealCall的内部类,AsyncCall继承NamedRunnable类,NamedRunnable又继承Runnable类。在NamedRunnable类中声明了一个抽象方法execute(),且在run方法中调用了。我们由代码executorService().execute(call)知道此时会调用run方法,从而会调用execute方法。
下面看AsyncCall的execute方法的源码(注意:这个方法执行在子线程):

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          responseCallback.onResponse(RealCall.this, response);
        }
      }
    }

此时发现,原来回调方法在这被调用的啊。

总结:回调方法在AsyncCall的execute方法中被调用,即是在AsyncCall的run方法中被调用。而AsyncCall是Runnable的子类,所以run方法会执行在子线程,所以回调方法也执行在子线程中。

四, Okhttp上传文件的原理。

使用OKHttp上传文件首先将文件封装到RequestBody中,创建带有文件的RequestBody使用的代码是:

MediaType fileType = MediaType.parse("File/*");//数据类型为json格式,
File file = new File("path");//file对象.
RequestBody body = RequestBody.create(fileType , file );

下面查看RequestBody类create方法的源码:

public static RequestBody create(final MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
      @Override public MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(file);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }

写文件的核心代码是:

source = Okio.source(file);//根据文件得到输入流对象。
sink.writeAll(source);//将输入流对象写出去。

writeAll是RealBufferSink类的方法,这个也是属于Okio框架中的。方法的原码是:

  @Override public long writeAll(Source source) throws IOException {
    long totalBytesRead = 0;
    for (long readCount; (readCount = source.read(buffer, Segment.SIZE)) != -1; ) {
      totalBytesRead += readCount;
      emitCompleteSegments();
    }
    return totalBytesRead;
  }

emitCompleteSegments方法的源码是:

  @Override public BufferedSink emitCompleteSegments() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    long byteCount = buffer.completeSegmentByteCount();
    if (byteCount > 0) sink.write(buffer, byteCount);
    return this;
  }

此时发现:上传文件就是使用的输出流的write方法,且使用字节数组的方式写出,字节数组的长度是:Segment.SIZE = 8192。

注意:使用OKHttp上传文件是直接写入,并不支持断点续传。

五,OKHttp下载文件的原理,是否支持断点续传?

OKHttp使用详解中讲到OKHttp中并没有提供下载文件的功能,但是在Response中可以获取流对象,有了流对象之后我们自己实现文件的下载。代码如下:

try{
    InputStream  is = response.body().byteStream();//从服务器得到输入流对象
    long sum = 0;
    File dir = new File(mDestFileDir);
    if (!dir.exists()){
        dir.mkdirs();
    }
    File file = new File(dir, mdestFileName);//根据目录和文件名得到file对象
    FileOutputStream  fos = new FileOutputStream(file);
    byte[] buf = new byte[1024*8];
    int len = 0;
    while ((len = is.read(buf)) != -1){
        fos.write(buf, 0, len);
    }
    fos.flush();
    return file;

}

总结:OKHttp并没有封装文件的下载功能,需要自己去实现。所以要想支持断点续传功能也需要自己去实现。

六,Okhttp的网络请求的本质

OKHttp原码分析(五)之Interceptor中的第三条,CallServerInterceptor类分析中讲到:网络请求的本质是CallServerInterceptor类中的intercept方法。这个方法写入了请求头,写入了请求体,得到了响应头,得到了response对象。但是这些操作都是调用的httpStream类中的方法,在httpStream类的方法中先通过RealConnection对象得到流对象,再调用RequestBody类的方法进行写操作。所以网络请求的本质是在RealConnection类中。所以问题的答案在OKHttp原码分析(六)之RealConnection中。

我们看RealConnection类的connectSocket方法的源码:

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    ;//得到socket对象
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    rawSocket.setSoTimeout(readTimeout);
    try {
    ;//连接socket
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      throw new ConnectException("Failed to connect to " + route.socketAddress());
    }
    source = Okio.buffer(Okio.source(rawSocket));//从socket中获取source 对象。
    sink = Okio.buffer(Okio.sink(rawSocket));//从socket中获取sink 对象。
  }

这个方法里做了三件事情:
1,创建socket对象。
2,连接socket。
3,使用okio包从socket中得到source 对象和sink 对象。

总结:okhttp的网络请求的本质在RealConnection类的connectSocket方法中。这里创建了socket对象,使用socket连接服务器,所以okhttp的网络请求的本质是封装的socket。与httpURLconnection没有任何关系。

七, socket中不区分响应头和响应体,为什么底层封装socket的OKHttp就区分了响应头和响应体?

这个问题的答案在OKHttp原码分析(七)之HttpStream中的第八条,OKHttp是怎么区分的响应头与响应体。

总结来说是:OKHttp在封装socket时遵从的是http协议,在http协议中规定,将数据分成两份,且以\n为分界点,\n之前的数据属于响应头。\n之后的数据属于响应体。服务器和客户端都遵从这个协议,就能区分出响应头和响应体了。

猜你喜欢

转载自blog.csdn.net/fightingxia/article/details/71507345