您的位置:首页 > 理论基础 > 计算机网络

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

2017-05-09 20:44 330 查看

一,概述

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

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

首先提出以下几个问题:

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

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

Okhttp上传文件的原理。

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

Okhttp的网络请求的本质。

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之后的数据属于响应体。服务器和客户端都遵从这个协议,就能区分出响应头和响应体了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  okhttp源码分析