[关闭]
@flyouting 2014-07-12T14:48:51.000000Z 字数 7679 阅读 4537

Volley源码学习笔记二

Android volley


Request

这里看下网络请求类 Request
先看下类图:
此处输入图片的描述

来看下Request类的重要属性:

  1. public abstract class Request<T> implements Comparable<Request<T>> {
  2. /** 请求方法 目前支持 GET, POST, PUT, and DELETE. */
  3. private final int mMethod;
  4. /** 请求的api地址 */
  5. private final String mUrl;
  6. /** 发生错误的回调接口 */
  7. private final Response.ErrorListener mErrorListener;
  8. /** 请求的序号,相同优先级的请求在请求队列中根据序号来进行排序,序号低的排在队列前面。*/
  9. private Integer mSequence;
  10. /** 该请求所在的请求队列 */
  11. private RequestQueue mRequestQueue;
  12. /** 此请求的网络返回是否需要保存在缓存 */
  13. private boolean mShouldCache = true;
  14. /** 是否能被取消 */
  15. private boolean mCanceled = false;
  16. }

然后是主要方法:

  1. /**
  2. * 返回请求体的字节数组表示。默认实现为返回null,所以如果是POST或PUT请求,子类需要重写这个方法。
  3. *
  4. * @throws AuthFailureError in the event of auth failure
  5. */
  6. public byte[] getBody() throws AuthFailureError {
  7. Map<String, String> params = getParams();
  8. if (params != null && params.size() > 0) {
  9. return encodeParameters(params, getParamsEncoding());
  10. }
  11. return null;
  12. }
  1. /**
  2. * 抽象方法,需要子类实现,用以解析网路返回的原始数据
  3. * 并返回一个特定的响应类型即Response<T>中的T类
  4. * 传回null将会再deliverResponse时不进行分发处理
  5. */
  6. abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
  1. /**
  2. * 用来将解析好的响应结果交付给监听器进行处理
  3. * 传入的参数response需要保证为非空
  4. * 解析失败的response将不会被分发处理
  5. */
  6. abstract protected void deliverResponse(T response);

Request实现Comparable接口来对Request的优先级进行比较,从而决定Request在队列中的顺序。优先级越高,在请求队列中排得越前,相同优先级的序号越低,排得越前。

  1. @Override
  2. public int compareTo(Request<T> other) {
  3. Priority left = this.getPriority();
  4. Priority right = other.getPriority();
  5. // High-priority requests are "lesser" so they are sorted to the front.
  6. // Equal priorities are sorted by sequence number to provide FIFO ordering.
  7. return left == right ?
  8. this.mSequence - other.mSequence :
  9. right.ordinal() - left.ordinal();
  10. }

