[关闭]
@flyouting 2014-07-16T17:22:37.000000Z 字数 5422 阅读 6069

Volley框架的增强一

Android volley


Volley框架整体是很适合快速开发时使用的,但是在某些项目中,纠结于一些需求,Volley框架就需要自己动手改一改了。

开放网络请求dispatcher线程数量

RequestQueue的构造函数中是可以定义网络请求线程池的数量的,但是在Volley中的newRequestQueue()方法中却没有线程数量的参数可以传入。这里可以添加一个。顺便可以在这里添加自定义L2缓冲区的参数。改动后代码如下:

  1. /**
  2. * 创建一个默认的请求队列实例,启动worker线程池 Creates a default instance of the worker pool
  3. * and calls {@link RequestQueue#start()} on it.
  4. *
  5. * @param context A {@link Context} 用于创建 cache 文件夹.
  6. * @param stack An {@link HttpStack} 用于网络, 默认为null.
  7. * @param threadPoolSize 线程池数量
  8. * @param cache L2级磁盘缓冲区
  9. * @return A started {@link RequestQueue} instance.
  10. */
  11. public static RequestQueue newRequestQueue(Context context, HttpStack stack, Cache cache,
  12. int threadPoolSize) {
  13. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  14. String userAgent = "volley/0";
  15. try {
  16. String packageName = context.getPackageName();
  17. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  18. userAgent = packageName + "/" + info.versionCode;
  19. } catch (NameNotFoundException e) {
  20. }
  21. if (stack == null) {
  22. if (Build.VERSION.SDK_INT >= 9) {
  23. stack = new HurlStack();
  24. } else {
  25. // Gingerbread之前的版本, 采用HttpUrlConnection 不太合适.
  26. // 原因参考这里:
  27. // http://android-developers.blogspot.com/2011/09/androids-http-clients.html
  28. stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
  29. }
  30. }
  31. Network network = new BasicNetwork(stack);
  32. RequestQueue queue;
  33. if (threadPoolSize <= 0) {
  34. queue = new RequestQueue(cache == null ? new DiskBasedCache(cacheDir) : cache, network);
  35. } else {
  36. queue = new RequestQueue(cache == null ? new DiskBasedCache(cacheDir) : cache, network,
  37. threadPoolSize);
  38. }
  39. queue.start();
  40. return queue;
  41. }

修改网络超时时间

框架中默认的超时时间是2500毫秒,在中国这种环境中简直无法适应,所以稍微调整高点。10秒就还可以了,正常的网络这个值就行,如果不行也可以调整为30秒。修改文件为DefaultRetryPolicy

  1. /** 默认的超时时间 */
  2. public static final int DEFAULT_TIMEOUT_MS = 10000;

定义一个优化后的ImageLoader

实际开发中一定会处理这样一个问题,ListView的每个Item都包含一个图片,在我们快速滑动ListView的时候,每个图片获取的逻辑操作都被启动了,但是由于Item的复用机制,在一个Item被复用的时候,这个item调起的图片请求线程可能还正在运行或者在排队,这时候我们需要把这个线程取消掉以节约资源。

Volley框架中有个NetworkImageView,在类里边包含了一个ImageContainer,当view被复用的时候,发现这个ImageContainer存在,我们就可以从这里获取之前的url,去队列里取消掉。但是这样的逻辑在默认的ImageLoader里是不存在的,我们需要自己添加一个。

主要逻辑代码:

  1. public ImageContainer get(String requestUrl, ImageView imageView, Drawable placeHolder,
  2. int maxWidth, int maxHeight) {
  3. // 当这个view被复用,查找是否有旧的图片加载请求绑定到这个ImageView
  4. ImageContainer imageContainer = imageView.getTag() != null
  5. && imageView.getTag() instanceof ImageContainer ? (ImageContainer) imageView
  6. .getTag() : null;
  7. // 找到之前请求的图片地址
  8. String recycledImageUrl = imageContainer != null ? imageContainer.getRequestUrl() : null;
  9. // 如果新的图片地址为null,或者跟之前的请求地址不同
  10. if (requestUrl == null || !requestUrl.equals(recycledImageUrl)) {
  11. if (imageContainer != null) {
  12. // 取消之前的请求
  13. imageContainer.cancelRequest();
  14. imageView.setTag(null);
  15. }
  16. if (requestUrl != null) {
  17. // 启动一个新的Request去请求新的地址
  18. imageContainer = get(requestUrl,
  19. getImageListener(mResources, imageView, placeHolder, mFadeInImage),
  20. maxWidth, maxHeight);
  21. // 把Request作为tag存入对应的ImageView
  22. imageView.setTag(imageContainer);
  23. } else {
  24. imageView.setImageDrawable(placeHolder);
  25. imageView.setTag(null);
  26. }
  27. }
  28. return imageContainer;
  29. }

