[关闭]
@946898963 2018-05-18T04:06:01.000000Z 字数 11776 阅读 902

Volley-Request的源码解析

Android控件跟框架 Android源码分析


前言

Request是一个抽象类,Request被称为请求,通过继承Request来自定义request,为volley提供了更加灵活的接口。

Request中的泛型T,是指解析response以后的结果。ResponseDelivery会把response分派给对应的request。在我们定义的请求中,需要重写parseNetworkResponse(NetworkResponse response)这个方法,解析请求,解析出来的结果,就是T类型的。

继承关系

Request请求接口结构如下所示:

此处输入图片的描述

源码分析

成员变量

接下来对Request的源码进行解析,首先看成员变量

  1. public abstract class Request<T> implements Comparable<Request<T>> {
  2. //默认编码
  3. private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
  4. //请求方式枚举类型
  5. public interface Method {
  6. int DEPRECATED_GET_OR_POST = -1;
  7. int GET = 0;
  8. int POST = 1;
  9. int PUT = 2;
  10. int DELETE = 3;
  11. int HEAD = 4;
  12. int OPTIONS = 5;
  13. int TRACE = 6;
  14. int PATCH = 7;
  15. }
  16. //用于跟踪请求的生存时间,用于调试
  17. private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
  18. //请求方式
  19. private final int mMethod;
  20. //请求URL
  21. private final String mUrl;
  22. //流量统计标签
  23. private final int mDefaultTrafficStatsTag;
  24. //错误监听器
  25. private final Response.ErrorListener mErrorListener;
  26. //请求序号,用于fifo算法
  27. private Integer mSequence;
  28. // 请求所在的请求队列
  29. private RequestQueue mRequestQueue;
  30. //是否使用缓存响应请求或者是否允许请求响应被缓存
  31. private boolean mShouldCache = true;
  32. //该请求是否被取消
  33. private boolean mCanceled = false;
  34. //该请求的结果是否已经被分发
  35. private boolean mResponseDelivered = false;
  36. //请求重试策略
  37. private RetryPolicy mRetryPolicy;
  38. //缓存记录。当请求可以从缓存中获得响应,但必须从网络上更新时。我们保留这个缓存记录,所以一旦从网络上获得的响应带有Not Modified (没有更新)时,来保证这个缓存没有被回收.
  39. private Cache.Entry mCacheEntry = null;
  40. //用于自定义标记,可以理解为用于请求的分类 撤销请求的时候会用到
  41. private Object mTag;
  42. ....
  43. }
  1. DEFAULT_PARAMS_ENCODING = "UTF-8" 默认编码

  2. mMethod 请求方式

  3. mUrl 请求地址

  4. mDefaultTrafficStatsTag 流量统计标签

  5. mErrorListener 错误监听器

  6. mSequence 请求序列号,就是这个request在队列中的序号(不是顺序),用于fifo算法

  7. mRequestQueue 请求所在的请求队列

  8. mShouldCache 是否使用缓存响应请求或者是否允许请求响应被缓存

  9. mCanceled 该请求是否被取消

  10. mResponseDelivered 该请求的结果是否已经被分发

  11. mRetryPolicy 请求重试策略

  12. mCacheEntry 缓存记录,这个记录用于缓存响应头等

  13. mTag 用于自定义标记,可以理解为用于请求的分类 撤销请求的时候会用到

构造方法

  1. //根据请求方式,创建新的请求(需要地址,错误监听器等参数)
  2. @Deprecated
  3. public Request(String url, Response.ErrorListener listener) {
  4. this(Method.DEPRECATED_GET_OR_POST, url, listener);
  5. }
  6. public Request(int method, String url, Response.ErrorListener listener) {
  7. mMethod = method;
  8. mUrl = url;
  9. mErrorListener = listener;
  10. setRetryPolicy(new DefaultRetryPolicy());
  11. mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
  12. }
  1. public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
  2. mRetryPolicy = retryPolicy;
  3. return this;
  4. }

首先是请求方式,请求地址的设定,这是作为一个请求必须有的。然后是监听器的设定,注意这里只是设置了ErrorListner,说明errorListener是必须的,但是正确响应,我们有可能不处理。这样设定是合理的,因为出错了,我们必须处理,至于请求成功,我们可以不处理。那么我们想处理成功的请求怎么办呢,这需要在子类中重写构造方法(例如StringRequest)。

  1. private final Listener<String> mListener;
  2. public StringRequest(int method, String url, Listener<String> listener,
  3. ErrorListener errorListener) {
  4. super(method, url, errorListener);
  5. mListener = listener;
  6. }

