Okhttp文件上传源码分析

来源:互联网 发布:用u盘怎么重装mac系统 编辑:程序博客网 时间:2024/05/22 04:56

     一直搞开发,也使用各种框架,但是基本上也从来没研究过这些框架的底层是如何实现的。像我们客户端,一般重要的事情就是:网络请求、图片处理、操作数据库、展现View界面,刚好今天哥们有问我一个okhttp网络请求的问题,帮他看完了之后,就详细的走了一遍okhttp框架实现文件上传的基本流程,也算是对网络请求这块的东西学习一下。

     先来看一下我按照思路整理出来的详细的流程图。说起流程图,大家还不能单独看,必须要结合源码一起,流程图时刻可以帮助我们找到当前的点,但是没有源码,光看流程图,你根本不知道这是干什么的,所以大家参考的时候,还是需要对照源码一起分析,哦,还需要说一下,我当前使用的是okhttp-3.2.0.jar的包,可能不同的版本,细节实现会有一些差别。

     流程图当中几个重要的节点我都要红色标注出来了。我要在客户端测试的代码非常简单,因为代码片现在越来越多了,这里就不贴代码了,直接上一张图,因为代码很少,所以大家看着也方便。


     按照上图的流程,我们把整个文件上传的过程三为三个小节来分析:1、RealCall.getResponse之前的逻辑;2、RealCall类的getResponse方法中调用HttpEngine.sendRequest发送请求;3、RealCall类的getResponse方法中调用HttpEngine.readResponse读取响应。

     1、RealCall.getResponse之前的逻辑

     这一步中的逻辑都比较简单,就是一些数据封装类的操作,首先获取到我们要上传的文件,以我们的目标文件为参数创建一个RequestBody对象,我们来看一下RequestBody类的create(MediaType.parse(TYPE), file)方法的实现:

     这个方法的实现非常简单,就是直接构造了一个RequestBody对象返回了,需要给大家说的就是writeTo方法,从这个方法逻辑中也可以看出,我们上传文件时候,就是在这里把文件转换成Source对象,然后通过BufferedSink写入流,最后发送出去的。接着后面的两句创建requestBody、requestPostFile我们就不展开了,就是根据我们的数据创建一个请求对象。我们继续来看最后一句代码,client.newCall(requestPostFile)这一句就是创建一个RealCall对象,传入的参数requestPostFile当中保存了我们要上传的文件数据索引,我们继续来看RealCall类的enqueue方法:

     这里的实现也比较简单,就是判断当前的Call对象是否已执行,一个Call对象不能重复执行,这点和我们的线程对象一样。然后以传进来的Callback对象构造一个RealCall.AsyncCall,这里的forWebSocket是上面设置的,为false,同时获取当前client的分发器,并将构造好的RealCall.AsyncCall交给分发器执行。Dispatcher分发器当中使用的是一个线程池来处理我们的请求的,在它的enqueue方法中就直接执行我们的请求,RealCall.AsyncCall是继承NamedRunnable,而NamedRunnable又实现了Runnable,所以当任务被处理时,就会回调RealCall.AsyncCall类的execute方法,我们来看一下它的实现:

     看一下这个方法的代码,再对比一下我们的流程图,就可以看到,最终回调到我们应用层就是在这里的。而返回给我们的Response对象就是通过getResponseWithInterceptorChain获取到的。当我们的请求因异常而失败时,okhttp框架中都会将其取消,所以最终的结果就根据RealCall.this.canceled标志位来判断,如果取消了,那么肯定是请求失败了,否则请求就是成功的,最后请求完成后,将当前的call从分发器中移除。好,对照着流程图,接下来我们看一下RealCall类的getResponseWithInterceptorChain方法的实现,它当中就是直接调用ApplicationInterceptorChain内部类对象,然后直接调用它的proceed方法。构造它时传进来的第一个参数index为0,而我们当前的实现中,client中的拦截器数量也是0,所以就直接调用RealCall.this.getResponse进行处理。这个方法也是比较核心的方法了,从我们的流程图就可以看出来,在它当中调用了我们下面要讲的两具节点方法:sendRequest()、readResponse(),发送请求,读取响应,读取响应完成后,会将结果保存在HttpEngine类的成员变量userResponse当中,最后将它通过回调返回给应用层。我们来看一下getResponse方法的实现:

     先构造一个HttpEngine对象,然后以!this.canceled为条件进行循环,while循环中的逻辑采用了java label的标签写法,应该和C++中的goto是一样的道理,从实际开发中来看,label标签已经很少采用了,所以一般也不建议大家使用这样的写法,这样的代码易读性不好。这里主要就是第一个try代码块中处理请求,没有异常就直接跳出label173,然后调用this.engine.followUpRequest()取出下一个请求,再以它的返回值为参数构造一个新的HttpEngine,继续在while循环中处理。当然如果followUpRequest取回来的为空,那也就是说只有一个请求,并且已经处理完了,那就直接返回当前的结果var23了。这个方法当中的一些变量命名也不是很好,根本看不出来是啥意思,不知道开发者是怎么想的。这个方法分析完了,就要进入主要的逻辑了。

     2、RealCall类的getResponse方法中调用HttpEngine.sendRequest发送请求

     再来对照流程图,从中也可以看到,这一步的任务就是发送消息头,我们来看一下这个方法的实现:

     在这个方法中,networkRequest不为空,所以就进入最后一个else分支进行处理,第一句this.httpStream = this.connect()就是和服务端取得连接的过程,返回一个HttpStream对象,这个过程非常复杂,也非常重要。因为后边的数据传输就是在这个连接的基础上进行的,像常用的HTTP连接,BT连接等等的都会有这个过程,大家可以去看一下android bluetooth蓝牙模块的源码,也是相同的,我们这里就不展开了。bufferRequestBody是构造HttpEngine对象时传进来的,值为false,所以接下来就是调用writeRequestHeaders、createRequestBody来处理了。connect()返回来的是一个Http2xStream对象,它是实现了HttpStream的,writeRequestHeaders是由它来实现的,我们来看一下writeRequestHeaders方法的实现:

     这个方法当中就是将当前的请求头进行转换,得到一个List requestHeaders数据,然后将它作为参数,调用FramedConnection类的newStream方法,我们接着来看newStream方法的实现:

     这个方法当中就是构造一个FramedStream对象,associatedStreamId值为0,是调用时传进来的,然后调用成员变量this.frameWriter.synStream方法将数据写入流中,往下的我们就不跟踪了。

     再回来,对照流程图,看一下Http2xStream类的createRequestBody方法。它当就很简单,就是调用getSink()将上一步返回来的FramedStream对象的成员变量sink取出来返回给调用者。注意,这里返回的实体是FramedStream.FramedDataSink对象,它是实现了Sink接口的。

     好了,请求头发送完了,再来对照一下流程图,接下来就是读取响应了。

     3、RealCall类的getResponse方法中调用HttpEngine.readResponse读取响应

     我们来看一下这个方法的代码:

     在这里的逻辑中,构造HttpEngine对象时,传入的两个参数forWebSocket、callerWritesRequestBody都是false,所以这里就执行第二个else if分支。可以看到,在okhttp框架中,有很多拦截器,它们以Chain的形式组成一条链,至于这块的东西,我没有去深入研究,就不展开了,我们继续我们的流程分析。这里就是构造一个HttpEngine.NetworkInterceptorChain对象,然后调用它的proceed方法,从我们的流程图当中也可以看到,真正的文件传输就是在这里进行的。我们来看一下proceed方法的逻辑:

     我们在上一步构造HttpEngine.NetworkInterceptorChain对象时,传入的第一个参数index为0,而在这个调用过程当中,client中的拦截器的数量也是0,所以就直接执行到最后的一个else分支当中了。在这里判断我们的request.body() != null,因为我们要上传文件,所以这个条件为true,那么就调用构造好一个BufferedSink对象,然后调用request.body().writeTo(var8)把我们的目标文件写入流中。request.body()获取到的是一个RequestBody对象,但是它的writeTo方法定义的是一个虚函数,而实现就是在我们一开始调用create(final MediaType contentType, final File file)构造Request请求当中,直接new了一个new RequestBody(),大家可以回头看看我们本篇的第一个代码片,这里的writeTo就是回调到那里的,而回调时的参数BufferedSink也是在回调前构造好的。writeTo方法中先调用Okio.source(file),将我们的目标文件转换成Source类型,然后再sink.writeAll(source)把数据写进去,这里的流传输底层实现应该都是一样的,在看蓝牙模块文件传输的代码时,也可以看到这样的逻辑,我们的文件如何传过去的呢?就是把文件通过Stream转换成流,然后将流数据通过Socket发送给目标。

     好了,我们再回到HttpEngine.NetworkInterceptorChain类的proceed方法当中,文件数据写完后,就调用HttpEngine.this.readNetworkResponse()读取响应,最终返回给应用层的Response也就是在这里生成的。这里完成后,再往上退一步,就到了RealCall类的getResponse方法中,请求已经处理完了,响应数据也拿回来了,最后通过this.engine.getResponse()获取到响应,然后就返回给应用层了。

     在这篇文章中,我们只是大概走了一下Okhttp网络框架中的一个最简单的流程,还有很多非常重要的细节没有深入研究,比如将文件读入到流之后,最后如何通过Socket发送过去的,中间过程还有涉及到很多非常重要的对象,如文件转换后得到的Source,写文件时使用的BufferedSink等等,还有很多重量级对象,大家如果有兴趣,可以自己研究一下。虽然我们没有进一步深入分析这些细节,但是从这个过程中,我们还是了解到了网络通信的一些实现,也希望能给大家带来一些帮助。

     这节课就到这里了,谢谢大家,也希望大家关注我的博客!!

0 0
原创粉丝点击