添加一个返回字节数据的ImageRequest

Volley中的ImageRequest有默认的数据解析,会转换成符合要求的bitmap返回,项目中有可能是gif类图片,如果转换成bitmap,会只有第一帧。这时我们需要拿到返回的数据,转换成我们需要的类型。而不是统统的都是bitmap。

  1. /**
  2. * 通过一个url获取图片数据,返回值为byte[]
  3. *
  4. * @author coffee
  5. */
  6. public class OtherImageRequest extends Request<byte[]> {
  7. /** 请求超时时间 */
  8. private static final int IMAGE_TIMEOUT_MS = 2000;
  9. /** 重试次数 */
  10. private static final int IMAGE_MAX_RETRIES = 2;
  11. /** 超时时间的乘数,重试时才用到 */
  12. private static final float IMAGE_BACKOFF_MULT = 2f;
  13. private final Response.Listener<byte[]> mListener;
  14. /**
  15. * Creates a new image request
  16. *
  17. * @param url URL of the image
  18. * @param listener Listener to receive
  19. * @param errorListener Error listener, or null to ignore errors
  20. */
  21. public OtherImageRequest(String url, Response.Listener<byte[]> listener,
  22. Response.ErrorListener errorListener) {
  23. super(Method.GET, url, errorListener);
  24. setRetryPolicy(new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES,
  25. IMAGE_BACKOFF_MULT));
  26. mListener = listener;
  27. }
  28. @Override
  29. public Priority getPriority() {
  30. return Priority.LOW;
  31. }
  32. @Override
  33. protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
  34. return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
  35. }
  36. @Override
  37. protected void deliverResponse(byte[] response) {
  38. mListener.onResponse(response);
  39. }
  40. }

给图片添加有效期

在图片获取中,一般服务端是没有cache-control的,所有图片在文件缓冲中的有效期往往是没有设置的,也就代表这没有这层缓冲。所以需要给图片缓冲加个有效期。在HttpHeaderParser文件中有个parseCacheHeaders方法是用来解析头文件,设置有效期的。所以,这里可以添加一个方法,专门针对ImageRequest来处理有效期。
代码如下:

  1. /**
  2. * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. This will
  3. * ignore the http server's Cache-Control.
  4. *
  5. * @param response The network response to parse headers from
  6. * @return a cache entry for the given response, or null if the response is
  7. * not cacheable.
  8. */
  9. public static Cache.Entry parseImageCacheHeaders(NetworkResponse response) {
  10. long now = System.currentTimeMillis();
  11. Map<String, String> headers = response.headers;
  12. long serverDate = 0;
  13. String serverEtag = null;
  14. String headerValue;
  15. headerValue = headers.get("Date");
  16. if (headerValue != null) {
  17. serverDate = parseDateAsEpoch(headerValue);
  18. }
  19. serverEtag = headers.get("ETag");
  20. // in 30 minutes cache will be hit, but also refreshed on background
  21. final long cacheHitButRefreshed = 30 * 60 * 1000;
  22. // in 24 hours this cache entry expires completely
  23. final long cacheExpired = 24 * 60 * 60 * 1000;
  24. final long softExpire = now + cacheHitButRefreshed;
  25. final long ttl = now + cacheExpired;
  26. Cache.Entry entry = new Cache.Entry();
  27. entry.data = response.data;
  28. entry.etag = serverEtag;
  29. entry.softTtl = softExpire;
  30. entry.ttl = ttl;
  31. entry.serverDate = serverDate;
  32. entry.responseHeaders = headers;
  33. return entry;
  34. }

设定30分钟后再次访问需要刷新,24小时文件完全过期,需要重新联网获取。
然后在ImageRequest中解析header的地方替换方法即可

  1. if (bitmap == null) {
  2. return Response.error(new ParseError(response));
  3. } else {
  4. return Response.success(bitmap, HttpHeaderParser.parseImageCacheHeaders(response));
  5. }

之后的工作有添加支持gzip的网络访问,添加自定义的L2缓冲区,添加网络请求进度。

微博:flyouting

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