接着是设置重试策略,关于重试策略,建议阅读:Volley的请求重试策略相关源码分析,接下来是流量标志的设置,所谓流量标志,是用于调试日志记录的,不是重点。

finish方法

  1. void finish(final String tag) {
  2. if (mRequestQueue != null) {
  3. mRequestQueue.finish(this);
  4. }
  5. //调试相关,不是重点,暂时不用关心
  6. if (MarkerLog.ENABLED) {
  7. final long threadId = Thread.currentThread().getId();//线程id
  8. if (Looper.myLooper() != Looper.getMainLooper()) {//请求不是在主线程
  9. //如果我们不是在主线程记录log,我们需要在主线程做这项工作来保证正确的顺序
  10. Handler mainThread = new Handler(Looper.getMainLooper());
  11. mainThread.post(new Runnable() {
  12. @Override
  13. public void run() {
  14. mEventLog.add(tag, threadId);
  15. mEventLog.finish(this.toString());
  16. }
  17. });
  18. return;
  19. }
  20. //如果在主线程,直接记录
  21. mEventLog.add(tag, threadId);
  22. mEventLog.finish(this.toString());
  23. }
  24. }

finish方法,主要是做了一些日志记录的工作,最重要的是调用了mRequestQueue的finish()方法,来从队列中去除这个请求,关于具体的细节,建议阅读:中对RequestQueue的finish方法的介绍。

compareTo方法

Request继承了Comparable>接口,重写了compareTo()方法,用于对请求进行排列。

  1. //优先级枚举类
  2. public enum Priority {
  3. LOW,
  4. NORMAL,
  5. HIGH,
  6. IMMEDIATE
  7. }
  8. public Priority getPriority() {
  9. return Priority.NORMAL;
  10. }
  11. @Override
  12. public int compareTo(Request<T> other) {
  13. Priority left = this.getPriority();
  14. Priority right = other.getPriority();
  15. //优先级越高,在请求队列中排得越前
  16. //相同优先级的按照序号进行排列,序号越低,排得越前。
  17. return left == right ?
  18. this.mSequence - other.mSequence :
  19. right.ordinal() - left.ordinal();
  20. }

在Volley中,一个Request是放在优先级阻塞队列中的,由阻塞线程和请求线程从队列中取出Request进行处理,排在队列前面的Request会优先被处理。有的请求比较重要,希望早点执行,我们可以让它排在请求队列的前头。通过比较方法,我们就可以设定请求在请求队列中排队顺序的根据,从而让优先级高的排在前面。

Request中提供了getPriority方法,默认返回Priority.NORMAL,如果我们想要修改Request的优先级,在创建Request的时候直接重写Request的这个方法即可,如下所示。

  1. StringRequest stringRequest = new StringRequest(1,"",null,null){
  2. @Override
  3. public Priority getPriority() {
  4. return Priority.HIGH;
  5. }
  6. };

响应处理方法

  1. //解析响应
  2. abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
  3. //解析网络错误
  4. protected VolleyError parseNetworkError(VolleyError volleyError) {
  5. return volleyError;
  6. }
  7. //分发响应
  8. abstract protected void deliverResponse(T response);
  9. //分发网络错误
  10. public void deliverError(VolleyError error) {
  11. if (mErrorListener != null) {
  12. mErrorListener.onErrorResponse(error);
  13. }
  14. }

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都重写了这这个方法,将结果解析成自己定义的类型。

getHeaders()方法

  1. public Map<String, String> getHeaders() throws AuthFailureError {
  2. return Collections.emptyMap();
  3. }

返回包含首部信息的Map,key为首部字段,value为首部字段的值,默认返回为空Map,如果用户想要设置某些请求首部,创建Request的时候,重写这个方法即可。

  1. StringRequest stringRequest = new StringRequest(1,"",null,null){
  2. @Override
  3. public Map<String, String> getHeaders() throws AuthFailureError {
  4. Map<String, String> headers = new HashMap<>();
  5. return headers;
  6. }
  7. };

