Okhttp文件上传源码分析

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

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

Okhttp文件上传源码分析_第1张图片

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

Okhttp文件上传源码分析_第2张图片

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

     1、RealCall.getResponse之前的逻辑

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

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

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

                public void writeTo(BufferedSink sink) throws IOException {
                    Source source = null;

                    try {
                        source = Okio.source(file);
                        sink.writeAll(source);
                    } finally {
                        Util.closeQuietly(source);
                    }

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

    public void enqueue(Callback responseCallback) {
        this.enqueue(responseCallback, false);
    }

    void enqueue(Callback responseCallback, boolean forWebSocket) {
        synchronized(this) {
            if(this.executed) {
                throw new IllegalStateException("Already Executed");
            }

            this.executed = true;
        }

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

        protected void execute() {
            boolean signalledCallback = false;

            try {
                Response e = RealCall.this.getResponseWithInterceptorChain(this.forWebSocket);
                if(RealCall.this.canceled) {
                    signalledCallback = true;
                    this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
                } else {
                    signalledCallback = true;
                    this.responseCallback.onResponse(RealCall.this, e);
                }
            } catch (IOException var6) {
                if(signalledCallback) {
                    Internal.logger.log(Level.INFO, "Callback failure for " + RealCall.this.toLoggableString(), var6);
                } else {
                    this.responseCallback.onFailure(RealCall.this, var6);
                }
            } finally {
                RealCall.this.client.dispatcher().finished(this);
            }

        }

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

    Response getResponse(Request request, boolean forWebSocket) throws IOException {
        RequestBody body = request.body();
        if(body != null) {
            Builder followUpCount = request.newBuilder();
            MediaType releaseConnection = body.contentType();
            if(releaseConnection != null) {
                followUpCount.header("Content-Type", releaseConnection.toString());
            }

            long response = body.contentLength();
            if(response != -1L) {
                followUpCount.header("Content-Length", Long.toString(response));
                followUpCount.removeHeader("Transfer-Encoding");
            } else {
                followUpCount.header("Transfer-Encoding", "chunked");
                followUpCount.removeHeader("Content-Length");
            }

            request = followUpCount.build();
        }

        this.engine = new HttpEngine(this.client, request, false, false, forWebSocket, (StreamAllocation)null, (RetryableSink)null, (Response)null);
        int var20 = 0;

        while(!this.canceled) {
            boolean var21 = true;
            boolean var15 = false;

            StreamAllocation streamAllocation;
            label173: {
                label172: {
                    try {
                        HttpEngine followUp;
                        try {
                            var15 = true;
                            this.engine.sendRequest();
                            this.engine.readResponse();
                            var21 = false;
                            var15 = false;
                            break label173;
                        } catch (RequestException var16) {
                            throw var16.getCause();
                        } catch (RouteException var17) {
                            followUp = this.engine.recover(var17.getLastConnectException(), (Sink)null);
                            if(followUp == null) {
                                throw var17.getLastConnectException();
                            }
                        } catch (IOException var18) {
                            followUp = this.engine.recover(var18, (Sink)null);
                            if(followUp != null) {
                                var21 = false;
                                this.engine = followUp;
                                var15 = false;
                                break label172;
                            }

                            throw var18;
                        }

                        var21 = false;
                        this.engine = followUp;
                        var15 = false;
                    } finally {
                        if(var15) {
                            if(var21) {
                                StreamAllocation streamAllocation1 = this.engine.close();
                                streamAllocation1.release();
                            }

                        }
                    }

                    if(var21) {
                        streamAllocation = this.engine.close();
                        streamAllocation.release();
                    }
                    continue;
                }

                if(var21) {
                    streamAllocation = this.engine.close();
                    streamAllocation.release();
                }
                continue;
            }

            if(var21) {
                StreamAllocation var22 = this.engine.close();
                var22.release();
            }

            Response var23 = this.engine.getResponse();
            Request var24 = this.engine.followUpRequest();
            if(var24 == null) {
                if(!forWebSocket) {
                    this.engine.releaseStreamAllocation();
                }

                return var23;
            }

            streamAllocation = this.engine.close();
            ++var20;
            if(var20 > 20) {
                streamAllocation.release();
                throw new ProtocolException("Too many follow-up requests: " + var20);
            }

            if(!this.engine.sameConnection(var24.url())) {
                streamAllocation.release();
                streamAllocation = null;
            }

            this.engine = new HttpEngine(this.client, var24, false, false, forWebSocket, streamAllocation, (RetryableSink)null, var23);
        }

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

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

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

    public void sendRequest() throws RequestException, RouteException, IOException {
        if(this.cacheStrategy == null) {
            if(this.httpStream != null) {
                throw new IllegalStateException();
            } else {
                Request request = this.networkRequest(this.userRequest);
                InternalCache responseCache = Internal.instance.internalCache(this.client);
                Response cacheCandidate = responseCache != null?responseCache.get(request):null;
                long now = System.currentTimeMillis();
                this.cacheStrategy = (new Factory(now, request, cacheCandidate)).get();
                this.networkRequest = this.cacheStrategy.networkRequest;
                this.cacheResponse = this.cacheStrategy.cacheResponse;
                if(responseCache != null) {
                    responseCache.trackResponse(this.cacheStrategy);
                }

                if(cacheCandidate != null && this.cacheResponse == null) {
                    Util.closeQuietly(cacheCandidate.body());
                }

                if(this.networkRequest == null && this.cacheResponse == null) {
                    this.userResponse = (new Builder()).request(this.userRequest).priorResponse(stripBody(this.priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();
                } else if(this.networkRequest == null) {
                    this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).build();
                    this.userResponse = this.unzip(this.userResponse);
                } else {
                    boolean success = false;

                    try {
                        this.httpStream = this.connect();
                        this.httpStream.setHttpEngine(this);
                        if(this.writeRequestHeadersEagerly()) {
                            long contentLength = OkHeaders.contentLength(request);
                            if(this.bufferRequestBody) {
                                if(contentLength > 2147483647L) {
                                    throw new IllegalStateException("Use setFixedLengthStreamingMode() or setChunkedStreamingMode() for requests larger than 2 GiB.");
                                }

                                if(contentLength != -1L) {
                                    this.httpStream.writeRequestHeaders(this.networkRequest);
                                    this.requestBodyOut = new RetryableSink((int)contentLength);
                                } else {
                                    this.requestBodyOut = new RetryableSink();
                                }
                            } else {
                                this.httpStream.writeRequestHeaders(this.networkRequest);
                                this.requestBodyOut = this.httpStream.createRequestBody(this.networkRequest, contentLength);
                            }
                        }

                        success = true;
                    } finally {
                        if(!success && cacheCandidate != null) {
                            Util.closeQuietly(cacheCandidate.body());
                        }

                    }

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

    public void writeRequestHeaders(Request request) throws IOException {
        if(this.stream == null) {
            this.httpEngine.writingRequestHeaders();
            boolean permitsRequestBody = this.httpEngine.permitsRequestBody(request);
            List requestHeaders = this.framedConnection.getProtocol() == Protocol.HTTP_2?http2HeadersList(request):spdy3HeadersList(request);
            boolean hasResponseBody = true;
            this.stream = this.framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
            this.stream.readTimeout().timeout((long)this.httpEngine.client.readTimeoutMillis(), TimeUnit.MILLISECONDS);
            this.stream.writeTimeout().timeout((long)this.httpEngine.client.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
        }
    }
     这个方法当中就是将当前的请求头进行转换,得到一个List requestHeaders数据,然后将它作为参数,调用FramedConnection类的newStream方法,我们接着来看newStream方法的实现:

    public FramedStream newStream(List requestHeaders, boolean out, boolean in) throws IOException {
        return this.newStream(0, requestHeaders, out, in);
    }

    private FramedStream newStream(int associatedStreamId, List requestHeaders, boolean out, boolean in) throws IOException {
        boolean outFinished = !out;
        boolean inFinished = !in;
        FrameWriter var9 = this.frameWriter;
        FramedStream stream;
        synchronized(this.frameWriter) {
            int streamId;
            synchronized(this) {
                if(this.shutdown) {
                    throw new IOException("shutdown");
                }

                streamId = this.nextStreamId;
                this.nextStreamId += 2;
                stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders);
                if(stream.isOpen()) {
                    this.streams.put(Integer.valueOf(streamId), stream);
                    this.setIdle(false);
                }
            }

            if(associatedStreamId == 0) {
                this.frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, requestHeaders);
            } else {
                if(this.client) {
                    throw new IllegalArgumentException("client streams shouldn\'t have associated stream IDs");
                }

                this.frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders);
            }
        }

        if(!out) {
            this.frameWriter.flush();
        }

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

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

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

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

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

    public void readResponse() throws IOException {
        if(this.userResponse == null) {
            if(this.networkRequest == null && this.cacheResponse == null) {
                throw new IllegalStateException("call sendRequest() first!");
            } else if(this.networkRequest != null) {
                Response networkResponse;
                if(this.forWebSocket) {
                    this.httpStream.writeRequestHeaders(this.networkRequest);
                    networkResponse = this.readNetworkResponse();
                } else if(!this.callerWritesRequestBody) {
                    networkResponse = (new HttpEngine.NetworkInterceptorChain(0, this.networkRequest)).proceed(this.networkRequest);
                } else {
                    if(this.bufferedRequestBody != null && this.bufferedRequestBody.buffer().size() > 0L) {
                        this.bufferedRequestBody.emit();
                    }

                    if(this.sentRequestMillis == -1L) {
                        if(OkHeaders.contentLength(this.networkRequest) == -1L && this.requestBodyOut instanceof RetryableSink) {
                            long responseCache = ((RetryableSink)this.requestBodyOut).contentLength();
                            this.networkRequest = this.networkRequest.newBuilder().header("Content-Length", Long.toString(responseCache)).build();
                        }

                        this.httpStream.writeRequestHeaders(this.networkRequest);
                    }

                    if(this.requestBodyOut != null) {
                        if(this.bufferedRequestBody != null) {
                            this.bufferedRequestBody.close();
                        } else {
                            this.requestBodyOut.close();
                        }

                        if(this.requestBodyOut instanceof RetryableSink) {
                            this.httpStream.writeRequestBody((RetryableSink)this.requestBodyOut);
                        }
                    }

                    networkResponse = this.readNetworkResponse();
                }

                this.receiveHeaders(networkResponse.headers());
                if(this.cacheResponse != null) {
                    if(validate(this.cacheResponse, networkResponse)) {
                        this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).headers(combine(this.cacheResponse.headers(), networkResponse.headers())).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();
                        networkResponse.body().close();
                        this.releaseStreamAllocation();
                        InternalCache responseCache1 = Internal.instance.internalCache(this.client);
                        responseCache1.trackConditionalCacheHit();
                        responseCache1.update(this.cacheResponse, stripBody(this.userResponse));
                        this.userResponse = this.unzip(this.userResponse);
                        return;
                    }

                    Util.closeQuietly(this.cacheResponse.body());
                }

                this.userResponse = networkResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();
                if(hasBody(this.userResponse)) {
                    this.maybeCache();
                    this.userResponse = this.unzip(this.cacheWritingResponse(this.storeRequest, this.userResponse));
                }

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

        public Response proceed(Request request) throws IOException {
            ++this.calls;
            if(this.index > 0) {
                Interceptor response = (Interceptor)HttpEngine.this.client.networkInterceptors().get(this.index - 1);
                Address code = this.connection().route().address();
                if(!request.url().host().equals(code.url().host()) || request.url().port() != code.url().port()) {
                    throw new IllegalStateException("network interceptor " + response + " must retain the same host and port");
                }

                if(this.calls > 1) {
                    throw new IllegalStateException("network interceptor " + response + " must call proceed() exactly once");
                }
            }

            if(this.index < HttpEngine.this.client.networkInterceptors().size()) {
                HttpEngine.NetworkInterceptorChain var7 = HttpEngine.this.new NetworkInterceptorChain(this.index + 1, request);
                Interceptor var10 = (Interceptor)HttpEngine.this.client.networkInterceptors().get(this.index);
                Response interceptedResponse = var10.intercept(var7);
                if(var7.calls != 1) {
                    throw new IllegalStateException("network interceptor " + var10 + " must call proceed() exactly once");
                } else if(interceptedResponse == null) {
                    throw new NullPointerException("network interceptor " + var10 + " returned null");
                } else {
                    return interceptedResponse;
                }
            } else {
                HttpEngine.this.httpStream.writeRequestHeaders(request);
                HttpEngine.this.networkRequest = request;
                if(HttpEngine.this.permitsRequestBody(request) && request.body() != null) {
                    Sink var5 = HttpEngine.this.httpStream.createRequestBody(request, request.body().contentLength());
                    BufferedSink var8 = Okio.buffer(var5);
                    request.body().writeTo(var8);
                    var8.close();
                }

                Response var6 = HttpEngine.this.readNetworkResponse();
                int var9 = var6.code();
                if((var9 == 204 || var9 == 205) && var6.body().contentLength() > 0L) {
                    throw new ProtocolException("HTTP " + var9 + " had non-zero Content-Length: " + var6.body().contentLength());
                } else {
                    return var6;
                }
            }
        }

     我们在上一步构造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等等,还有很多重量级对象,大家如果有兴趣,可以自己研究一下。虽然我们没有进一步深入分析这些细节,但是从这个过程中,我们还是了解到了网络通信的一些实现,也希望能给大家带来一些帮助。

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

你可能感兴趣的:(Android,View视图,Android框架总结,Android源码解析,数据结构和算法)