只是想说,自己太懒了。太久没更新博客。今后有时间就要来总结下。
年初的时候,写过一篇博客,Android网络通信框架Volley初体验,这篇可真的是初体验。。。可以当作对volley初步认识,但是在实际应用中,却不实用。在本片博客中,我将对其进行补充,以达到volley可以在实际项目中应用。Demo在博客最下方。
本篇博客主要补充内容如下:
- 初始化volley -- 采用单例模式,添加线程安全,防止oom和网络连接错误;
- 自定义request -- 添加对cookie的管理,添加返回值为string、json的自定义request;添加文件上传的自定义request;
- 重新编写NetworkUtils类,修改request和tag之间的对应关系,并在请求结束后,关闭该请求;
一、初始化Volley查询对象RequestQueue
在之前的博客中,RequestQueue的初始化方式的写法为:RequestQueue mRequestQueue = Volley.newRequestQueue(sInstance, new HttpClientStack(mHttpClient));这样写当然没问题,但是我们在后面需要上传文件,头文件信息中Content-Type应为multipart/form-data,而不是默认的application/x-www-form-urlencoded。而MultiPartStack只支持multipart/form-data格式。所以,在这里需要自定义一个HttpStack,使其同时支持application/x-www-form-urlencoded和multipart/form-data两种传输格式。初始化的代码如下。由于CustomHttpStack的代码过长,仅贴出关键代码,完整代码请参考Demo。
/**
* @return Volley 查询队列
*/
public RequestQueue getRequestQueue(String tag) {
if (mRequestQueue == null) {
Log.i("RequestQueue Initialize",
"mRequestQueue is Created by getRequestQueue() When "
+ tag);
DefaultHttpClient mDefaultHttpClient = new DefaultHttpClient();
HttpStack httpStack = new CustomHttpStack(mDefaultHttpClient);
mRequestQueue = Volley.newRequestQueue(mAppContext, httpStack);
}
return mRequestQueue;
}
CustomHttpStack部分代码:
/**
* 若该Request为CustomMultiPartStringRequest,则设置他的头文件信息Content-Type为multipart/
* form-data否则application/* x-www-form-urlencoded
*
* @param httpRequest
* @param request
* @throws AuthFailureError
*/
public static void setEntityIfNonEmptyBody(
HttpEntityEnclosingRequestBase httpRequest, Request<?> request)
throws AuthFailureError {
if (request instanceof IMultiPartRequest) {
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(org.apache.http.entity.mime.HttpMultipartMode.BROWSER_COMPATIBLE);
Map<String, File> fileUpload = ((IMultiPartRequest) request)
.getFileUploads();
for (Map.Entry<String, File> entry : fileUpload.entrySet()) {
builder.addPart(((String) entry.getKey()), new FileBody(
(File) entry.getValue()));
}
ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE,
HTTP.UTF_8);
Map<String, String> stringUpload = ((IMultiPartRequest) request)
.getStringUploads();
for (Map.Entry<String, String> entry : stringUpload.entrySet()) {
try {
builder.addPart(((String) entry.getKey()),
new org.apache.http.entity.mime.content.StringBody(
(String) entry.getValue(), contentType));
} catch (Exception e) {
e.printStackTrace();
}
}
httpRequest.setEntity(builder.build());
} else {
byte[] body = request.getBody();
if (body != null) {
HttpEntity entity = new ByteArrayEntity(body);
httpRequest.setEntity(entity);
}
}
}
在上述代码中,由于使用自定义的HttpStack,使该RequestQueue支持普通数据以及文件上传。
但是在实际应用中,会发现这样一个问题。情景描述:点击按钮,同时执行两个网络请求。这个时候,会报错,错误信息为:Make sure to release the connection before allocating another one。在下一次请求前,请先释放上一个网络连接。这个问题困扰了好久,后来是在Stack Overflow中,找到解决办法,为HttpClient添加线程安全的管理器ThreadSafeClientConnManager。最后RequestQueue初始化的代码为:
/**
* @return Volley 查询队列
*/
public RequestQueue getRequestQueue(String tag) {
if (mRequestQueue == null) {
Log.i("RequestQueue Initialize",
"mRequestQueue is Created by getRequestQueue() When "
+ tag);
DefaultHttpClient mDefaultHttpClient = new DefaultHttpClient();
ClientConnectionManager mClientConnectionManager = mDefaultHttpClient
.getConnectionManager();
HttpParams mHttpParams = mDefaultHttpClient.getParams();
ThreadSafeClientConnManager mThreadSafeClientConnManager = new ThreadSafeClientConnManager(
mHttpParams, mClientConnectionManager.getSchemeRegistry());
mDefaultHttpClient = new DefaultHttpClient(
mThreadSafeClientConnManager, mHttpParams);
HttpStack httpStack = new CustomHttpStack(mDefaultHttpClient);
mRequestQueue = Volley.newRequestQueue(mAppContext, httpStack);
}
return mRequestQueue;
}
通过上述代码,即可完成RequestQueue的初始化。由于采用单例模式,避免了该死的oom。
二、自定义Request
在该部分,自定义了3个Request类,CustomStringRequest、CustomJsonObjectRequest、CustomMultiPartStringRequest,在其中添加了对Cookie的管理。前两个类中,是直接以Volley提供的StringRequest和JsonObjectRequest为基础,实现普通数据的Get、Post请求;第三个类继承自Request<String>,实现了文件上传操作。
首先是对Cookie的管理,这里保存cookie的办法是将其存在sharepreference文件中。保存cookie代码如下:
/**
* 保存cookies
*
* @param headers
* Response Headers.
*/
public final void checkSessionCookie(Map<String, String> headers) {
if (headers.containsKey(SET_COOKIE_KEY)
&& headers.get(SET_COOKIE_KEY).startsWith(SESSION_COOKIE)) {
String cookie = headers.get(SET_COOKIE_KEY);
if (cookie.length() > 0) {
// String[] splitCookie = cookie.split(";");
// String[] splitSessionId = splitCookie[0].split("=");
// cookie = splitSessionId[1];
Editor prefEditor = mSharePreferences.edit();
prefEditor.putString(SESSION_COOKIE, cookie);
prefEditor.commit();
}
}
}
/**
* 添加cookies
*
* @param headers
*/
public final void addSessionCookie(Map<String, String> headers) {
String sessionId = mSharePreferences.getString(SESSION_COOKIE, "");
if (sessionId.length() > 0) {
// StringBuilder builder = new StringBuilder();
// builder.append(SESSION_COOKIE);
// builder.append("=");
// builder.append(sessionId);
// if (headers.containsKey(COOKIE_KEY)) {
// builder.append("; ");
// builder.append(headers.get(COOKIE_KEY));
// }
headers.put(SET_COOKIE_KEY, sessionId);
}
}
在自定义类中,调用方式为:
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
VolleyUtils.getInstance().checkSessionCookie(response.headers);
String str = null;
try {
str = new String(response.data, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return Response.success(str,
HttpHeaderParser.parseCacheHeaders(response));
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
if (headers == null || headers.equals(Collections.emptyMap())) {
headers = new HashMap<String, String>();
}
VolleyUtils.getInstance().addSessionCookie(headers);
return headers;
}
在发起请求前,会调用getHeaders方法,将cookie添加到头文件中。获得服务器结果之后,再将cookie信息保存在文件中。头文件的写入,请参考CustomHttpStack.setEntityIfNonEmptyBody()方法。
三、重新编写NetworkUtils类
为了确保每一个request对应一个tag,在这里将之前的写法修改如下,并在获得请求结果之后,将该request手动取消掉。例如发起一个返回值为String的Get请求,可以写为:
/**
* Get 请求,返回值格式为String
*
* @param mContext
* @param mTag
* @param url
* @param paramsForGet
* @param success
* @param error
*/
public static void getStringForGet(Context mContext, final String mTag,
String url, HashMap<String, String> paramsForGet,
final Listener<String> success, final ErrorListener error) {
try {
if (!isConnected(mContext)) {
Toast.makeText(mContext, "网络不可用,请检查网络连接!!~", Toast.LENGTH_LONG)
.show();
return;
}
String realUrl = NetworkUtil.getRequestUrl(url, paramsForGet);
CustomStringRequest sReq = new CustomStringRequest(Method.GET,
realUrl, new Listener<String>() {
@Override
public void onResponse(String response) {
success.onResponse(response);
cancelRequest(mTag);
}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError e) {
error.onErrorResponse(e);
cancelRequest(mTag);
}
});
getVolleyUtilsInstance().addToRequestQueue(sReq, mTag);
} catch (Exception e) {
e.printStackTrace();
}
}
通过对以上几个部分的修改,使volley可以在项目中使用。上述黏贴了部分代码,完整的代码,请下载Demo。Demo在使用的时候,请自行添加url和params。由于本人技术有限,代码中可能有些地方写的不合理,还请朋友们指出,共同进步。
源码下载:Volley_NetworkTools