@946898963
2018-05-18T04:06:01.000000Z
字数 11776
阅读 1290
Android控件跟框架 Android源码分析
Request是一个抽象类,Request被称为请求,通过继承Request来自定义request,为volley提供了更加灵活的接口。
Request中的泛型T,是指解析response以后的结果。ResponseDelivery会把response分派给对应的request。在我们定义的请求中,需要重写parseNetworkResponse(NetworkResponse response)这个方法,解析请求,解析出来的结果,就是T类型的。
Request请求接口结构如下所示:

接下来对Request的源码进行解析,首先看成员变量:
public abstract class Request<T> implements Comparable<Request<T>> {//默认编码private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";//请求方式枚举类型public interface Method {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;}//用于跟踪请求的生存时间,用于调试private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;//请求方式private final int mMethod;//请求URLprivate final String mUrl;//流量统计标签private final int mDefaultTrafficStatsTag;//错误监听器private final Response.ErrorListener mErrorListener;//请求序号,用于fifo算法private Integer mSequence;// 请求所在的请求队列private RequestQueue mRequestQueue;//是否使用缓存响应请求或者是否允许请求响应被缓存private boolean mShouldCache = true;//该请求是否被取消private boolean mCanceled = false;//该请求的结果是否已经被分发private boolean mResponseDelivered = false;//请求重试策略private RetryPolicy mRetryPolicy;//缓存记录。当请求可以从缓存中获得响应,但必须从网络上更新时。我们保留这个缓存记录,所以一旦从网络上获得的响应带有Not Modified (没有更新)时,来保证这个缓存没有被回收.private Cache.Entry mCacheEntry = null;//用于自定义标记,可以理解为用于请求的分类 撤销请求的时候会用到private Object mTag;....}
DEFAULT_PARAMS_ENCODING = "UTF-8" 默认编码
mMethod 请求方式
mUrl 请求地址
mDefaultTrafficStatsTag 流量统计标签
mErrorListener 错误监听器
mSequence 请求序列号,就是这个request在队列中的序号(不是顺序),用于fifo算法
mRequestQueue 请求所在的请求队列
mShouldCache 是否使用缓存响应请求或者是否允许请求响应被缓存
mCanceled 该请求是否被取消
mResponseDelivered 该请求的结果是否已经被分发
mRetryPolicy 请求重试策略
mCacheEntry 缓存记录,这个记录用于缓存响应头等
mTag 用于自定义标记,可以理解为用于请求的分类 撤销请求的时候会用到
//根据请求方式,创建新的请求(需要地址,错误监听器等参数)@Deprecatedpublic Request(String url, Response.ErrorListener listener) {this(Method.DEPRECATED_GET_OR_POST, url, listener);}public Request(int method, String url, Response.ErrorListener listener) {mMethod = method;mUrl = url;mErrorListener = listener;setRetryPolicy(new DefaultRetryPolicy());mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);}
public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {mRetryPolicy = retryPolicy;return this;}
首先是请求方式,请求地址的设定,这是作为一个请求必须有的。然后是监听器的设定,注意这里只是设置了ErrorListner,说明errorListener是必须的,但是正确响应,我们有可能不处理。这样设定是合理的,因为出错了,我们必须处理,至于请求成功,我们可以不处理。那么我们想处理成功的请求怎么办呢,这需要在子类中重写构造方法(例如StringRequest)。
private final Listener<String> mListener;public StringRequest(int method, String url, Listener<String> listener,ErrorListener errorListener) {super(method, url, errorListener);mListener = listener;}
接着是设置重试策略,关于重试策略,建议阅读:Volley的请求重试策略相关源码分析,接下来是流量标志的设置,所谓流量标志,是用于调试日志记录的,不是重点。
void finish(final String tag) {if (mRequestQueue != null) {mRequestQueue.finish(this);}//调试相关,不是重点,暂时不用关心if (MarkerLog.ENABLED) {final long threadId = Thread.currentThread().getId();//线程idif (Looper.myLooper() != Looper.getMainLooper()) {//请求不是在主线程//如果我们不是在主线程记录log,我们需要在主线程做这项工作来保证正确的顺序Handler mainThread = new Handler(Looper.getMainLooper());mainThread.post(new Runnable() {@Overridepublic void run() {mEventLog.add(tag, threadId);mEventLog.finish(this.toString());}});return;}//如果在主线程,直接记录mEventLog.add(tag, threadId);mEventLog.finish(this.toString());}}
finish方法,主要是做了一些日志记录的工作,最重要的是调用了mRequestQueue的finish()方法,来从队列中去除这个请求,关于具体的细节,建议阅读:中对RequestQueue的finish方法的介绍。
Request继承了Comparable>接口,重写了compareTo()方法,用于对请求进行排列。
//优先级枚举类public enum Priority {LOW,NORMAL,HIGH,IMMEDIATE}public Priority getPriority() {return Priority.NORMAL;}@Overridepublic int compareTo(Request<T> other) {Priority left = this.getPriority();Priority right = other.getPriority();//优先级越高,在请求队列中排得越前//相同优先级的按照序号进行排列,序号越低,排得越前。return left == right ?this.mSequence - other.mSequence :right.ordinal() - left.ordinal();}
在Volley中,一个Request是放在优先级阻塞队列中的,由阻塞线程和请求线程从队列中取出Request进行处理,排在队列前面的Request会优先被处理。有的请求比较重要,希望早点执行,我们可以让它排在请求队列的前头。通过比较方法,我们就可以设定请求在请求队列中排队顺序的根据,从而让优先级高的排在前面。
Request中提供了getPriority方法,默认返回Priority.NORMAL,如果我们想要修改Request的优先级,在创建Request的时候直接重写Request的这个方法即可,如下所示。
StringRequest stringRequest = new StringRequest(1,"",null,null){@Overridepublic Priority getPriority() {return Priority.HIGH;}};
//解析响应abstract protected Response<T> parseNetworkResponse(NetworkResponse response);//解析网络错误protected VolleyError parseNetworkError(VolleyError volleyError) {return volleyError;}//分发响应abstract protected void deliverResponse(T response);//分发网络错误public void deliverError(VolleyError error) {if (mErrorListener != null) {mErrorListener.onErrorResponse(error);}}
parseNetworkError和deliverError用于相应失败的情况,parseNetworkError()用于解析Volleyerror,deliverError()方法回调了前面提到的ErrorListener。
parseNetworkError()是在NetworkDispatcher的run方法中调用的,deliverError()是在ExecutorDelivery中调用的。
parseNetworkResponse和deliverResponse用于处理相应成功的情况,parseNetworkResponse(NetworkResponse response)用于将网络response解析为本地response,解析出来的response,会交给deliverResponse(T response)方法。
parseNetworkResponse()是在NetworkDispatcher的run方法中调用的,deliverResponse()是在ExecutorDelivery中调用的。
关于为什么要解析,其实开头已经说过,要将结果解析为T类型。Volley中提供的各种Request都重写了这这个方法,将结果解析成自己定义的类型。
public Map<String, String> getHeaders() throws AuthFailureError {return Collections.emptyMap();}
返回包含首部信息的Map,key为首部字段,value为首部字段的值,默认返回为空Map,如果用户想要设置某些请求首部,创建Request的时候,重写这个方法即可。
StringRequest stringRequest = new StringRequest(1,"",null,null){@Overridepublic Map<String, String> getHeaders() throws AuthFailureError {Map<String, String> headers = new HashMap<>();return headers;}};
设置在Map中的首部信息,会在HttpStack的实现类的performRequest方法中,被取出,设置给网络请求。
@Overridepublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError {String url = request.getUrl();HashMap<String, String> map = new HashMap<String, String>();map.putAll(request.getHeaders());map.putAll(additionalHeaders);....URL parsedUrl = new URL(url);HttpURLConnection connection = openConnection(parsedUrl, request);for (String headerName : map.keySet()) {connection.addRequestProperty(headerName, map.get(headerName));}....return response;}
我们可以设置Request为各种http请求方式,最常见的是get方式和post方式。通过get方式请求数据的时候,数据是通过组拼url提交的,也就是说如果我们的Request为get请求方式,我们直接将数据附加在url上即可。但是当我通过post方式提交数据的时候,数据是被添加在请求体中提交的,所以如果我们想要设置Request为post请求方式,我们需要重写getParams()方法,将我们的参数附加在map中:
StringRequest stringRequest = new StringRequest(1,"",null,null){@Overrideprotected Map<String, String> getParams() throws AuthFailureError {Map<String, String> params = new HashMap<>();params.put("userName", "zhangsan");params.put("password", "123");return params;}};
我们通过getParams()返回的含有的参数值的Map最终也是在HttpStack的实现类的performRequest方法中,被取出,设置给网络请求。
@Overridepublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError {String url = request.getUrl();HashMap<String, String> map = new HashMap<String, String>();map.putAll(request.getHeaders());map.putAll(additionalHeaders);....URL parsedUrl = new URL(url);HttpURLConnection connection = openConnection(parsedUrl, request);for (String headerName : map.keySet()) {connection.addRequestProperty(headerName, map.get(headerName));}setConnectionParametersForRequest(connection, request);....return response;}
设置的操作,就发生在HttpStack的实现类的setConnectionParametersForRequest方法中:
static void setConnectionParametersForRequest(HttpURLConnection connection,Request<?> request) throws IOException, AuthFailureError {switch (request.getMethod()) {....case Method.POST:connection.setRequestMethod("POST");addBodyIfExists(connection, request);break;....default:throw new IllegalStateException("Unknown method type.");}}
我们可以看到当Request位post请求方式的时候,会执行如下代码:
connection.setRequestMethod("POST");addBodyIfExists(connection, request);
设置HttpUrlConnection的请求方式为post请求方式,同时将提交的数据设置到HttpUrlConnection中,设置的最终操作就是HttpStack的实现类的addBodyIfExists方法中:
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)throws IOException, AuthFailureError {byte[] body = request.getBody();if (body != null) {connection.setDoOutput(true);connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());DataOutputStream out = new DataOutputStream(connection.getOutputStream());out.write(body);out.close();}}
在addBodyIfExists方法中,会调用Request的getBody()方法,getBody()中调用了我们重写的Request的getParams方法,获取到了含有我们要提交的数据的map。
Request中的方法:public byte[] getBody() throws AuthFailureError {Map<String, String> params = getParams();if (params != null && params.size() > 0) {return encodeParameters(params, getParamsEncoding());}return null;}private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";protected String getParamsEncoding() {return DEFAULT_PARAMS_ENCODING;}
getBody方法中取出我们要提交的数据之后,会按照我们设置的编码方式,将数据进行编码。
Request中的方法: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);}}
继续回到HttpStack的实现类的addBodyIfExists方法:
private static final String HEADER_CONTENT_TYPE = "Content-Type";private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)throws IOException, AuthFailureError {byte[] body = request.getBody();if (body != null) {connection.setDoOutput(true);connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());DataOutputStream out = new DataOutputStream(connection.getOutputStream());out.write(body);out.close();}}
得到按照特定的编码方式编码的要提交的数据的字节数组之后,通过输出流将数据提到到服务器,在提交之前,设置了请求的 "Content-Type"首部字段:
Request中的方法:public String getBodyContentType() {return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();}
这样,设置Request为post方式提交数据的时候,Request的几个方法是如何调用的,我们已经很清楚了。
最后再看下当我们不设置Request的请求方式的时候,Request中的几个相关方法是如何调用的,当我们不设置Request的请求方式的时候,请求方式mMethod的值为:
Method.DEPRECATED_GET_OR_POST
我们在直接看HttpStack的实现类的setConnectionParametersForRequest方法,是如何处理的。
static void setConnectionParametersForRequest(HttpURLConnection connection,Request<?> request) throws IOException, AuthFailureError {switch (request.getMethod()) {case Method.DEPRECATED_GET_OR_POST:byte[] postBody = request.getPostBody();if (postBody != null) {connection.setDoOutput(true);connection.setRequestMethod("POST");connection.addRequestProperty(HEADER_CONTENT_TYPE,request.getPostBodyContentType());DataOutputStream out = new DataOutputStream(connection.getOutputStream());out.write(postBody);out.close();}break;....default:throw new IllegalStateException("Unknown method type.");}}
当请求方式为Method.DEPRECATED_GET_OR_POST的时候,先调用Request的getPostBody()方法,检查有没有要提交的数据,如果没有数据的话,则认为这个请求方式是get方法,注意我们看到这里并没有执行设置connection为get请求方式,
connection.setRequestMethod("GET");
这是因为HttpUrlConnection默认就是get方式的,所以这里并没有进行设置。如果有提交的数据的话,则和前面处理方式一样,通过输出流将数据写到服务器,在提交之前,同样设置了请求的"Content-Type"首部字段,只不过这里调用的是getPostBodyContentType(),其实最终调用的还是getBodyContentType()方法,我们看下getPostBodyContentType()的源码:
Request中的方法:@Deprecatedpublic String getPostBodyContentType() {return getBodyContentType();}
最后看下getPostBody()的实现,getPostBody()的源码如下所示:
@Deprecatedpublic byte[] getPostBody() throws AuthFailureError {Map<String, String> postParams = getPostParams();if (postParams != null && postParams.size() > 0) {return encodeParameters(postParams, getPostParamsEncoding());}return null;}
getPostBody()中调用了getPostParams()方法,getPostParams()的源码如下所示:
@Deprecatedprotected Map<String, String> getPostParams() throws AuthFailureError {return getParams();}
getPostParams()最终调用了getParams()方法。
在对数据进行编码的encodeParameters方法中,调用了getPostParamsEncoding()方法,getPostParamsEncoding()的源码如下所示:
@Deprecatedprotected String getPostParamsEncoding() {return getParamsEncoding()。;}
getPostParamsEncoding()最终是调用了getParamsEncoding()方法。
至此,关于Request的源码分析就结束了,撒欢。
参考链接:
详细解读Volley(一)—— 基本Request对象 & RequestQueue
Volley学习(三)ImageRequest、ImageLoader、NetworkImageView源码简读