设置在Map中的首部信息,会在HttpStack的实现类的performRequest方法中,被取出,设置给网络请求。

  1. @Override
  2. public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
  3. throws IOException, AuthFailureError {
  4. String url = request.getUrl();
  5. HashMap<String, String> map = new HashMap<String, String>();
  6. map.putAll(request.getHeaders());
  7. map.putAll(additionalHeaders);
  8. ....
  9. URL parsedUrl = new URL(url);
  10. HttpURLConnection connection = openConnection(parsedUrl, request);
  11. for (String headerName : map.keySet()) {
  12. connection.addRequestProperty(headerName, map.get(headerName));
  13. }
  14. ....
  15. return response;
  16. }

post相关方法

我们可以设置Request为各种http请求方式,最常见的是get方式和post方式。通过get方式请求数据的时候,数据是通过组拼url提交的,也就是说如果我们的Request为get请求方式,我们直接将数据附加在url上即可。但是当我通过post方式提交数据的时候,数据是被添加在请求体中提交的,所以如果我们想要设置Request为post请求方式,我们需要重写getParams()方法,将我们的参数附加在map中:

  1. StringRequest stringRequest = new StringRequest(1,"",null,null){
  2. @Override
  3. protected Map<String, String> getParams() throws AuthFailureError {
  4. Map<String, String> params = new HashMap<>();
  5. params.put("userName", "zhangsan");
  6. params.put("password", "123");
  7. return params;
  8. }
  9. };

我们通过getParams()返回的含有的参数值的Map最终也是在HttpStack的实现类的performRequest方法中,被取出,设置给网络请求。

  1. @Override
  2. public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
  3. throws IOException, AuthFailureError {
  4. String url = request.getUrl();
  5. HashMap<String, String> map = new HashMap<String, String>();
  6. map.putAll(request.getHeaders());
  7. map.putAll(additionalHeaders);
  8. ....
  9. URL parsedUrl = new URL(url);
  10. HttpURLConnection connection = openConnection(parsedUrl, request);
  11. for (String headerName : map.keySet()) {
  12. connection.addRequestProperty(headerName, map.get(headerName));
  13. }
  14. setConnectionParametersForRequest(connection, request);
  15. ....
  16. return response;
  17. }

设置的操作,就发生在HttpStack的实现类的setConnectionParametersForRequest方法中:

  1. static void setConnectionParametersForRequest(HttpURLConnection connection,
  2. Request<?> request) throws IOException, AuthFailureError {
  3. switch (request.getMethod()) {
  4. ....
  5. case Method.POST:
  6. connection.setRequestMethod("POST");
  7. addBodyIfExists(connection, request);
  8. break;
  9. ....
  10. default:
  11. throw new IllegalStateException("Unknown method type.");
  12. }
  13. }

我们可以看到当Request位post请求方式的时候,会执行如下代码:

  1. connection.setRequestMethod("POST");
  2. addBodyIfExists(connection, request);

设置HttpUrlConnection的请求方式为post请求方式,同时将提交的数据设置到HttpUrlConnection中,设置的最终操作就是HttpStack的实现类的addBodyIfExists方法中:

  1. private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
  2. throws IOException, AuthFailureError {
  3. byte[] body = request.getBody();
  4. if (body != null) {
  5. connection.setDoOutput(true);
  6. connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
  7. DataOutputStream out = new DataOutputStream(connection.getOutputStream());
  8. out.write(body);
  9. out.close();
  10. }
  11. }

在addBodyIfExists方法中,会调用Request的getBody()方法,getBody()中调用了我们重写的Request的getParams方法,获取到了含有我们要提交的数据的map。

  1. Request中的方法:
  2. public byte[] getBody() throws AuthFailureError {
  3. Map<String, String> params = getParams();
  4. if (params != null && params.size() > 0) {
  5. return encodeParameters(params, getParamsEncoding());
  6. }
  7. return null;
  8. }
  9. private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
  10. protected String getParamsEncoding() {
  11. return DEFAULT_PARAMS_ENCODING;
  12. }

getBody方法中取出我们要提交的数据之后,会按照我们设置的编码方式,将数据进行编码。

  1. Request中的方法:
  2. private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
  3. StringBuilder encodedParams = new StringBuilder();
  4. try {
  5. for (Map.Entry<String, String> entry : params.entrySet()) {
  6. encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
  7. encodedParams.append('=');
  8. encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
  9. encodedParams.append('&');
  10. }
  11. return encodedParams.toString().getBytes(paramsEncoding);
  12. } catch (UnsupportedEncodingException uee) {
  13. throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
  14. }
  15. }

