美文网首页Android知识
VOLLEY源码完全解析之Request

VOLLEY源码完全解析之Request

作者: 抽象语法树 | 来源:发表于2018-01-28 12:31 被阅读0次

    从StringRequest,JsonObjectRequest,JsonArrayRequest说起

    • 以GET请求为例,我们使用VOLLEY进行网络请求最基本的用法如下:
            mStringRequest = new StringRequest(Request.Method.GET, url, vl.getListener(), vl.getErrorListener());
            mStringRequest.setTag(tag);
            MyApplication.getRequestQueue().add(mStringRequest);
    

    即创建一个StringRequest,指定HTTP协议的类型以及响应成功和失败的监听事件。
    而点进去查看StringRequest的源码可以发现,StringRequest继承自抽象类Request,实现了抽象方法deliverResponse和parseNetworkResponse即分发监听事件的方法和解析数据的方法。

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

    我们知道,HTTP响应的信息由状态行、消息报头、响应正文三部分组成,而parseNetworkResponse方法中将响应正文按一定的编码格式(根据response.headers的内容,若response.headers失败,则使用android默认的DEFAULT_CHARSET,各API版本不同,大部分为UTF-8)进行解析后返回。同理可知,JsonObjectRequest,JsonArrayRequest与StringRequest的区别恐怕是出在parseNetworkResponse方法上:

        protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
            try {
                String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, "utf-8"));
                return Response.success(new JSONObject(jsonString), HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException var3) {
                return Response.error(new ParseError(var3));
            } catch (JSONException var4) {
                return Response.error(new ParseError(var4));
            }
    
      protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
            try {
                String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, "utf-8"));
                return Response.success(new JSONArray(jsonString), HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException var3) {
                return Response.error(new ParseError(var3));
            } catch (JSONException var4) {
                return Response.error(new ParseError(var4));
            }
        }
    

    可以看到,JsonObjectRequest和JsonArrayRequest只不过是将响应内容中的数据解析成JsonObject或者JsonArray后返回,不过需要注意的是这个类都继承在JsonRequest之下,而JsonRequest则对Request类进行了进一步的封装,这里先不讨论。
    通过上面对三个请求类的分析,我们可以发现,请求子类中,并没有对HTTP响应失败的处理,自然而然,错误信息的处理肯定放在了Request中,带着这个问题,继续观察Request类。

    Request类解析

    • 首先是它的成员变量:
        private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
        private final MarkerLog mEventLog;
        private final int mMethod;
        private final String mUrl;
        private String mRedirectUrl;
        private final int mDefaultTrafficStatsTag;
        private final ErrorListener mErrorListener;
        private Integer mSequence;
        private RequestQueue mRequestQueue;
        private boolean mShouldCache;
        private boolean mCanceled;
        private boolean mResponseDelivered;
        private long mRequestBirthTime;
        private static final long SLOW_REQUEST_THRESHOLD_MS = 3000L;
        private RetryPolicy mRetryPolicy;
        private Entry mCacheEntry;
        private Object mTag;
    

    DEFAULT_PARAMS_ENCODING :对于传递的参数的默认编码方式。
    MarkerLog:DEBUG工具,同于追踪该Request的生命周期。
    mMethod:HTTP的请求方式,支持以下几种:

            int DEPRECATED_GET_OR_POST = -1;
            int GET = 0;
            int POST = 1;
            int PUT = 2;
            int DELETE = 3;
            int HEAD = 4;
            int OPTIONS = 5;
            int TRACE = 6;
            int PATCH = 7;
    

    mUrl:网络请求的URL。
    mRedirectUrl:状态码为3xx使用的重定向URL。
    mDefaultTrafficStatsTag:Default tag。主机的hashCode。
    mErrorListener:请求出现错误的监听事件。
    mSequence:请求的序列号,在重试策略或者请求队列的调度中可能用到?
    mShouldCache:是否需要缓存。
    mCanceled:是否被取消。
    mResponseDelivered:是否分发响应。
    mRequestBirthTime:请求产生的时间,用于跟踪请求,从而抛弃慢的请求 。
    SLOW_REQUEST_THRESHOLD_MS :慢请求的阈值。
    mRetryPolicy:重试策略。
    mCacheEntry:缓存记录。
    mTag:一个标记,批量取消任务的时候可能用到。

    • 接下来看到它的构造函数:
        public Request(int method, String url, ErrorListener listener) {
            this.mEventLog = MarkerLog.ENABLED?new MarkerLog():null;
            this.mShouldCache = true;
            this.mCanceled = false;
            this.mResponseDelivered = false;
            this.mRequestBirthTime = 0L;
            this.mCacheEntry = null;
            this.mMethod = method;
            this.mUrl = url;
            this.mErrorListener = listener;
            this.setRetryPolicy(new DefaultRetryPolicy());
            this.mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
        }
    

    可以看到,在构造函数中,除了对传入的method,url,listener进行赋值外(响应成功的listener放在其子类中,具有更好的可扩展性),还对其他的成员变量进行默认的赋值。其中需要注意的是setRetryPolicy方法,观察其默认的重试策略。
    首先,所有的重试策略都需要实现RetryPolicy接口,也就意味着我们可以实现自己的重试策略。

    public interface RetryPolicy {
        int getCurrentTimeout();
    
        int getCurrentRetryCount();
    
        void retry(VolleyError var1) throws VolleyError;
    }
    

    其中getCurrentTimeout和getCurrentRetryCount分别为当前超时时间和重试次数,而retry方法则是具体的重试手段。在VolleyError封装了发生错误的响应信息,意味着我们可以拿到具体的错误代码。
    当重试操作不能执行的时候,我们需要抛出VolleyError异常,从而结束该请求(此操作在BasicNetwork中实现,后面会继续分析)。
    而VOLLEY的默认重试策略为:

    public class DefaultRetryPolicy implements RetryPolicy {
        private int mCurrentTimeoutMs;
        private int mCurrentRetryCount;
        private final int mMaxNumRetries;
        private final float mBackoffMultiplier;
        public static final int DEFAULT_TIMEOUT_MS = 2500;
        public static final int DEFAULT_MAX_RETRIES = 1;
        public static final float DEFAULT_BACKOFF_MULT = 1.0F;
    
        public DefaultRetryPolicy() {
            this(2500, 1, 1.0F);
        }
    
        public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
            this.mCurrentTimeoutMs = initialTimeoutMs;
            this.mMaxNumRetries = maxNumRetries;
            this.mBackoffMultiplier = backoffMultiplier;
        }
    
        public int getCurrentTimeout() {
            return this.mCurrentTimeoutMs;
        }
    
        public int getCurrentRetryCount() {
            return this.mCurrentRetryCount;
        }
    
        public float getBackoffMultiplier() {
            return this.mBackoffMultiplier;
        }
    
        public void retry(VolleyError error) throws VolleyError {
            ++this.mCurrentRetryCount;
            this.mCurrentTimeoutMs = (int)((float)this.mCurrentTimeoutMs + (float)this.mCurrentTimeoutMs * this.mBackoffMultiplier);
            if(!this.hasAttemptRemaining()) {
                throw error;
            }
        }
    
        protected boolean hasAttemptRemaining() {
            return this.mCurrentRetryCount <= this.mMaxNumRetries;
        }
    }
    

    可以看到,其默认超时时间为2500ms,重试次数为1,延时增量为1。其具体实现的策略很简单,每次将重试次数加1,每次重试的超时时长均为默认值,重试一次后仍失败则抛出异常,结束该次请求。

    回到Request类,从源码中可以看到,Request中许多方法都是对自身变量的存取操作,所以只对一些有价值的方法进行分析。
    看到finish()方法:

        void finish(final String tag) {
            if(this.mRequestQueue != null) {
                this.mRequestQueue.finish(this);
            }
    
            final long threadId;
            if(MarkerLog.ENABLED) {
                threadId = Thread.currentThread().getId();
                if(Looper.myLooper() != Looper.getMainLooper()) {
                    Handler mainThread = new Handler(Looper.getMainLooper());
                    mainThread.post(new Runnable() {
                        public void run() {
                            Request.this.mEventLog.add(tag, threadId);
                            Request.this.mEventLog.finish(this.toString());
                        }
                    });
                    return;
                }
    
                this.mEventLog.add(tag, threadId);
                this.mEventLog.finish(this.toString());
            } else {
                threadId = SystemClock.elapsedRealtime() - this.mRequestBirthTime;
                if(threadId >= 3000L) {
                    VolleyLog.d("%d ms: %s", new Object[]{Long.valueOf(threadId), this.toString()});
                }
            }
    
        }
    

    可以看到,该方法主要是调用RequestQueue的finish()方法来结束请求的,然后根据DEBUG日志的设置进行打印相关信息,这里先不对Volley的DEBUG结构进行解释。

    再看到它的encodeParameters()函数:

        private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
            StringBuilder encodedParams = new StringBuilder();
            try {
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
                    encodedParams.append('=');
                    encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
                    encodedParams.append('&');
                }
                return encodedParams.toString().getBytes(paramsEncoding);
            } catch (UnsupportedEncodingException uee) {
                throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
            }
        }
    

    其主要作用是解析传递过来的参数,将其拼接并转换成byte数组。但可以发现,在Request内部都是通过getParams()作为参数传递给这个方法的,再来看到getParams():

        protected Map<String, String> getParams() throws AuthFailureError {
            return null;
        }
    

    看到这里想到了什么?没错,我们在使用POST方式请求时,都需要重写getParams()方法,建造一个map来放置我们的参数就是因为这里默认返回空。那么这里为什么不将getParams()写成抽象函数呢?因为Request有多种请求方式,并不是每种请求方式都需要传递参数的,所以写成抽象方法的形式对不需要传参的请求方式就不大友好了,所以这里写成了可重写的protected方法。

    小结

    • 通过分析Request类,很好理解了volley进行网络请求的基本单位的结构,同时根据Request类的开放性,其实我们也能写出我们自己的网络请求类单位,比如在获得相应数据后,解析成我们想要的数据形式等。
    • 下一篇计划分析volley底层是如何对httpconnection和httpclient进行封装的。

    相关文章

      网友评论

        本文标题:VOLLEY源码完全解析之Request

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