美文网首页
BasicNetwork详解

BasicNetwork详解

作者: Jaesoon | 来源:发表于2018-07-16 23:18 被阅读19次

    BasicNetwork详解

    BasicNetwork详解

    BasicNetwork负责完成网络请求,受NetworkDispatcher类调用。在NetworkDispatcher的run()方法中,是这样调用的。

    void run(){
        processRequest();
    }
    
    ......
    
    void processRequest(Request<?> request) {
        ......
        // Perform the network request.
        NetworkResponse networkResponse = mNetwork.performRequest(request);
        request.addMarker("network-http-complete");
        ......
    }
    
    ......
    

    Network

    它是接口Network的一个实例。老规矩,我们看看这个类的注释:

    /** A network performing Volley requests over an {@link HttpStack}. */
    

    它实现了Network接口。我们阅读一下这个类的源码。

    /** An interface for performing requests. */
    public interface Network {
        /**
         * Performs the specified request.
         *
         * @param request Request to process
         * @return A {@link NetworkResponse} with data and caching metadata; will never be null
         * @throws VolleyError on errors
         */
        NetworkResponse performRequest(Request<?> request) throws VolleyError;
    }
    

    注释说明,这个是一个实现请求的接口。只有一个方法-performRequest()。一个入参,泛型Reqest。这个方法的作用是实现指定的请求。返回的是请求的结果为-NetworkResponse。

    Request

    Request是一个抽象类,所有的请求都是继承于这个,作用就是进行网络请求参数的封装和请求结果的解析。在这里暂时不详细介绍。它有三个重要的方法:

        /**
         * Subclasses must implement this to parse the raw network response and return an appropriate
         * response type. This method will be called from a worker thread. The response will not be
         * delivered if you return null.
         *
         * @param response Response from the network
         * @return The parsed response, or null in the case of an error
         */
        @Override
        protected void deliverResponse(String response);
    
        /**
         * Subclasses must implement this to perform delivery of the parsed response to their listeners.
         * The given response is guaranteed to be non-null; responses that fail to parse are not
         * delivered.
         *
         * @param response The parsed response returned by {@link
         *     #parseNetworkResponse(NetworkResponse)}
         */
        @Override
        @SuppressWarnings("DefaultCharset")
        protected Response<String> parseNetworkResponse(NetworkResponse response)
    
        /**
         * Mark this request as canceled.
         *
         * <p>No callback will be delivered as long as either:
         *
         * <ul>
         *   <li>This method is called on the same thread as the {@link ResponseDelivery} is running on.
         *       By default, this is the main thread.
         *   <li>The request subclass being used overrides cancel() and ensures that it does not invoke
         *       the listener in {@link #deliverResponse} after cancel() has been called in a
         *       thread-safe manner.
         * </ul>
         *
         * <p>There are no guarantees if both of these conditions aren't met.
         */
        @Override
        public void cancel() 
    

    作用如上的注释。

    构造函数

        /**
         * @deprecated Should never have been exposed in the API. This field may be removed in a future
         *     release of Volley.
         */
        @Deprecated protected final HttpStack mHttpStack;
    
        private final BaseHttpStack mBaseHttpStack;
    
        protected final ByteArrayPool mPool;
    
        /**
         * @param httpStack HTTP stack to be used
         * @deprecated use {@link #BasicNetwork(BaseHttpStack)} instead to avoid depending on Apache
         *     HTTP. This method may be removed in a future release of Volley.
         */
        @Deprecated
        public BasicNetwork(HttpStack httpStack) {
            // If a pool isn't passed in, then build a small default pool that will give us a lot of
            // benefit and not use too much memory.
            this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
        }
    
        /**
         * @param httpStack HTTP stack to be used
         * @param pool a buffer pool that improves GC performance in copy operations
         * @deprecated use {@link #BasicNetwork(BaseHttpStack, ByteArrayPool)} instead to avoid
         *     depending on Apache HTTP. This method may be removed in a future release of Volley.
         */
        @Deprecated
        public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
            mHttpStack = httpStack;
            mBaseHttpStack = new AdaptedHttpStack(httpStack);
            mPool = pool;
        }
    
        /** @param httpStack HTTP stack to be used */
        public BasicNetwork(BaseHttpStack httpStack) {
            // If a pool isn't passed in, then build a small default pool that will give us a lot of
            // benefit and not use too much memory.
            this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
        }
    
        /**
         * @param httpStack HTTP stack to be used
         * @param pool a buffer pool that improves GC performance in copy operations
         */
        public BasicNetwork(BaseHttpStack httpStack, ByteArrayPool pool) {
            mBaseHttpStack = httpStack;
            // Populate mHttpStack for backwards compatibility, since it is a protected field. However,
            // we won't use it directly here, so clients which don't access it directly won't need to
            // depend on Apache HTTP.
            mHttpStack = httpStack;
            mPool = pool;
        }
    

    额,貌似,遇到了问题,我们要看看HttpStack和ByteArrayPool是个啥东西。

    HttpStack

    代码不长,我们直接贴出来。

    /**
     * An HTTP stack abstraction.
     *
     * @deprecated This interface should be avoided as it depends on the deprecated Apache HTTP library.
     *     Use {@link BaseHttpStack} to avoid this dependency. This class may be removed in a future
     *     release of Volley.
     */
    @Deprecated
    public interface HttpStack {
        /**
         * Performs an HTTP request with the given parameters.
         *
         * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
         * and the Content-Type header is set to request.getPostBodyContentType().
         *
         * @param request the request to perform
         * @param additionalHeaders additional headers to be sent together with {@link
         *     Request#getHeaders()}
         * @return the HTTP response
         */
        HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
                throws IOException, AuthFailureError;
    }
    

    它是一个接口,类说明是:一个抽象的Http栈。又说了:应该避免使用此接口,因为它取决于已弃用的Apache HTTP库。 使用{@link BaseHttpStack}来避免这种依赖。 此类可能会在未来的Volley版本中删除。这是,因为,谷歌android5.0之后更换了将HttpClient更换成了HttpURLConnection。关于这部分,我们先暂时分析到这里,稍后我们在后面详细讲讲网络请求的实现。

    ByteArrayPool

    类说明:

    /**
     * ByteArrayPool is a source and repository of <code>byte[]</code> objects. Its purpose is to supply
     * those buffers to consumers who need to use them for a short period of time and then dispose of
     * them. Simply creating and disposing such buffers in the conventional manner can considerable heap
     * churn and garbage collection delays on Android, which lacks good management of short-lived heap
     * objects. It may be advantageous to trade off some memory in the form of a permanently allocated
     * pool of buffers in order to gain heap performance improvements; that is what this class does.
     *
     * <p>A good candidate user for this class is something like an I/O system that uses large temporary
     * <code>byte[]</code> buffers to copy data around. In these use cases, often the consumer wants the
     * buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks off
     * of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into account
     * and also to maximize the odds of being able to reuse a recycled buffer, this class is free to
     * return buffers larger than the requested size. The caller needs to be able to gracefully deal
     * with getting buffers any size over the minimum.
     *
     * <p>If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this
     * class will allocate a new buffer and return it.
     *
     * <p>This class has no special ownership of buffers it creates; the caller is free to take a buffer
     * it receives from this pool, use it permanently, and never return it to the pool; additionally, it
     * is not harmful to return to this pool a buffer that was allocated elsewhere, provided there are
     * no other lingering references to it.
     *
     * <p>This class ensures that the total size of the buffers in its recycling pool never exceeds a
     * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit,
     * least-recently-used buffers are disposed.
     */
    

    真正的代码不多,主要注释很详细。 大概的意思是它是一个Byte[]池,作用是能够更快速的对byte[]进行操作,节省I/O,减小内存分配和垃圾回收的系统消耗。提供了两个方法:

    • public synchronized byte[] getBuf(int len)
      如果一个缓冲区在请求的大小中可用,则返回该缓冲区中的缓冲区;如果池中的缓冲区不可用,则返回一个新的缓冲区。
    • public synchronized void returnBuf(byte[] buf)
      返回池的缓冲区,如果池超过其分配的大小,则丢弃旧的缓冲区。

    回到构造函数。有两个被废弃的,我们不分析了。另外的两个的区别是,一个不带ByteArrayPool。我们直接分析复杂的那个。

        /** @param httpStack HTTP stack to be used */
        public BasicNetwork(BaseHttpStack httpStack) {
            // If a pool isn't passed in, then build a small default pool that will give us a lot of
            // benefit and not use too much memory.
            this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
        }
    
        /**
         * @param httpStack HTTP stack to be used
         * @param pool a buffer pool that improves GC performance in copy operations
         */
        public BasicNetwork(BaseHttpStack httpStack, ByteArrayPool pool) {
            mBaseHttpStack = httpStack;
            // Populate mHttpStack for backwards compatibility, since it is a protected field. However,
            // we won't use it directly here, so clients which don't access it directly won't need to
            // depend on Apache HTTP.
            //填充mHttpStack以实现向后兼容性,因为它是受保护的字段。 但是,我们不会在这里直接使用它,因此不直接访问它的客户端不需要依赖Apache HTTP。
            mHttpStack = httpStack;
            mPool = pool;
        }
    

    可以知道,现在我们的网络请求都是使用mBaseHttpStack。
    然后我们来看看重写的一个方法-performRequest,从名字可以看出来,这个方法用来实现网络请求。代码有点多,额,我也没办法。读吧。

        @Override
        public NetworkResponse performRequest(Request<?> request) throws VolleyError {
            long requestStart = SystemClock.elapsedRealtime();
            while (true) {
                HttpResponse httpResponse = null;
                byte[] responseContents = null;
                List<Header> responseHeaders = Collections.emptyList();
                try {
                    // Gather headers.
                    Map<String, String> additionalRequestHeaders =
                            getCacheHeaders(request.getCacheEntry());
                    httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                    int statusCode = httpResponse.getStatusCode();
    
                    responseHeaders = httpResponse.getHeaders();
                    // Handle cache validation.
                    if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                        Entry entry = request.getCacheEntry();
                        if (entry == null) {
                            return new NetworkResponse(
                                    HttpURLConnection.HTTP_NOT_MODIFIED,
                                    /* data= */ null,
                                    /* notModified= */ true,
                                    SystemClock.elapsedRealtime() - requestStart,
                                    responseHeaders);
                        }
                        // Combine cached and response headers so the response will be complete.
                        List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                        return new NetworkResponse(
                                HttpURLConnection.HTTP_NOT_MODIFIED,
                                entry.data,
                                /* notModified= */ true,
                                SystemClock.elapsedRealtime() - requestStart,
                                combinedHeaders);
                    }
    
                    // Some responses such as 204s do not have content.  We must check.
                    InputStream inputStream = httpResponse.getContent();
                    if (inputStream != null) {
                        responseContents =
                                inputStreamToBytes(inputStream, httpResponse.getContentLength());
                    } else {
                        // Add 0 byte response as a way of honestly representing a
                        // no-content request.
                        responseContents = new byte[0];
                    }
    
                    // if the request is slow, log it.
                    long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                    logSlowRequests(requestLifetime, request, responseContents, statusCode);
    
                    if (statusCode < 200 || statusCode > 299) {
                        throw new IOException();
                    }
                    return new NetworkResponse(
                            statusCode,
                            responseContents,
                            /* notModified= */ false,
                            SystemClock.elapsedRealtime() - requestStart,
                            responseHeaders);
                } catch (SocketTimeoutException e) {
                    attemptRetryOnException("socket", request, new TimeoutError());
                } catch (MalformedURLException e) {
                    throw new RuntimeException("Bad URL " + request.getUrl(), e);
                } catch (IOException e) {
                    int statusCode;
                    if (httpResponse != null) {
                        statusCode = httpResponse.getStatusCode();
                    } else {
                        throw new NoConnectionError(e);
                    }
                    VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                    NetworkResponse networkResponse;
                    if (responseContents != null) {
                        networkResponse =
                                new NetworkResponse(
                                        statusCode,
                                        responseContents,
                                        /* notModified= */ false,
                                        SystemClock.elapsedRealtime() - requestStart,
                                        responseHeaders);
                        if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
                                || statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                            attemptRetryOnException(
                                    "auth", request, new AuthFailureError(networkResponse));
                        } else if (statusCode >= 400 && statusCode <= 499) {
                            // Don't retry other client errors.
                            throw new ClientError(networkResponse);
                        } else if (statusCode >= 500 && statusCode <= 599) {
                            if (request.shouldRetryServerErrors()) {
                                attemptRetryOnException(
                                        "server", request, new ServerError(networkResponse));
                            } else {
                                throw new ServerError(networkResponse);
                            }
                        } else {
                            // 3xx? No reason to retry.
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        attemptRetryOnException("network", request, new NetworkError());
                    }
                }
            }
        }
    
        /** Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */
        private void logSlowRequests(
                long requestLifetime, Request<?> request, byte[] responseContents, int statusCode) {
            if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
                VolleyLog.d(
                        "HTTP response for request=<%s> [lifetime=%d], [size=%s], "
                                + "[rc=%d], [retryCount=%s]",
                        request,
                        requestLifetime,
                        responseContents != null ? responseContents.length : "null",
                        statusCode,
                        request.getRetryPolicy().getCurrentRetryCount());
            }
        }
    
        /**
         * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
         * request's retry policy, a timeout exception is thrown.
         *
         * @param request The request to use.
         */
        private static void attemptRetryOnException(
                String logPrefix, Request<?> request, VolleyError exception) throws VolleyError {
            RetryPolicy retryPolicy = request.getRetryPolicy();
            int oldTimeout = request.getTimeoutMs();
    
            try {
                retryPolicy.retry(exception);
            } catch (VolleyError e) {
                request.addMarker(
                        String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
                throw e;
            }
            request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
        }
    
        private Map<String, String> getCacheHeaders(Cache.Entry entry) {
            // If there's no cache entry, we're done.
            if (entry == null) {
                return Collections.emptyMap();
            }
    
            Map<String, String> headers = new HashMap<>();
    
            if (entry.etag != null) {
                headers.put("If-None-Match", entry.etag);
            }
    
            if (entry.lastModified > 0) {
                headers.put(
                        "If-Modified-Since", HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified));
            }
    
            return headers;
        }
    
        protected void logError(String what, String url, long start) {
            long now = SystemClock.elapsedRealtime();
            VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
        }
    
        /** Reads the contents of an InputStream into a byte[]. */
        private byte[] inputStreamToBytes(InputStream in, int contentLength)
                throws IOException, ServerError {
            PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, contentLength);
            byte[] buffer = null;
            try {
                if (in == null) {
                    throw new ServerError();
                }
                buffer = mPool.getBuf(1024);
                int count;
                while ((count = in.read(buffer)) != -1) {
                    bytes.write(buffer, 0, count);
                }
                return bytes.toByteArray();
            } finally {
                try {
                    // Close the InputStream and release the resources by "consuming the content".
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException e) {
                    // This can happen if there was an exception above that left the stream in
                    // an invalid state.
                    VolleyLog.v("Error occurred when closing InputStream");
                }
                mPool.returnBuf(buffer);
                bytes.close();
            }
        }
    
        /**
         * Converts Headers[] to Map&lt;String, String&gt;.
         *
         * @deprecated Should never have been exposed in the API. This method may be removed in a future
         *     release of Volley.
         */
        @Deprecated
        protected static Map<String, String> convertHeaders(Header[] headers) {
            Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            for (int i = 0; i < headers.length; i++) {
                result.put(headers[i].getName(), headers[i].getValue());
            }
            return result;
        }
    
        /**
         * Combine cache headers with network response headers for an HTTP 304 response.
         *
         * <p>An HTTP 304 response does not have all header fields. We have to use the header fields
         * from the cache entry plus the new ones from the response. See also:
         * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
         *
         * @param responseHeaders Headers from the network response.
         * @param entry The cached response.
         * @return The combined list of headers.
         */
        private static List<Header> combineHeaders(List<Header> responseHeaders, Entry entry) {
            // First, create a case-insensitive set of header names from the network
            // response.
            Set<String> headerNamesFromNetworkResponse = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
            if (!responseHeaders.isEmpty()) {
                for (Header header : responseHeaders) {
                    headerNamesFromNetworkResponse.add(header.getName());
                }
            }
    
            // Second, add headers from the cache entry to the network response as long as
            // they didn't appear in the network response, which should take precedence.
            List<Header> combinedHeaders = new ArrayList<>(responseHeaders);
            if (entry.allResponseHeaders != null) {
                if (!entry.allResponseHeaders.isEmpty()) {
                    for (Header header : entry.allResponseHeaders) {
                        if (!headerNamesFromNetworkResponse.contains(header.getName())) {
                            combinedHeaders.add(header);
                        }
                    }
                }
            } else {
                // Legacy caches only have entry.responseHeaders.
                if (!entry.responseHeaders.isEmpty()) {
                    for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) {
                        if (!headerNamesFromNetworkResponse.contains(header.getKey())) {
                            combinedHeaders.add(new Header(header.getKey(), header.getValue()));
                        }
                    }
                }
            }
            return combinedHeaders;
        }
    

    首先,调用mBaseHttpStack的excuteRequest()方法,得到请求的结果。然后根据响应码,判断是不是304(HttpURLConnection.HTTP_NOT_MODIFIED),如果是,表明使用上一次请求的结果即可。取request中的cacheEntry的值,创建NetworkResponse,并返回。如果不是,读取接下来的数据流。用 inputStreamToBytes() 方法将接下来的数据流转换成byte[](注释:就是在这里,我们使用到了byteArrayPool)。
    继续判断状态码,如果是小于200或大于299,则表明出现异常了,直接抛出一个IOException异常。在异常catch代码中,我们创建一个异常的NetworkResponse。如果不是,继续往下走。我们根据状态码和内容,创建一个新的NetworkResponse,并返回。好了,BaseNetwork源码就分析完毕。

    相关文章

      网友评论

          本文标题:BasicNetwork详解

          本文链接:https://www.haomeiwen.com/subject/admipftx.html