@flyouting
2014-07-12T14:48:51.000000Z
字数 7679
阅读 4537
Android
volley
这里看下网络请求类 Request
。
先看下类图:
来看下Request类的重要属性:
public abstract class Request<T> implements Comparable<Request<T>> {
/** 请求方法 目前支持 GET, POST, PUT, and DELETE. */
private final int mMethod;
/** 请求的api地址 */
private final String mUrl;
/** 发生错误的回调接口 */
private final Response.ErrorListener mErrorListener;
/** 请求的序号,相同优先级的请求在请求队列中根据序号来进行排序,序号低的排在队列前面。*/
private Integer mSequence;
/** 该请求所在的请求队列 */
private RequestQueue mRequestQueue;
/** 此请求的网络返回是否需要保存在缓存 */
private boolean mShouldCache = true;
/** 是否能被取消 */
private boolean mCanceled = false;
}
然后是主要方法:
/**
* 返回请求体的字节数组表示。默认实现为返回null,所以如果是POST或PUT请求,子类需要重写这个方法。
*
* @throws AuthFailureError in the event of auth failure
*/
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
/**
* 抽象方法,需要子类实现,用以解析网路返回的原始数据
* 并返回一个特定的响应类型即Response<T>中的T类
* 传回null将会再deliverResponse时不进行分发处理
*/
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
/**
* 用来将解析好的响应结果交付给监听器进行处理
* 传入的参数response需要保证为非空
* 解析失败的response将不会被分发处理
*/
abstract protected void deliverResponse(T response);
Request
实现Comparable
接口来对Request
的优先级进行比较,从而决定Request
在队列中的顺序。优先级越高,在请求队列中排得越前,相同优先级的序号越低,排得越前。
@Override
public int compareTo(Request<T> other) {
Priority left = this.getPriority();
Priority right = other.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 - other.mSequence :
right.ordinal() - left.ordinal();
}
Requestv派生出三个子类
JsonRequest、
ImageRequest、
ClearCacheRequest`。
JsonRequest<T>
也是一个抽象类,可以发送一个Json
表示的请求体,并返回一个T类型的响应。
public JsonRequest(String url, String requestBody, Listener<T> listener,
ErrorListener errorListener) {
this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener);
}
默认的方法是GET,除非getPostBody()
和getPostParams()
方法被重写,会默认POST
方法。构造方法需要传入的参数API地址,请求体的JSON
字符串requestbody
,数据成功返回后的回调方法,网络错误的回调方法。
JsonArrayRequest
、JsonObjectRequest
继承自JsonRequest
,分别表示返回一个JsonArray
响应的请求与返回一个JsonObject
响应的请求。
根据一个URL来请求一个位图Bitmap
,看构造函数
public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
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;
}
mMaxWidth
: 解码位图的最大宽度,mMaxHeight
:解码位图的最大高度,如果mMaxWidth
,mMaxHeight
都为0,则保持位图的原始尺寸,如果其中一个不为0,则按照原始位图的宽高比进行解码,如果都不为0,则将解码成最适合width * height区域并且保持原始位图宽高比的位图。另外还有个处理解码后的位图的监听器。
ImageRequest
的优先级是最低的。
@Override
public Priority getPriority() {
return Priority.LOW;
}
这里需要注意的是,回调是返回的一个bitmap
对象。网络数据返回时是byte数组,然后编码成bitmap
,并有对宽高进行处理的逻辑,最后将符合要求的bitmap
返回。对于普通的图片可以这样做,但是对于特别的格式,比如gif文件,不能返回bitmap,否则就会只返回第一帧,对于有这样需求的地方,需要自己定义一个新的图片获取request
类,可以讲byte数组封装成自己需要的类型返回。
private Response<Bitmap> 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);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
// 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));
}
}
}
一个模拟的用来清理缓存的请求
public ClearCacheRequest(Cache cache, Runnable callback) {
super(Method.GET, null, null);
mCache = cache;
mCallback = callback;
}
mCache
:需要清理的缓存,mCallback
:缓存清理完后在主线程中被调用的回调接口。parseNetworkResponse
与deliverResponse
都是空实现,因为这是一个模拟的Request
,没有实际的网络请求。
ClearCacheRequest
的优先级是最高的
@Override
public Priority getPriority() {
return Priority.IMMEDIATE;
}
在平时用的时候,我们可以写一些request
的使用基类,因为有时候我们需要在每个请求里添加固定的几项参数,或者header
。在基类里复写getHeaders()
,getParams()
方法。
这里看看request
的访问缓存机制。
request
里用到的缓存对象是在RequestQueue
的构造方法里传入的一个新创建的DiskBasedCache
。
在volley
中定义了一个Cache
接口,并在此接口中定义了再cache
中包存对象的数据结构Entry
。
public interface Cache {
public Entry get(String key);
public void put(String key, Entry entry);
public void initialize();
public void invalidate(String key, boolean fullExpire);
public void remove(String key);
public void clear();
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** Date of this response as reported by the server. */
public long serverDate;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
由代码可以看到接口中定义了常用的获取,添加,删除,清空方法。Entry
的数据结构包含了字节数组对象,服务器时间,数据在本地生成的时间,软生成时间,这两个时间用于判断是否过期以及是否需要刷新数据。
DiskBasedCache
实现了Cache接口,重写了接口中的几个方法。先看一下添加进缓冲区对象:
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
FileOutputStream fos = new FileOutputStream(file);
CacheHeader e = new CacheHeader(key, entry);
boolean success = e.writeHeader(fos);
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
首先是检查对象的大小,当缓冲区空间足够新对象的加入时就直接添加进来,否则会删除部分对象,一直到新对象添加进来后还会有10%的空间剩余时为止。文件引用以LinkHashMap
保存,默认的LRU
策略。添加时,首先以URL为key,经过个文本转换后,以转换后的文本为名称,获取一个file
对象。首先向这个对象写入缓存的头文件,然后是真正有用的网络返回数据。最后是当前内存占有量数值的更新,这里需要注意的是真实数据被写入磁盘文件后,在内存中维护的应用,存的只是数据的相关属性。
再看怎么从缓冲区获取数据:
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = null;
try {
cis = new CountingInputStream(new FileInputStream(file));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
首先根据key从缓存中获取CacheHeader
,这里包含了真实数据的各种属性,然后再拿到存有数据的磁盘文件,先从文件中把头文件过滤,然后读取剩下的真正的数据部分到CacheHeader
对象中,这样,一个完整的CacheHeader
对象得到了,可以返回了。
由此可见,Volley
中的第二层缓冲区即文件缓冲区对外不可见,是因为RequestQueue
的构造方法直接生成了一个新的DiskBasedCache
,如果开放出这个接口,可以传入自定义的DiskCache
,只要实现cache
接口,重写必要的方法就行,至于存储对象的数据结构,就看具体需求了。那些头文件中的数据属性信息其实也可以不存,直接把数据存成文件。比如在gif文件的支持中,我们需要在文件缓冲区中直接拿到gif的文件对象。这就需要在这一层次中添加接口了。
参考资料:
http://www.cnblogs.com/spec-dog/p/3821417.html
作者:flyouting