• Volley——网络请求(三)


    上一节,介绍了HurlStack的实现,根据我们外层的代码:

    /**
         * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
         *
         * @param context A {@link Context} to use for creating the cache dir.
         * @param stack An {@link HttpStack} to use for the network, or null for default.
         * @return A started {@link RequestQueue} instance.
         */
        public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
            File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    
            String userAgent = "volley/0";
            try {
                String packageName = context.getPackageName();
                PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
                userAgent = packageName + "/" + info.versionCode;
            } catch (NameNotFoundException e) {
            }
    
            if (stack == null) {
                if (Build.VERSION.SDK_INT >= 9) {
                    stack = new HurlStack();
                } else {
                    // Prior to Gingerbread, HttpUrlConnection was unreliable.
                    // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                    stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
                }
            }
    
            Network network = new BasicNetwork(stack);
    
            RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
            queue.start();
    
            return queue;
        }

       这一节,我将阅读并记录BasicNetwork的实现。

      

        /**
         * @param httpStack HTTP stack to be used
         */
        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
         */
        public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
            mHttpStack = httpStack;
            mPool = pool;
        }

       先看BasicNetwork的构造方法。我们在此方法中,传入了HttpStack,这个上一篇已经分析过了。然后我们新建了一个ByteArrayPool传入。我们可以阅读一下ByteArrayPool这个类。

    public class ByteArrayPool {
        /** The buffer pool, arranged both by last use and by buffer size */
        private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();
        private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);
    
        /** The total size of the buffers in the pool */
        private int mCurrentSize = 0;
    
        /**
         * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay
         * under this limit.
         */
        private final int mSizeLimit;
    
        /** Compares buffers by size */
        protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
            @Override
            public int compare(byte[] lhs, byte[] rhs) {
                return lhs.length - rhs.length;
            }
        };
    
        /**
         * @param sizeLimit the maximum size of the pool, in bytes
         */
        public ByteArrayPool(int sizeLimit) {
            mSizeLimit = sizeLimit;
        }
    
        /**
         * Returns a buffer from the pool if one is available in the requested size, or allocates a new
         * one if a pooled one is not available.
         *
         * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be
         *        larger.
         * @return a byte[] buffer is always returned.
         */
        public synchronized byte[] getBuf(int len) {
            for (int i = 0; i < mBuffersBySize.size(); i++) {
                byte[] buf = mBuffersBySize.get(i);
                if (buf.length >= len) {
                    mCurrentSize -= buf.length;
                    mBuffersBySize.remove(i);
                    mBuffersByLastUse.remove(buf);
                    return buf;
                }
            }
            return new byte[len];
        }
    
        /**
         * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
         * size.
         *
         * @param buf the buffer to return to the pool.
         */
        public synchronized void returnBuf(byte[] buf) {
            if (buf == null || buf.length > mSizeLimit) {
                return;
            }
            mBuffersByLastUse.add(buf);
            int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
            if (pos < 0) {
                pos = -pos - 1;
            }
            mBuffersBySize.add(pos, buf);
            mCurrentSize += buf.length;
            trim();
        }
    
        /**
         * Removes buffers from the pool until it is under its size limit.
         */
        private synchronized void trim() {
            while (mCurrentSize > mSizeLimit) {
                byte[] buf = mBuffersByLastUse.remove(0);
                mBuffersBySize.remove(buf);
                mCurrentSize -= buf.length;
            }
        }
    
    }

      整体上来说,它是一个ByteArray的缓冲类,它提供了存、取和清理三个功能。目的是让ByteArray保持在合适的长度,这个设计理念非常类似LruCache。 接着就是阅读BasicNetwork中的performRequest方法。为什么是这个方法,在Volley的第一篇分析中,我们曾阅读过RequestQueue的源码,中间有这样一段:  

            for (int i = 0; i < mDispatchers.length; i++) {
                NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                        mCache, mDelivery);
                mDispatchers[i] = networkDispatcher;
                networkDispatcher.start();
            }

       然后在NetworkDispatcher这个Thread的子类中的run方法中,又有这样一段:

                    // Perform the network request.
                    NetworkResponse networkResponse = mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");

      当时我们一笔带过了。BasicNetwork的performRequest方法实现代码,比较长,我们只能分段阅读。

        @Override
        public NetworkResponse performRequest(Request<?> request) throws VolleyError {
            long requestStart = SystemClock.elapsedRealtime();
            while (true) {
                HttpResponse httpResponse = null;
                byte[] responseContents = null;
                Map<String, String> responseHeaders = Collections.emptyMap();
                try {
                    // Gather headers.
                    Map<String, String> headers = new HashMap<String, String>();
                    addCacheHeaders(headers, request.getCacheEntry());
                    httpResponse = mHttpStack.performRequest(request, headers);

      我们很容易发现,BasicNetwork的performRequest整体是一个无限循环中。我们来看看循环里面的内容:

      首先创建了HttpResponse、一个存放response内容的byte数组和一个存放response头的Map。

      然后,调用addCacheHeader方法,我们可以看一下这个方法的实现,比较简单。 

        private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
            // If there's no cache entry, we're done.
            if (entry == null) {
                return;
            }
    
            if (entry.etag != null) {
                headers.put("If-None-Match", entry.etag);
            }
    
            if (entry.lastModified > 0) {
                Date refTime = new Date(entry.lastModified);
                headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
            }
        }

      就是将request中缓存的etag和lastModified属性加入到response的header中。

      最后就是执行HurlStack中的performRequest方法,这个在第二节中已经详细记录过,不再赘述。

      接着是下面一段: 

                    httpResponse = mHttpStack.performRequest(request, headers);
                    StatusLine statusLine = httpResponse.getStatusLine();
                    int statusCode = statusLine.getStatusCode();
    
                    responseHeaders = convertHeaders(httpResponse.getAllHeaders());

      这一段的关键方法是converHeaders,我们来看看它的实现。

        /**
         * Converts Headers[] to Map<String, String>.
         */
        protected static Map<String, String> convertHeaders(Header[] headers) {
            Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
            for (int i = 0; i < headers.length; i++) {
                result.put(headers[i].getName(), headers[i].getValue());
            }
            return result;
        }

      这个方法其实比较简单,就是将httpResponse中的headers取出来,放入responseHaeader的map中。经过两上面段代码,我们将缓存中的header和HurlStack返回的httpResponse的header做了合并,最终都存入responseHeaders这个Map中。

      

                    // Handle cache validation.
                    if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
    
                        Entry entry = request.getCacheEntry();
                        if (entry == null) {
                            return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                    responseHeaders, true,
                                    SystemClock.elapsedRealtime() - requestStart);
                        }
    
                        // A 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.
                        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                        entry.responseHeaders.putAll(responseHeaders);
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                                entry.responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

      接下来的一段进行了缓存校验。如果请求返回的状态码是304(即SC_NOT_MODIFIED),表示自上次请求之后,所请求的内容没有任何改变。这种情况我就可以直接在缓存中取数据。 不论缓存是否为空,我们都会新建一个NetworkResponse对象返回。区别只在于,如果有缓存,我们会将刚刚合并好的httpResponse的header加入其中。

                    // Some responses such as 204s do not have content.  We must check.
                    if (httpResponse.getEntity() != null) {
                      responseContents = entityToBytes(httpResponse.getEntity());
                    } else {
                      // Add 0 byte response as a way of honestly representing a
                      // no-content request.
                      responseContents = new byte[0];
                    }

      校验完304的情况后,开始对204进行校验。即是否返回无内容。如果返回无内容则新建一个空byte数组,如果有内容则需要一个转化。我们来看看这个转化方法entityToBytes:

        /** Reads the contents of HttpEntity into a byte[]. */
        private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
            PoolingByteArrayOutputStream bytes =
                    new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
            byte[] buffer = null;
            try {
                InputStream in = entity.getContent();
                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".
                    entity.consumeContent();
                } catch (IOException e) {
                    // This can happen if there was an exception above that left the entity in
                    // an invalid state.
                    VolleyLog.v("Error occured when calling consumingContent");
                }
                mPool.returnBuf(buffer);
                bytes.close();
            }
        }

       一个比较基础的数据读取,运用了之前提到的ByteArrayPool这个类。一次只读取1024位。值得注意的是,bytes.write进行了重写。

        @Override
        public synchronized void write(byte[] buffer, int offset, int len) {
            expand(len);
            super.write(buffer, offset, len);
        }
    
        /**
         * Ensures there is enough space in the buffer for the given number of additional bytes.
         */
        private void expand(int i) {
            /* Can the buffer handle @i more bytes, if not expand it */
            if (count + i <= buf.length) {
                return;
            }
            byte[] newbuf = mPool.getBuf((count + i) * 2);
            System.arraycopy(buf, 0, newbuf, 0, count);
            mPool.returnBuf(buf);
            buf = newbuf;
        }

       如果需要读取的流,长度超过了1024会进行扩展。读取完成后,会将数据再放回mPool,进行缓存。

      接下来,就是performRequest方法的try块儿中的最后一段:

                    logSlowRequests(requestLifetime, request, responseContents, statusLine);
    
                    if (statusCode < 200 || statusCode > 299) {
                        throw new IOException();
                    }
                    return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                            SystemClock.elapsedRealtime() - requestStart);

       这一段分为三步,第一步,检测慢速请求,第二步,检测2xx错误,第三步,返回NetworkResponse。

      我们先来看看检测慢速请求的代码:

      

        /**
         * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.
         */
        private void logSlowRequests(long requestLifetime, Request<?> request,
                byte[] responseContents, StatusLine statusLine) {
            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",
                        statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
            }
        }

      这一段代码,只做了一个简单的判断,当请求用时,大于SLOW_REQUEST_THRESHOLD_MS时,即为慢速请求,报出log。

      至此,performRequest方法的try块儿,就阅读完了。下面阅读catch部分,这里涉及了一些错误处理方法和重发机制。  

                } catch (SocketTimeoutException e) {
                    attemptRetryOnException("socket", request, new TimeoutError());
                } catch (ConnectTimeoutException e) {
                    attemptRetryOnException("connection", request, new TimeoutError());
                } catch (MalformedURLException e) {
                    throw new RuntimeException("Bad URL " + request.getUrl(), e);
                } catch (IOException e) {
                    int statusCode = 0;
                    NetworkResponse networkResponse = null;
                    if (httpResponse != null) {
                        statusCode = httpResponse.getStatusLine().getStatusCode();
                    } else {
                        throw new NoConnectionError(e);
                    }
                    VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                    if (responseContents != null) {
                        networkResponse = new NetworkResponse(statusCode, responseContents,
                                responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                        if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                                statusCode == HttpStatus.SC_FORBIDDEN) {
                            attemptRetryOnException("auth",
                                    request, new AuthFailureError(networkResponse));
                        } else {
                            // TODO: Only throw ServerError for 5xx status codes.
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        throw new NetworkError(networkResponse);
                    }
                }

      我们先来看一看attemptRetryOnException方法,在超时的异常中,它首先会被调用。  

        /**
         * 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));
        }

       代码会取出请求中的重发策略,进行重发。RetryPolicy是个接口,我们需要在自己实现。RetryPolicy:

    /**
     * Retry policy for a request.
     */
    public interface RetryPolicy {
    
        /**
         * Returns the current timeout (used for logging).
         */
        public int getCurrentTimeout();
    
        /**
         * Returns the current retry count (used for logging).
         */
        public int getCurrentRetryCount();
    
        /**
         * Prepares for the next retry by applying a backoff to the timeout.
         * @param error The error code of the last attempt.
         * @throws VolleyError In the event that the retry could not be performed (for example if we
         * ran out of attempts), the passed in error is thrown.
         */
        public void retry(VolleyError error) throws VolleyError;
    }

       RetryPolicy接口包含了三个方法,获取超时,获取重发次数和重发。

      至此,BasicNetwork的基本实现,概读了一遍。接下来,会阅读Volley中的Request。

    Done~

  • 相关阅读:
    5. Longest Palindromic Substring
    4. Median of Two Sorted Arrays(topK-logk)
    apache开源项目--Derby
    apache开源项目--Apache Drill
    apache开源项目-- OODT
    apache开源项目--Mavibot
    apache开源项目--ApacheDS
    apache开源项目--TIKA
    apache开源项目--Mahout
    apache开源项目--Jackrabbit
  • 原文地址:https://www.cnblogs.com/fishbone-lsy/p/5480038.html
Copyright © 2020-2023  润新知