与Request相关的类
- Request:实现一个请求的基类
- HttpHeaderParse:实现对响应头的解析
- StringRequest:实现String类型的请求类
- JsonRequest:实现Json类型的请求基类
- JsonObjectRequest:实现JsonObject类型的请求类
- JsonArrayRequest:实现JsonArray类型的请求类
1.Request类的实现
- 常用变量的定义
//定义默认的字符编码
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 VolleyLog.MarkerLog mEventLog = VolleyLog.MarkerLog.ENABLED ? new VolleyLog.MarkerLog() : null;
private final int mMethod;
private final String mUrl;
//流量统计的默认标志
private final int mDefaultTrafficStatsTag;
/** Listener interface for errors. */
private final Response.ErrorListener mErrorListener;
//请求的序列号,在优先级相同的情况下,用于保证FIFO
private Integer mSequence;
//请求队列
private RequestQueue mRequestQueue;
//是否需要缓存
private boolean mShouldCache = true;
//该请求是否存在缓存
private boolean mCanceled = false;
//该请求事件是否被分发
private boolean mResponseDelivered = false;
/** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
private boolean mShouldRetryServerErrors = false;
//重试策略
private RetryPolicy mRetryPolicy;
//缓存内容
private Cache.Entry mCacheEntry = null;
//标志该请求,用于批量取消
private Object mTag;
public 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;
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
setRetryPolicy(new DefaultRetryPolicy());
}
- 针对POST类型,对请求体数据的解析:
//获取提交内容
public byte[] getPostBody() throws AuthFailureError{
Map<String,String> postParams = getParams();
if (postParams != null && postParams.size() > 0){
return encodeParameter(postParams, getPostParamsEncoding());
}
return null;
}
//将提交内容进行解析,返回为byte数组
private byte[] encodeParameter(Map<String,String> params, String paramEncoding){
StringBuilder encodedParams = new StringBuilder();
try {
for (Map.Entry<String,String> entry : params.entrySet()){
encodedParams.append(URLEncoder.encode(entry.getKey(),paramEncoding));
encodedParams.append("=");
encodedParams.append(URLEncoder.encode(entry.getValue(), paramEncoding));
encodedParams.append("&");
}
return encodedParams.toString().getBytes(paramEncoding);
} catch (UnsupportedEncodingException uee){
throw new RuntimeException("Encoding not supported: " + paramEncoding, uee);
}
}
- 实现请求的优先级
public enum Priority{
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
//默认为普通优先级
public Priority getPriority(){
return Priority.NORMAL;
}
//定义请求的“大小”:优先级优先,若优先级相同,则比较序列数
@Override
public int compareTo(Request<T> o) {
//获取其对应的优先级
Priority left = this.getPriority();
Priority right = o.getPriority();
// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
// 注意序列数和优先级的前后顺序
return left == right ?
this.mSequence - o.mSequence :
right.ordinal() - left.ordinal();
}
要注意的是这里对优先级高的线程对应的数值为较小。
关于ordinal函数的使用,若没有给对应的变量赋值,会默认第一个为0,并逐个递增。如下:
enum Priority{
LOW,
NORMAL,
HIGH
}
public static void main(String[] args){
System.out.print(Priority.LOW.ordinal());
System.out.print(Priority.NORMAL.ordinal());
System.out.println(Priority.HIGH.ordinal());
}
输出结果:
0 1 2
- 取消请求事件
//通知请求队列将该请求终止
void finish(final String tag){
if (mRequestQueue != null){
mRequestQueue.finish(this);
}
if (VolleyLog.MarkerLog.ENABLED){
final long threadId = Thread.currentThread().getId();
//使用主线程来记录该事件,保证事件的有序性
if (Looper.getMainLooper() != Looper.myLooper()){
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
}
实际上,这里重点是要学习日志记录线程时间时,采用主线程提交方式,以保证事件的有序性。否则,若交给其他线程处理,无法确保事件的先后顺序。
2.HttpHeaderParse的实现
- 获取响应头的字符编码
Content-Type: application/x-www-form-urlencoded;charset=utf-8;
具体实例如上,那么我们要做的就是获取到charset中对应的内容
//获取响应头的字符编码
public static String parseCharset(Map<String,String> headers, String defaultCharset){
//获取到该行数据
String contentType = headers.get("Content-Type");
if (contentType != null){
String[] params = contentType.split(";");
for (int i = 0; i < params.length; i ++){
//trim清除空白字符
String[] pair = params[i].trim().split("=");
if (pair.length == 2 && pair[0].equals("charset")){
return pair[1];
}
}
}
return defaultCharset;
}
public static String parseCharset(Map<String,String> headers){
return parseCharset(headers, HTTP.DEFAULT_CONTENT_CHARSET);
}
- 对响应头进行解析:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
Cache-Control: no-cache
具体实例如上,我们要获取的信息主要是用于构造Entry对象
//解析响应头,并返回一个Entry对象
public static Cache.Entry parseCacheHeaders(NetworkResponse response){
//获取当前时间
long now = System.currentTimeMillis();
Map<String,String> headers = response.headers;
//服务器返回数据的时间
long serverDate = 0;
//上一次修改的时间
long lastModified = 0;
//服务器过期时间
//三者之间的关系:第一个为服务器获取的事件,后两个对应为Entry对象中的softTtl和ttl
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 headValue;
headValue = headers.get("Date");
if (headValue != null){
serverDate = parseDateAsEpoch(headValue);
}
headValue = headers.get("Cache-Control");
//获取服务器缓存有关信息
if (headValue != null){
hasCacheControl = true;
String[] tokens = headValue.split(",");
for (int i = 0; i < tokens.length; i ++){
String token = tokens[i].trim();
//不使用缓存,则直接返回null
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-with-revalidate=")){
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e){
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")){
mustRevalidate = true;
}
}
}
headValue = headers.get("Expires");
if (headValue != null){
serverExpires = parseDateAsEpoch(headValue);
}
headValue = headers.get("last-Modified");
if (headValue != null){
lastModified = parseDateAsEpoch(headValue);
}
serverETag = headers.get("ETag");
//计算获取Entry对象中的ttl和softTtl
if (hasCacheControl){
//注意:这里的单位为毫秒
softExpire = now + maxAge * 1000;
//若不需要立马重新验证,那么缓存还可以再存活一段时间
finalExpire = mustRevalidate ?
softExpire : softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate){
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
//返回Entry对象
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;
}
//将时间格式化为Long类型
private static long parseDateAsEpoch(String headValue) {
try {
return DateUtils.parseDate(headValue).getTime();
} catch (DateParseException e){
return 0;
}
}
要注意的一点是对日期的处理,转换为Long类型保存
3. 实现JsonRequest
public abstract class JsonRequest<T> extends Request<T>{
//定义默认字符编码
protected static final String PROTOCOL_CHARSET = "utf-8";
//定义默认请求类型
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json;charset=%s",PROTOCOL_CHARSET);
private final Response.Listener<T> mListener;
//提交的请求体
private final String mRequestBody;
public JsonRequest(String url, String requestBody, Response.Listener<T> listener,
Response.ErrorListener errorListener){
this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener);
}
public JsonRequest(int method, String url, String requestBody, Response.Listener<T> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
mRequestBody = requestBody;
}
@Override
protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
@Override
public String getPostBodyContentType() {
return getBodyContentType();
}
//重新定义了请求体的Content-Type
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
@Override
public byte[] getPostBody() throws AuthFailureError {
return getBody();
}
//若请求体不为空,则将请求体转换为byte数组返回
@Override
public byte[] getBody() throws AuthFailureError {
try {
return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (UnsupportedEncodingException e){
VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
mRequestBody, PROTOCOL_CHARSET);
return null;
}
}
}
要注意的几点:
- 重新定义了其请求体的Content-Type类型,因为Request基类中定义的Content-Type并不包含json数据:
public String getBodyContentType(){
return "application/x-www-form-urlencoded;charset=" + getParamsEncoding();
}
- 重新了请求体的getBody内容,保证是在Json定义下的字符编码下进行转换为byte数组类型
4.实现JsonObjectRequest
public class JsonObjectRequest extends JsonRequest<JSONObject> {
//传入JsonObject数据
public JsonObjectRequest(int method, String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
listener, errorListener);
}
//根据JsonObject数据判断请求方式
public JsonObjectRequest(String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
listener, errorListener);
}
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
//转换为String类型
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
//返回为JsonObject类型
return Response.success(new JSONObject(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e){
return Response.error(new ParseError(e));
} catch (JSONException je){
return Response.error(new ParseError(je));
}
}
}
实际上,基于JsonRequest实现,所需添加的内容非常简单。不过要注意的是其构造函数的使用,根据传入的JSONObject类型参数判断是否为空,来决定其请求方式。
5. 实现JsonArrayRequest
public class JsonArrayRequest extends JsonRequest<JSONArray> {
public JsonArrayRequest(String url, Response.Listener<JSONArray> listener,
Response.ErrorListener errorListener){
this(Method.GET, url, null, listener, errorListener);
}
public JsonArrayRequest(int method, String url, JSONArray jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener);
}
@Override
protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
//返回JsonArray类型的结果
return Response.success(new JSONArray(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException je){
return Response.error(new ParseError(je));
}
}
}
实际上,该实现和JsonObjectRequest类似,只不过就是在解析响应时,返回的数据类型不同。
6. 实现StringRequest
public class StringRequest extends Request<String> {
private final Response.Listener<String> mListener;
public StringRequest(String url, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
super(url, errorListener);
mListener = listener;
}
public StringRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener){
super(method, url, errorListener);
mListener = listener;
}
//对数据进行解析,并返回
@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 ));
}
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
}
实际上,就做了两步,一步是用listener去分发结果,一步是将响应解析为String类型
7. 实现ImageRequest
- 对ImageRequest中常见变量的定义
//默认获取图片时间
public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;
//默认尝试次数
public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;
public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
private final Response.Listener<Bitmap> mListener;
//图片保存格式,比如ARGB_8888
private final Bitmap.Config mDecodeConfig;
//传入的所需图片的宽高
private final int mMaxWidth;
private final int mMaxHeight;
//图片的缩放类型
private final ImageView.ScaleType mScaleType;
//图片请求的锁,保证图片在解析过程中的线程安全性,避免多个线程对图片进行解析
private static final Object sDecodeLock = new Object();
public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
ImageView.ScaleType scaleType, Bitmap.Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(new DefaultRetryPolicy(DEFAULT_IMAGE_TIMEOUT_MS,DEFAULT_IMAGE_MAX_RETRIES
, DEFAULT_IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxHeight = maxHeight;
mMaxWidth = maxWidth;
mScaleType = scaleType;
}
public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
Bitmap.Config decodeConfig, Response.ErrorListener errorListener) {
this(url, listener, maxWidth, maxHeight, ImageView.ScaleType.CENTER_INSIDE,
decodeConfig, errorListener);
}
- 解析响应,并返回Bitmap对象
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
//保证单一线程进行解析图片
synchronized (sDecodeLock){
try {
return doParse(response);
} catch (OutOfMemoryError e){
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length);
return Response.error(new ParseError(e));
}
}
我们可以看到解析过程是保证单线程进行的,防止同一张图进行多次解析,带来的不必要的浪费。
//对图片进行解析
private Response<Bitmap> doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
//若图片所需的显示宽高设置都为0,则显示原图即可
if (mMaxWidth == 0 && mMaxHeight == 0){
//设置图片格式
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
//保证只解析图片的宽高
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
//获取到原图的宽高
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
//获取图片预期的宽高
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth,
actualHeight, mScaleType);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight,
actualWidth, mScaleType);
//对图片进行解析,并转换为Bitmap对象
decodeOptions.inJustDecodeBounds = false;
//获取图片的缩放比
decodeOptions.inSampleSize = findBestSimpleSize(actualWidth, actualHeight,
desiredWidth, desiredHeight);
//???是否需要添加
decodeOptions.inPreferredConfig = mDecodeConfig;
//创建一个临时的Bitmap对象
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length,
decodeOptions);
//若该对象超过预期的宽高,则需要进一步缩小
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)){
bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
//回收临时Bitmap对象
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
//返回解析结果
if (bitmap == null){
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
如上为图片的解析过程,主要流程为:
获取图片预期的宽高 —— 获取图片的缩放比 —— 获取到对应的Bitmap对象
获取图片预期的宽高
//获取预期宽高
private int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
int actualSecondary, ImageView.ScaleType scaleType) {
//无需处理,直接返回原图尺寸
if (maxPrimary == 0 && maxSecondary == 0){
return actualPrimary;
}
//FIT_XY:不需要进行等比缩放,但要求图片铺满矩阵
if (scaleType == ImageView.ScaleType.FIT_XY){
if (maxPrimary == 0){
return actualPrimary;
}
return maxPrimary;
}
// If primary is unspecified, scale primary to match secondary's scaling ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0){
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
//CENTER_CROP:要求等比缩放,且不留空白,不要求图片完全显示
if (scaleType == ImageView.ScaleType.CENTER_CROP){
if ((resized * ratio) < maxSecondary){
resized = (int)(maxSecondary / ratio);
}
return resized;
}
//保证图片完全显示,允许留空白
if ((resized * ratio) > maxSecondary){
resized = (int)(maxSecondary / ratio);
}
return resized;
}
注意FIT_XY和CENTER_CROP两种类型的不同处理
获取图片的最适压缩比:
static int findBestSimpleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio){
n *= 2;
}
return (int)n;
}
如上,保证了其压缩比为2的幂次方数