@flyouting
2014-07-16T17:22:37.000000Z
字数 5422
阅读 6069
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.html
stack = 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被复用,查找是否有旧的图片加载请求绑定到这个ImageView
ImageContainer 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存入对应的ImageView
imageView.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;
}
@Override
public Priority getPriority() {
return Priority.LOW;
}
@Override
protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
}
@Override
protected 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 background
final long cacheHitButRefreshed = 30 * 60 * 1000;
// in 24 hours this cache entry expires completely
final 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