Requestv派生出三个子类JsonRequestImageRequestClearCacheRequest`。

JsonRequest

JsonRequest<T>也是一个抽象类,可以发送一个Json表示的请求体,并返回一个T类型的响应。

  1. public JsonRequest(String url, String requestBody, Listener<T> listener,
  2. ErrorListener errorListener) {
  3. this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener);
  4. }

默认的方法是GET,除非getPostBody()getPostParams()方法被重写,会默认POST方法。构造方法需要传入的参数API地址,请求体的JSON字符串requestbody,数据成功返回后的回调方法,网络错误的回调方法。

JsonArrayRequestJsonObjectRequest继承自JsonRequest,分别表示返回一个JsonArray响应的请求与返回一个JsonObject响应的请求。

ImageRequest

根据一个URL来请求一个位图Bitmap,看构造函数

  1. public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
  2. Config decodeConfig, Response.ErrorListener errorListener) {
  3. super(Method.GET, url, errorListener);
  4. setRetryPolicy(
  5. new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
  6. mListener = listener;
  7. mDecodeConfig = decodeConfig;
  8. mMaxWidth = maxWidth;
  9. mMaxHeight = maxHeight;
  10. }

mMaxWidth: 解码位图的最大宽度,mMaxHeight:解码位图的最大高度,如果mMaxWidthmMaxHeight都为0,则保持位图的原始尺寸,如果其中一个不为0,则按照原始位图的宽高比进行解码,如果都不为0,则将解码成最适合width * height区域并且保持原始位图宽高比的位图。另外还有个处理解码后的位图的监听器。

ImageRequest的优先级是最低的。

  1. @Override
  2. public Priority getPriority() {
  3. return Priority.LOW;
  4. }

这里需要注意的是,回调是返回的一个bitmap对象。网络数据返回时是byte数组,然后编码成bitmap,并有对宽高进行处理的逻辑,最后将符合要求的bitmap返回。对于普通的图片可以这样做,但是对于特别的格式,比如gif文件,不能返回bitmap,否则就会只返回第一帧,对于有这样需求的地方,需要自己定义一个新的图片获取request类,可以讲byte数组封装成自己需要的类型返回。

  1. private Response<Bitmap> doParse(NetworkResponse response) {
  2. byte[] data = response.data;
  3. BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
  4. Bitmap bitmap = null;
  5. if (mMaxWidth == 0 && mMaxHeight == 0) {
  6. decodeOptions.inPreferredConfig = mDecodeConfig;
  7. bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
  8. } else {
  9. // If we have to resize this image, first get the natural bounds.
  10. decodeOptions.inJustDecodeBounds = true;
  11. BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
  12. int actualWidth = decodeOptions.outWidth;
  13. int actualHeight = decodeOptions.outHeight;
  14. // Then compute the dimensions we would ideally like to decode to.
  15. int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
  16. actualWidth, actualHeight);
  17. int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
  18. actualHeight, actualWidth);
  19. // Decode to the nearest power of two scaling factor.
  20. decodeOptions.inJustDecodeBounds = false;
  21. // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
  22. // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
  23. decodeOptions.inSampleSize =
  24. findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
  25. Bitmap tempBitmap =
  26. BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
  27. // If necessary, scale down to the maximal acceptable size.
  28. if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
  29. tempBitmap.getHeight() > desiredHeight)) {
  30. bitmap = Bitmap.createScaledBitmap(tempBitmap,
  31. desiredWidth, desiredHeight, true);
  32. tempBitmap.recycle();
  33. } else {
  34. bitmap = tempBitmap;
  35. }
  36. }
  37. if (bitmap == null) {
  38. return Response.error(new ParseError(response));
  39. } else {
  40. return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
  41. }
  42. }
  43. }

ClearCacheRequest

一个模拟的用来清理缓存的请求

  1. public ClearCacheRequest(Cache cache, Runnable callback) {
  2. super(Method.GET, null, null);
  3. mCache = cache;
  4. mCallback = callback;
  5. }

mCache:需要清理的缓存,mCallback:缓存清理完后在主线程中被调用的回调接口。parseNetworkResponsedeliverResponse都是空实现,因为这是一个模拟的Request,没有实际的网络请求。

ClearCacheRequest的优先级是最高的

  1. @Override
  2. public Priority getPriority() {
  3. return Priority.IMMEDIATE;
  4. }

在平时用的时候,我们可以写一些request的使用基类,因为有时候我们需要在每个请求里添加固定的几项参数,或者header。在基类里复写getHeaders()getParams()方法。

cache

这里看看request的访问缓存机制。

request里用到的缓存对象是在RequestQueue的构造方法里传入的一个新创建的DiskBasedCache

volley中定义了一个Cache接口,并在此接口中定义了再cache中包存对象的数据结构Entry

  1. public interface Cache {
  2. public Entry get(String key);
  3. public void put(String key, Entry entry);
  4. public void initialize();
  5. public void invalidate(String key, boolean fullExpire);
  6. public void remove(String key);
  7. public void clear();
  8. public static class Entry {
  9. /** The data returned from cache. */
  10. public byte[] data;
  11. /** Date of this response as reported by the server. */
  12. public long serverDate;
  13. /** TTL for this record. */
  14. public long ttl;
  15. /** Soft TTL for this record. */
  16. public long softTtl;
  17. /** Immutable response headers as received from server; must be non-null. */
  18. public Map<String, String> responseHeaders = Collections.emptyMap();
  19. /** True if the entry is expired. */
  20. public boolean isExpired() {
  21. return this.ttl < System.currentTimeMillis();
  22. }
  23. /** True if a refresh is needed from the original data source. */
  24. public boolean refreshNeeded() {
  25. return this.softTtl < System.currentTimeMillis();
  26. }
  27. }
  28. }

由代码可以看到接口中定义了常用的获取,添加,删除,清空方法。Entry的数据结构包含了字节数组对象,服务器时间,数据在本地生成的时间,软生成时间,这两个时间用于判断是否过期以及是否需要刷新数据。

DiskBasedCache实现了Cache接口,重写了接口中的几个方法。先看一下添加进缓冲区对象:

  1. @Override
  2. public synchronized void put(String key, Entry entry) {
  3. pruneIfNeeded(entry.data.length);
  4. File file = getFileForKey(key);
  5. try {
  6. FileOutputStream fos = new FileOutputStream(file);
  7. CacheHeader e = new CacheHeader(key, entry);
  8. boolean success = e.writeHeader(fos);
  9. if (!success) {
  10. fos.close();
  11. VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
  12. throw new IOException();
  13. }
  14. fos.write(entry.data);
  15. fos.close();
  16. putEntry(key, e);
  17. return;
  18. } catch (IOException e) {
  19. }
  20. boolean deleted = file.delete();
  21. if (!deleted) {
  22. VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
  23. }
  24. }

首先是检查对象的大小,当缓冲区空间足够新对象的加入时就直接添加进来,否则会删除部分对象,一直到新对象添加进来后还会有10%的空间剩余时为止。文件引用以LinkHashMap保存,默认的LRU策略。添加时,首先以URL为key,经过个文本转换后,以转换后的文本为名称,获取一个file对象。首先向这个对象写入缓存的头文件,然后是真正有用的网络返回数据。最后是当前内存占有量数值的更新,这里需要注意的是真实数据被写入磁盘文件后,在内存中维护的应用,存的只是数据的相关属性。

再看怎么从缓冲区获取数据:

  1. @Override
  2. public synchronized Entry get(String key) {
  3. CacheHeader entry = mEntries.get(key);
  4. // if the entry does not exist, return.
  5. if (entry == null) {
  6. return null;
  7. }
  8. File file = getFileForKey(key);
  9. CountingInputStream cis = null;
  10. try {
  11. cis = new CountingInputStream(new FileInputStream(file));
  12. CacheHeader.readHeader(cis); // eat header
  13. byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
  14. return entry.toCacheEntry(data);
  15. } catch (IOException e) {
  16. VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
  17. remove(key);
  18. return null;
  19. } finally {
  20. if (cis != null) {
  21. try {
  22. cis.close();
  23. } catch (IOException ioe) {
  24. return null;
  25. }
  26. }
  27. }
  28. }

首先根据key从缓存中获取CacheHeader,这里包含了真实数据的各种属性,然后再拿到存有数据的磁盘文件,先从文件中把头文件过滤,然后读取剩下的真正的数据部分到CacheHeader对象中,这样,一个完整的CacheHeader对象得到了,可以返回了。

由此可见,Volley中的第二层缓冲区即文件缓冲区对外不可见,是因为RequestQueue的构造方法直接生成了一个新的DiskBasedCache,如果开放出这个接口,可以传入自定义的DiskCache,只要实现cache接口,重写必要的方法就行,至于存储对象的数据结构,就看具体需求了。那些头文件中的数据属性信息其实也可以不存,直接把数据存成文件。比如在gif文件的支持中,我们需要在文件缓冲区中直接拿到gif的文件对象。这就需要在这一层次中添加接口了。

参考资料:
http://www.cnblogs.com/spec-dog/p/3821417.html
作者:flyouting

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