从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进行封装的。
网友评论