zoukankan      html  css  js  c++  java
  • Volley——网络请求(四)Request

      前面分析了Volley初始化的基本流程,下面我们来看一看Volley发送请求的过程。  

            StringRequest translateRequset = new StringRequest(Dict_Url + word.toLowerCase(), mResponListener, mErrorListener);
            mQueue.add(translateRequset);

      这是最简单的发请求过程。

      我们看一下StringRequest的实现。  

    public class StringRequest extends Request<String> {
        private final Listener<String> mListener;
    
        /**
         * Creates a new request with the given method.
         *
         * @param method the request {@link Method} to use
         * @param url URL to fetch the string at
         * @param listener Listener to receive the String response
         * @param errorListener Error listener, or null to ignore errors
         */
        public StringRequest(int method, String url, Listener<String> listener,
                ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
        }
    
        /**
         * Creates a new GET request.
         *
         * @param url URL to fetch the string at
         * @param listener Listener to receive the String response
         * @param errorListener Error listener, or null to ignore errors
         */
        public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
            this(Method.GET, url, listener, errorListener);
        }
    
        @Override
        protected void deliverResponse(String response) {
            mListener.onResponse(response);
        }
    
        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String parsed;
            try {
                parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            } catch (UnsupportedEncodingException e) {
                parsed = new String(response.data);
            }
            return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
        }
    }

      这个类,主要是一个构造方法,两个实现方法。我们一个一个阅读:

      构造方法:

        public StringRequest(int method, String url, Listener<String> listener,
                ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
        }
    
        /**
         * Creates a new GET request.
         *
         * @param url URL to fetch the string at
         * @param listener Listener to receive the String response
         * @param errorListener Error listener, or null to ignore errors
         */
        public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
            this(Method.GET, url, listener, errorListener);
        }

        可以看到,这个方法,主要保存了最后请求完成的监听,其余的直接使用父类的。因此,我们顺藤摸瓜看一看父类的构造方法。

        /**
         * Creates a new request with the given method (one of the values from {@link Method}),
         * URL, and error listener.  Note that the normal response listener is not provided here as
         * delivery of responses is provided by subclasses, who have a better idea of how to deliver
         * an already-parsed response.
         */
        public Request(int method, String url, Response.ErrorListener listener) {
            mMethod = method;
            mUrl = url;
            mErrorListener = listener;
            setRetryPolicy(new DefaultRetryPolicy());
    
            mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
        }

      我们先来看findDefaultTrafficStatesTag方法,因为它比较简单。

        /**
         * @return The hashcode of the URL's host component, or 0 if there is none.
         */
        private static int findDefaultTrafficStatsTag(String url) {
            if (!TextUtils.isEmpty(url)) {
                Uri uri = Uri.parse(url);
                if (uri != null) {
                    String host = uri.getHost();
                    if (host != null) {
                        return host.hashCode();
                    }
                }
            }
            return 0;
        }

      我们可以看到,这个方法,主要是将url解析为Uri,并取出它的host的hashCode。

      然后,我们看看DefaultRetryPolicy这个类,看看默认的重发策略是什么样的。  

        /** The default socket timeout in milliseconds */
        public static final int DEFAULT_TIMEOUT_MS = 2500;
    
        /** The default number of retries */
        public static final int DEFAULT_MAX_RETRIES = 1;
    
        /** The default backoff multiplier */
        public static final float DEFAULT_BACKOFF_MULT = 1f;
    
        /**
         * Constructs a new retry policy using the default timeouts.
         */
        public DefaultRetryPolicy() {
            this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
        }
    
        /**
         * Constructs a new retry policy.
         * @param initialTimeoutMs The initial timeout for the policy.
         * @param maxNumRetries The maximum number of retries.
         * @param backoffMultiplier Backoff multiplier for the policy.
         */
        public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
            mCurrentTimeoutMs = initialTimeoutMs;
            mMaxNumRetries = maxNumRetries;
            mBackoffMultiplier = backoffMultiplier;
        }
    
        /**
         * Returns the current timeout.
         */
        @Override
        public int getCurrentTimeout() {
            return mCurrentTimeoutMs;
        }
    
        /**
         * Returns the current retry count.
         */
        @Override
        public int getCurrentRetryCount() {
            return mCurrentRetryCount;
        }
    
        /**
         * Returns the backoff multiplier for the policy.
         */
        public float getBackoffMultiplier() {
            return mBackoffMultiplier;
        }

      这一段中不难发现,我们默认的重发策略是2500ms定义为超时,重发次数为1次,DEFAULT_BACKOFF_MULT=1.0f。DEFAULT_BACKOFF_MULT称为超时因子,每次重发,超时都会乘上这个因子:

        /**
         * Prepares for the next retry by applying a backoff to the timeout.
         * @param error The error code of the last attempt.
         */
        @Override
        public void retry(VolleyError error) throws VolleyError {
            mCurrentRetryCount++;
            mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
            if (!hasAttemptRemaining()) {
                throw error;
            }
        }

      至此,我们对于StringRequest的构造方法阅读完成。

      接下来就来看两个核心方法:deliverResponse和parseNetworkResponse。先不看内容,我们先来看这两个方法在哪里调用的,其实在之前的文章中提到过,但是可能很多人,包括我自己也忘记了,所以此处来回顾一下。

      在RequestQueue中,我们有如下代码:

      

        public void start() {
            stop();  // Make sure any currently running dispatchers are stopped.
            // Create the cache dispatcher and start it.
            mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
            mCacheDispatcher.start();
    
            // Create network dispatchers (and corresponding threads) up to the pool size.
            for (int i = 0; i < mDispatchers.length; i++) {
                NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                        mCache, mDelivery);
                mDispatchers[i] = networkDispatcher;
                networkDispatcher.start();
            }
        }

      其中networkDispatcher是Thread的子类,networkDispatcher的run方法中:

    ……               
             // Parse the response here on the worker thread.
                    Response<?> response = request.parseNetworkResponse(networkResponse);
                    request.addMarker("network-parse-complete");
    ……

      而另一个方法,则是在ExecutorDelivery中,我们在networkDispatcher中也可以找到它的踪迹:

                    mDelivery.postResponse(request, response);
                } catch (VolleyError volleyError) {
                    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                    parseAndDeliverNetworkError(request, volleyError);
                } catch (Exception e) {
                    VolleyLog.e(e, "Unhandled exception %s", e.toString());
                    VolleyError volleyError = new VolleyError(e);
                    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                    mDelivery.postError(request, volleyError);
                }

      mDelivery的类型就是ExecutorDelivery。ExecutorDelivery类我们还没有读过,这个放在后面。下面我们来看看deliverResponse和parseNetworkResponse的实现。

      

        @Override
        protected void deliverResponse(String response) {
            mListener.onResponse(response);
        }
    
        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String parsed;
            try {
                parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            } catch (UnsupportedEncodingException e) {
                parsed = new String(response.data);
            }
            return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
        }

      deliverResponse不用多说,parseNetworkResponse中,直接用byte[]类型的response中的data,生成字符串。而字符串的编码方式,则需要从请求的响应中进行解析。

      我们来看一看解析的方法:

        public static String parseCharset(Map<String, String> headers, String defaultCharset) {
            String contentType = headers.get(HTTP.CONTENT_TYPE);
            if (contentType != null) {
                String[] params = contentType.split(";");
                for (int i = 1; i < params.length; i++) {
                    String[] pair = params[i].trim().split("=");
                    if (pair.length == 2) {
                        if (pair[0].equals("charset")) {
                            return pair[1];
                        }
                    }
                }
            }
    
            return defaultCharset;
        }

      在headers的Content-Type字段中,我们可以读出很多信息,它们以分号彼此区分。我们找到其中关于charset的设置,将其返回。

      最后我们来看一看Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));

      

    /**
         * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}.
         *
         * @param response The network response to parse headers from
         * @return a cache entry for the given response, or null if the response is not cacheable.
         */
        public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
            long now = System.currentTimeMillis();
    
            Map<String, String> headers = response.headers;
    
            long serverDate = 0;
            long lastModified = 0;
            long serverExpires = 0;
            long softExpire = 0;
            long finalExpire = 0;
            long maxAge = 0;
            long staleWhileRevalidate = 0;
            boolean hasCacheControl = false;
            boolean mustRevalidate = false;
    
            String serverEtag = null;
            String headerValue;
    
            headerValue = headers.get("Date");
            if (headerValue != null) {
                serverDate = parseDateAsEpoch(headerValue);
            }
    
            headerValue = headers.get("Cache-Control");
            if (headerValue != null) {
                hasCacheControl = true;
                String[] tokens = headerValue.split(",");
                for (int i = 0; i < tokens.length; i++) {
                    String token = tokens[i].trim();
                    if (token.equals("no-cache") || token.equals("no-store")) {
                        return null;
                    } else if (token.startsWith("max-age=")) {
                        try {
                            maxAge = Long.parseLong(token.substring(8));
                        } catch (Exception e) {
                        }
                    } else if (token.startsWith("stale-while-revalidate=")) {
                        try {
                            staleWhileRevalidate = Long.parseLong(token.substring(23));
                        } catch (Exception e) {
                        }
                    } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                        mustRevalidate = true;
                    }
                }
            }
    
            headerValue = headers.get("Expires");
            if (headerValue != null) {
                serverExpires = parseDateAsEpoch(headerValue);
            }
    
            headerValue = headers.get("Last-Modified");
            if (headerValue != null) {
                lastModified = parseDateAsEpoch(headerValue);
            }
    
            serverEtag = headers.get("ETag");
    
            // Cache-Control takes precedence over an Expires header, even if both exist and Expires
            // is more restrictive.
            if (hasCacheControl) {
                softExpire = now + maxAge * 1000;
                finalExpire = mustRevalidate
                        ? softExpire
                        : softExpire + staleWhileRevalidate * 1000;
            } else if (serverDate > 0 && serverExpires >= serverDate) {
                // Default semantic for Expire header in HTTP specification is softExpire.
                softExpire = now + (serverExpires - serverDate);
                finalExpire = softExpire;
            }
    
            Cache.Entry entry = new Cache.Entry();
            entry.data = response.data;
            entry.etag = serverEtag;
            entry.softTtl = softExpire;
            entry.ttl = finalExpire;
            entry.serverDate = serverDate;
            entry.lastModified = lastModified;
            entry.responseHeaders = headers;
    
            return entry;
        }
    
        /**
         * Parse date in RFC1123 format, and return its value as epoch
         */
        public static long parseDateAsEpoch(String dateStr) {
            try {
                // Parse date in RFC1123 format if this header contains one
                return DateUtils.parseDate(dateStr).getTime();
            } catch (DateParseException e) {
                // Date in invalid format, fallback to 0
                return 0;
            }
        }

      这一段是解析http的Response中的缓存机制。

      我们从Response中取出了以下一些属性:Cache-Control;Expires;Last-Modified;Etag

      Cache-Control——缓存控制:    

    no-cache  指示请求或响应消息不能缓存(HTTP/1.0用Pragma的no-cache替换)
    根据什么能被缓存
    no-store  用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
    根据缓存超时
    max-age  指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
    min-fresh  指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
    max-stale  指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以
    接收超出超时期指定值之内的响应消息。

      Expires:

    表示存在时间,允许客户端在这个时间之前不去检查(发请求),等同max-age的
    效果。但是如果同时存在,则被Cache-Control的max-age覆盖。

      Last-Modified:服务器上文件的最后修改时间

      Etag:

    Etag 主要为了解决 Last-Modified 无法解决的一些问题。
    
    1、 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
    
    2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
    
    3、某些服务器不能精确的得到文件的最后修改时间;
    
    为此,HTTP/1.1 引入了 Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的是Etag需要放在""内。

      最后:

        /** Returns a successful response containing the parsed result. */
        public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
            return new Response<T>(result, cacheEntry);
        }
    
        private Response(T result, Cache.Entry cacheEntry) {
            this.result = result;
            this.cacheEntry = cacheEntry;
            this.error = null;
        }

     Done~~

  • 相关阅读:
    urllib.request.urlretrieve()
    python2.X与python3.X爬虫常用的模块变化对应
    .net 发布程序时出现“类型ASP.global_asax同时存在于...”错误的解决办法
    批量引用iconfont字体图标到项目
    动态设置bootstrapswitch状态
    MD5加密过时方法替换
    SQL语句
    PHP中的闭包
    算法复杂度
    快速排序
  • 原文地址:https://www.cnblogs.com/fishbone-lsy/p/5507125.html
Copyright © 2011-2022 走看看