关于Volley源码解析的文章很多,这里推荐一篇CodeKK的文章——《Volley 源码解析》
—— 注:文章链接已经找不到了,但网上转载很多。
总的来说,Volley的源码比较好理解,而且具有很强的拓展性,下面贴出两张图(流程图,类图)。建议直接看源码,然后再看这两张图,分分钟就懂了。
根据类图,可以发现Volley支持多种类型的请求:
public class StringRequest extends Request<String> {
private Listener mListener;
public StringRequest(int method, String url, Listener listener, ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
public StringRequest(String url, Listener listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
@Override
protected void deliverResponse(String response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
// 主要是这里将网络获取的数据转成String类型
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
// 这里只是将String类型的数据存放在Response对象的一个result字段里
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
}
前提:数据的返回类型必须是json格式的。
public class GsonRequest extends Request<T> {
private Listener mListener;
private final Class clazz;
private final Gson gson = new Gson();
// 这里要传一个实体对象进去
public StringRequest(String url, Class clazz, Listener listener, ErrorListener errorListener) {
super(Method.GET, url, listener, errorListener);
mListener = listener;
this.clazz = clazz;
}
@Override
protected void deliverResponse(T response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
// 先转成String类型的json
String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
// 解析String (关键)
return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}
public class ImageRequest extends Request<Bitmap> {
/** Socket timeout in milliseconds for image requests */
private static final int IMAGE_TIMEOUT_MS = 1000;
/** Default number of retries for image requests */
private static final int IMAGE_MAX_RETRIES = 2;
/** Default backoff multiplier for image requests */
private static final float IMAGE_BACKOFF_MULT = 2f;
private final Response.Listener mListener;
private final Config mDecodeConfig;
private final int mMaxWidth;
private final int mMaxHeight;
private ScaleType mScaleType;
/** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
private static final Object sDecodeLock = new Object();
/**
* Creates a new image request.
*/
public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
ScaleType scaleType, Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(
new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mScaleType = scaleType;
}
/**
* Scales one side of a rectangle to fit aspect ratio.
*/
private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary, ScaleType scaleType) {
// If no dominant value at all, just return the actual.
if ((maxPrimary == 0) && (maxSecondary == 0)) {
return actualPrimary;
}
// If ScaleType.FIT_XY fill the whole rectangle, ignore ratio.
if (scaleType == 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;
// If ScaleType.CENTER_CROP fill the whole rectangle, preserve aspect ratio.
if (scaleType == ScaleType.CENTER_CROP) {
if ((resized * ratio) < maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
if ((resized * ratio) > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
return doParse(response);
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
/**
* 图片的主要处理逻辑(值得参考)
* The real guts of parseNetworkResponse. Broken out for readability.
*/
private Response doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight, mScaleType);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth, mScaleType);
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =
findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap =
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
@Override
protected void deliverResponse(Bitmap response) {
mListener.onResponse(response);
}
/**
* 返回一个inSimpleSize的最佳值
*/
static int findBestSampleSize(
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;
}
}