截止到上一篇blog,OKHttp的源码分析已结束,由于篇幅有限,个人能力不足,分析不够细致,也不够完美,希望小伙们多多指正。
这篇blog以带着问题找答案的方式再次回顾下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(), 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上传文件首先将文件封装到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中并没有提供下载文件的功能,但是在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原码分析(五)之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没有任何关系。
这个问题的答案在OKHttp原码分析(七)之HttpStream中的第八条,OKHttp是怎么区分的响应头与响应体。
总结来说是:OKHttp在封装socket时遵从的是http协议,在http协议中规定,将数据分成两份,且以\n为分界点,\n之前的数据属于响应头。\n之后的数据属于响应体。服务器和客户端都遵从这个协议,就能区分出响应头和响应体了。