@flyouting
        
        2014-07-16T09:22:37.000000Z
        字数 5422
        阅读 6396
    Android volley
Volley框架整体是很适合快速开发时使用的,但是在某些项目中,纠结于一些需求,Volley框架就需要自己动手改一改了。
RequestQueue的构造函数中是可以定义网络请求线程池的数量的,但是在Volley中的newRequestQueue()方法中却没有线程数量的参数可以传入。这里可以添加一个。顺便可以在这里添加自定义L2缓冲区的参数。改动后代码如下:
/*** 创建一个默认的请求队列实例,启动worker线程池 Creates a default instance of the worker pool* and calls {@link RequestQueue#start()} on it.** @param context A {@link Context} 用于创建 cache 文件夹.* @param stack An {@link HttpStack} 用于网络, 默认为null.* @param threadPoolSize 线程池数量* @param cache L2级磁盘缓冲区* @return A started {@link RequestQueue} instance.*/public static RequestQueue newRequestQueue(Context context, HttpStack stack, Cache cache,int threadPoolSize) {File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);String userAgent = "volley/0";try {String packageName = context.getPackageName();PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);userAgent = packageName + "/" + info.versionCode;} catch (NameNotFoundException e) {}if (stack == null) {if (Build.VERSION.SDK_INT >= 9) {stack = new HurlStack();} else {// Gingerbread之前的版本, 采用HttpUrlConnection 不太合适.// 原因参考这里:// http://android-developers.blogspot.com/2011/09/androids-http-clients.htmlstack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));}}Network network = new BasicNetwork(stack);RequestQueue queue;if (threadPoolSize <= 0) {queue = new RequestQueue(cache == null ? new DiskBasedCache(cacheDir) : cache, network);} else {queue = new RequestQueue(cache == null ? new DiskBasedCache(cacheDir) : cache, network,threadPoolSize);}queue.start();return queue;}
框架中默认的超时时间是2500毫秒,在中国这种环境中简直无法适应,所以稍微调整高点。10秒就还可以了,正常的网络这个值就行,如果不行也可以调整为30秒。修改文件为DefaultRetryPolicy
/** 默认的超时时间 */public static final int DEFAULT_TIMEOUT_MS = 10000;
实际开发中一定会处理这样一个问题,ListView的每个Item都包含一个图片,在我们快速滑动ListView的时候,每个图片获取的逻辑操作都被启动了,但是由于Item的复用机制,在一个Item被复用的时候,这个item调起的图片请求线程可能还正在运行或者在排队,这时候我们需要把这个线程取消掉以节约资源。
Volley框架中有个NetworkImageView,在类里边包含了一个ImageContainer,当view被复用的时候,发现这个ImageContainer存在,我们就可以从这里获取之前的url,去队列里取消掉。但是这样的逻辑在默认的ImageLoader里是不存在的,我们需要自己添加一个。
主要逻辑代码:
public ImageContainer get(String requestUrl, ImageView imageView, Drawable placeHolder,int maxWidth, int maxHeight) {// 当这个view被复用,查找是否有旧的图片加载请求绑定到这个ImageViewImageContainer imageContainer = imageView.getTag() != null&& imageView.getTag() instanceof ImageContainer ? (ImageContainer) imageView.getTag() : null;// 找到之前请求的图片地址String recycledImageUrl = imageContainer != null ? imageContainer.getRequestUrl() : null;// 如果新的图片地址为null,或者跟之前的请求地址不同if (requestUrl == null || !requestUrl.equals(recycledImageUrl)) {if (imageContainer != null) {// 取消之前的请求imageContainer.cancelRequest();imageView.setTag(null);}if (requestUrl != null) {// 启动一个新的Request去请求新的地址imageContainer = get(requestUrl,getImageListener(mResources, imageView, placeHolder, mFadeInImage),maxWidth, maxHeight);// 把Request作为tag存入对应的ImageViewimageView.setTag(imageContainer);} else {imageView.setImageDrawable(placeHolder);imageView.setTag(null);}}return imageContainer;}
Volley中的ImageRequest有默认的数据解析,会转换成符合要求的bitmap返回,项目中有可能是gif类图片,如果转换成bitmap,会只有第一帧。这时我们需要拿到返回的数据,转换成我们需要的类型。而不是统统的都是bitmap。
/*** 通过一个url获取图片数据,返回值为byte[]** @author coffee*/public class OtherImageRequest extends Request<byte[]> {/** 请求超时时间 */private static final int IMAGE_TIMEOUT_MS = 2000;/** 重试次数 */private static final int IMAGE_MAX_RETRIES = 2;/** 超时时间的乘数,重试时才用到 */private static final float IMAGE_BACKOFF_MULT = 2f;private final Response.Listener<byte[]> mListener;/*** Creates a new image request** @param url URL of the image* @param listener Listener to receive* @param errorListener Error listener, or null to ignore errors*/public OtherImageRequest(String url, Response.Listener<byte[]> listener,Response.ErrorListener errorListener) {super(Method.GET, url, errorListener);setRetryPolicy(new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES,IMAGE_BACKOFF_MULT));mListener = listener;}@Overridepublic Priority getPriority() {return Priority.LOW;}@Overrideprotected Response<byte[]> parseNetworkResponse(NetworkResponse response) {return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));}@Overrideprotected void deliverResponse(byte[] response) {mListener.onResponse(response);}}
在图片获取中,一般服务端是没有cache-control的,所有图片在文件缓冲中的有效期往往是没有设置的,也就代表这没有这层缓冲。所以需要给图片缓冲加个有效期。在HttpHeaderParser文件中有个parseCacheHeaders方法是用来解析头文件,设置有效期的。所以,这里可以添加一个方法,专门针对ImageRequest来处理有效期。 
代码如下:
/*** Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. This will* ignore the http server's Cache-Control.** @param response The network response to parse headers from* @return a cache entry for the given response, or null if the response is* not cacheable.*/public static Cache.Entry parseImageCacheHeaders(NetworkResponse response) {long now = System.currentTimeMillis();Map<String, String> headers = response.headers;long serverDate = 0;String serverEtag = null;String headerValue;headerValue = headers.get("Date");if (headerValue != null) {serverDate = parseDateAsEpoch(headerValue);}serverEtag = headers.get("ETag");// in 30 minutes cache will be hit, but also refreshed on backgroundfinal long cacheHitButRefreshed = 30 * 60 * 1000;// in 24 hours this cache entry expires completelyfinal long cacheExpired = 24 * 60 * 60 * 1000;final long softExpire = now + cacheHitButRefreshed;final long ttl = now + cacheExpired;Cache.Entry entry = new Cache.Entry();entry.data = response.data;entry.etag = serverEtag;entry.softTtl = softExpire;entry.ttl = ttl;entry.serverDate = serverDate;entry.responseHeaders = headers;return entry;}
设定30分钟后再次访问需要刷新,24小时文件完全过期,需要重新联网获取。 
然后在ImageRequest中解析header的地方替换方法即可
if (bitmap == null) {return Response.error(new ParseError(response));} else {return Response.success(bitmap, HttpHeaderParser.parseImageCacheHeaders(response));}
之后的工作有添加支持gzip的网络访问,添加自定义的L2缓冲区,添加网络请求进度。
微博:flyouting