继续回到HttpStack的实现类的addBodyIfExists方法:

  1. private static final String HEADER_CONTENT_TYPE = "Content-Type";
  2. private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
  3. throws IOException, AuthFailureError {
  4. byte[] body = request.getBody();
  5. if (body != null) {
  6. connection.setDoOutput(true);
  7. connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
  8. DataOutputStream out = new DataOutputStream(connection.getOutputStream());
  9. out.write(body);
  10. out.close();
  11. }
  12. }

得到按照特定的编码方式编码的要提交的数据的字节数组之后,通过输出流将数据提到到服务器,在提交之前,设置了请求的 "Content-Type"首部字段:

  1. Request中的方法:
  2. public String getBodyContentType() {
  3. return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
  4. }

这样,设置Request为post方式提交数据的时候,Request的几个方法是如何调用的,我们已经很清楚了。

最后再看下当我们不设置Request的请求方式的时候,Request中的几个相关方法是如何调用的,当我们不设置Request的请求方式的时候,请求方式mMethod的值为:

  1. Method.DEPRECATED_GET_OR_POST

我们在直接看HttpStack的实现类的setConnectionParametersForRequest方法,是如何处理的。

  1. static void setConnectionParametersForRequest(HttpURLConnection connection,
  2. Request<?> request) throws IOException, AuthFailureError {
  3. switch (request.getMethod()) {
  4. case Method.DEPRECATED_GET_OR_POST:
  5. byte[] postBody = request.getPostBody();
  6. if (postBody != null) {
  7. connection.setDoOutput(true);
  8. connection.setRequestMethod("POST");
  9. connection.addRequestProperty(HEADER_CONTENT_TYPE,
  10. request.getPostBodyContentType());
  11. DataOutputStream out = new DataOutputStream(connection.getOutputStream());
  12. out.write(postBody);
  13. out.close();
  14. }
  15. break;
  16. ....
  17. default:
  18. throw new IllegalStateException("Unknown method type.");
  19. }
  20. }

当请求方式为Method.DEPRECATED_GET_OR_POST的时候,先调用Request的getPostBody()方法,检查有没有要提交的数据,如果没有数据的话,则认为这个请求方式是get方法,注意我们看到这里并没有执行设置connection为get请求方式,

  1. connection.setRequestMethod("GET");

这是因为HttpUrlConnection默认就是get方式的,所以这里并没有进行设置。如果有提交的数据的话,则和前面处理方式一样,通过输出流将数据写到服务器,在提交之前,同样设置了请求的"Content-Type"首部字段,只不过这里调用的是getPostBodyContentType(),其实最终调用的还是getBodyContentType()方法,我们看下getPostBodyContentType()的源码:

  1. Request中的方法:
  2. @Deprecated
  3. public String getPostBodyContentType() {
  4. return getBodyContentType();
  5. }

最后看下getPostBody()的实现,getPostBody()的源码如下所示:

  1. @Deprecated
  2. public byte[] getPostBody() throws AuthFailureError {
  3. Map<String, String> postParams = getPostParams();
  4. if (postParams != null && postParams.size() > 0) {
  5. return encodeParameters(postParams, getPostParamsEncoding());
  6. }
  7. return null;
  8. }

getPostBody()中调用了getPostParams()方法,getPostParams()的源码如下所示:

  1. @Deprecated
  2. protected Map<String, String> getPostParams() throws AuthFailureError {
  3. return getParams();
  4. }

getPostParams()最终调用了getParams()方法。

在对数据进行编码的encodeParameters方法中,调用了getPostParamsEncoding()方法,getPostParamsEncoding()的源码如下所示:

  1. @Deprecated
  2. protected String getPostParamsEncoding() {
  3. return getParamsEncoding()。;
  4. }

getPostParamsEncoding()最终是调用了getParamsEncoding()方法。

至此,关于Request的源码分析就结束了,撒欢。

参考链接:

详细解读Volley(一)—— 基本Request对象 & RequestQueue

Volley库源码解析----主要是Request

Volley学习(三)ImageRequest、ImageLoader、NetworkImageView源码简读

volley源码解析(二)--Request类的介绍(只介绍了Request类,参考完)

Volley源码解析<三> Request请求

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注