本篇博客内容:
介绍Volley的基本知识点
分析StringRequest,JsonRequest的源码
自定义带header(包含coockie),Json参数,Gson解析的GsonRequest
Volley是android 官方团队开发的框架,不仅集成Universal-image-loader框架的图片加载特点,还集成android-async-http 框架的请求数据的特点。
先来了解下Volley框架的优点和缺点:
Volley有以下优点:
自动调度请求(缓存线程自动将缓存队列中请求调度给网络线程执行)。
高并发执行网络请求(4个网络线程并发执行请求)
默认情况下,网络线程会通过DiskBasedCache 类将解析后的响应数据缓存在磁盘中。 在使用ImageRequest的时候,自定义LruCache子类来实现配置内存缓存。
支持指定请求的优先级
能有随时取消单个请求,或者带有相同标识的所有请求
异步执行网络操作,然后回调到主线程更新UI.
(最重要的是)可以按项目需求来各种定制,例如定制请求的重试策略,自定义Request(带json,Gson解析的GsonRequest,文件上传的MultiPartRequest),自定义传输层OkHttp等等。
Volley的缺点:
不适合下载大数据的文件,Volley中(1个)缓存线程,(4个)网络线程在执行请求时候,都保持着所有的响应。 网络线程在执行完请求后,会默认在磁盘缓存解析后的响应数据。
文件下载推举使用DownLoadManager。有兴趣的,可以阅读 DownloadManager(强制版本更新和源码分析)
Volley框架的基本使用:
Volley将异步操作,网络请求,磁盘缓存已经封装好了,只需要使用以各种Request来向服务器获取对应的数据。
以下是Volley#Request常用的几种:
StringRquest, ImageRequest,JsonObjectRequest, JsonArrayRequest
以上几种请求的使用案例,使用方式比较常见,这里不再多描述,请阅读android官方参考案例,中文翻译的Volley使用案例。
自定义适合项目需求的Request之前,先来了解下Request:
首先了解Request中的T
/**
* Base class for all network requests.
*
* @param The type of parsed response this request expects.
*
* 全部请求的超类
* 参数是响应解析后返回的数据类型
*/
public abstract class Request<T> implements Comparable<Request<T>> {
..........
}
public class StringRequest extends Request<String> {
}
public class JsonObjectRequest extends JsonRequest<JSONObject>{
}
public class JsonArrayRequest extends JsonRequest<JSONArray> {
}
public class ImageRequest extends Request<Bitmap> {
}
从以上代码可知T是指响应数据解析后返回的数据类型。
StringRequest是返回的数据类型String.
JsonObjectRequest:返回的数据类型是JsonObject
JsonArrayRequest对应的数据类型是JSONArray
ImageRequest对应的数据类型是Bitmap .
了解完返回的数据类型后,来了解下参数和标头是怎么添加到Http请求的。
Request中Body和Header:
众所周知:Http请求包含Body和header两部分。有兴趣,可以阅读 Java网络编程之HttpURLConnection , Android网络编程之HttpUrlConnection.
Volley框架中HurlStack类是操作HttpURLConnection的。
在HurlStack类中,先来查看下添加Header到HttpURLConnection.
/**
* 执行HttpURLConnection,返回HttpResponse
*
* @param request the request to perform
* @param additionalHeaders additional headers to be sent together with
* {@link Request#getHeaders()}
* @return
* @throws IOException
* @throws AuthFailureError
*/
@Override
public HttpResponse performRequest(Request> request, Map additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
//创建一个Map来,装载Http中标头
HashMap map = new HashMap();
//添加本次请求中标头(来源开发者手动设置)
map.putAll(request.getHeaders());
//添加上次相同key请求的响应数据中的一些标头(来源于磁盘缓存)
map.putAll(additionalHeaders);
//对Url进行转换
if (mUrlRewriter != null) {//默认情况,UrlRewriter 为空,
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
//创建一个HttpUrlConnection或者其子类,进行网络连接。
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
//添加Http的标头
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
............
}
从源码可知:Http请求的Header是通过Rquest#getHeaders()来设置的。
在HurlStack类中,查看下添加Body到HttpURLConnection.
/**
* 若是请求中存在Body(post传递的参数),则写入body到流中。
* @param connection
* @param request
* @throws IOException
* @throws AuthFailureError
*/
private static void addBodyIfExists(HttpURLConnection connection, Request> request)
throws IOException, AuthFailureError {
//获取到Request中传递的参数
byte[] body = request.getBody();
if (body != null) {
//设置post请求方法,允许写入客户端传递的参数
connection.setDoOutput(true);
//设置标头的Content-Type属性
connection.addRequestProperty(HEADER_CONTENT_TYPE,
request.getBodyContentType());
DataOutputStream out = new DataOutputStream(
connection.getOutputStream());
//写入post传递的参数
out.write(body);
out.close();
}
}
从源码可知, Http请求的的body是通过Request#getBody()获取的。Content-Type标头是通过Request#getBodyContentType()
Request#Content-Type:
总所周知: 各种数据上传时的Content-Type类型是不一样的。
在Request.java:
/**
* Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
*
* 默认情况下,Post或者Put方法的编码格式
*/
private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
protected String getParamsEncoding() {
return DEFAULT_PARAMS_ENCODING;
}
/**
* 获取到内容的编码格式
* @return
*/
public String getBodyContentType() {
return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
}
可知: Request 对应的是:application/x-www-form-urlencoded
在JsonRequest.java中:
/** Charset for request. */
private static final String PROTOCOL_CHARSET = "utf-8";
/** Content type for request. */
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json; charset=%s", PROTOCOL_CHARSET);
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
可知:JsonRequest(包含两个子类JsonArrayRequest和JsonObjectRequest) 对应的是: application/json
从Request中的Body,Header (包括Content-Type),返回的数据类型T,便形成了一个请求的全部过程:设置请求–>解析服务器响应数据。
实际需求:目前,项目开发中客户端和服务器间常用的传递数据是json,利用官方库Gson解析json.
解决方式:自定义一个带有header(coockie,Content-type),JsonObject参数,Gson解析的GsonRequest。
1.先继承Request,自定义GsonRequest:
public class GsonRequest<T> extends Request<T> {
}
2.然后设置JsonObject参数,初始化Header,Content-Type的类型,返回的解析后的实体类类型
/** Charset for request. */
private static final String PROTOCOL_CHARSET = "utf-8";
/** Content type for request. */
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json; charset=%s", PROTOCOL_CHARSET);
/**
* 解析后的实体类
*/
private final Class clazz;
private final Response.Listener listener;
/**
* 自定义header:
*/
private Map headers;
/**
* post传递的参数
*/
private final String mRequestBody;
public GsonRequest(int method, String url, JSONObject
jsonRequest,Class clazz,Response.Listener listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.clazz = clazz;
this.listener = listener;
headers = new HashMap<>();
this.mRequestBody=(jsonRequest==null)
?null:jsonRequest.toString();
}
3.重写getBody(),getHeaders(), getBodyContentType(),往Http请求中添加参数和Header:
/**
* 重写getHeaders(),添加自定义的header
*
* @return
* @throws AuthFailureError
*/
@Override
public Map getHeaders() throws AuthFailureError {
return headers;
}
/**
* 设置请求的header
* "Charset", "UTF-8"://编码格式:utf-8
* "Cookie", coockie:////设置coockie
* @param
* @return
*/
public Map setHeader(String key, String content){
if(!TextUtils.isEmpty(key)&&!TextUtils.isEmpty(content)){
headers.put(key, content);
}
return headers;
}
/**
* 重写Content-Type:设置为json
*/
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
/**
* post参数类型
*/
@Override
public String getPostBodyContentType() {
return getBodyContentType();
}
/**
* post参数
*/
@Override
public byte[] getPostBody() throws AuthFailureError {
return getBody();
}
/**
* 将string编码成byte
* @return
* @throws AuthFailureError
*/
@Override
public byte[] getBody() throws AuthFailureError {
try {
return mRequestBody == null ? null :
mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (Exception e) {
return null;
}
}
4.利用Gson解析相应后的数据生成的json,返回实体类:
private final Gson gson = new Gson();
/**
* 工作线程中解析
*
* @param response
* @return
*/
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
//按照格式(默认ISO-8859-1),设置响应的数据
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
Log.i("NetworkResponse",json);
//gson解析
T t = gson.fromJson(json, clazz);
return Response.success(t,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
/**
* 回调到主线程
*
* @param t
*/
@Override
protected void deliverResponse(T t) {
listener.onResponse(t);
}
GsonRequest中完整代码:
public class GsonRequest<T> extends Request<T> {
/** Charset for request. */
private static final String PROTOCOL_CHARSET = "utf-8";
/** Content type for request. */
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json; charset=%s", PROTOCOL_CHARSET);
private final Gson gson = new Gson();
/**
* 解析后的实体类
*/
private final Class clazz;
private final Response.Listener listener;
/**
* 自定义header:
*/
private Map headers;
/**
* post传递的参数
*/
private final String mRequestBody;
public GsonRequest(int method, String url, JSONObject jsonRequest,
Class clazz,
Response.Listener listener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.clazz = clazz;
this.listener = listener;
headers = new HashMap<>();
this.mRequestBody=(jsonRequest==null)?null:jsonRequest.toString();
}
/**
* 重写getHeaders(),添加自定义的header
*
* @return
* @throws AuthFailureError
*/
@Override
public Map getHeaders() throws AuthFailureError {
return headers;
}
/**
* 工作线程中解析
*
* @param response
* @return
*/
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
Log.i("NetworkResponse",json);
T t = gson.fromJson(json, clazz);
return Response.success(t, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
/**
* 回调到主线程
*
* @param t
*/
@Override
protected void deliverResponse(T t) {
listener.onResponse(t);
}
/**
* 设置请求的header
* "Charset", "UTF-8"://编码格式:utf-8
* "Cookie", coockie:////设置coockie
* @param
* @return
*/
public Map setHeader(String key, String content) {
if(!TextUtils.isEmpty(key)&&!TextUtils.isEmpty(content)){
headers.put(key, content);
}
return headers;
}
/**
* 重写Content-Type:设置为json
*/
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
/**
* post参数类型
*/
@Override
public String getPostBodyContentType() {
return getBodyContentType();
}
/**
* post参数
*/
@Override
public byte[] getPostBody() throws AuthFailureError {
return getBody();
}
/**
* 将string编码成byte
* @return
* @throws AuthFailureError
*/
@Override
public byte[] getBody() throws AuthFailureError {
try {
return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (Exception e) {
return null;
}
}
}
接下来就是用了GsonRequest了。
先按照官方教程对Volley的设置如下:
先设置一个单例类,确保app生命周期中只有一个RequestQueue:
/**
* static 的单例类
*
* @author 新根
*/
public class VolleySingleton {
private static VolleySingleton mInstance;
private static Context context;
private RequestQueue mRequestQueue;
private ImageLoader imageLoader;
public static VolleySingleton getInstance() {
if (mInstance == null) {
//防止多线程的并发访问
synchronized (VolleySingleton.class) {
if (mInstance == null) {
mInstance = new VolleySingleton();
}
}
}
return mInstance;
}
private VolleySingleton() {
//自定义的Application子类
context = BaseApplication.getAppContext();
mRequestQueue = getRequestQueue();
imageLoader = new ImageLoader(mRequestQueue, new VolleyBitmapCache(BaseApplication.getAppContext(),mRequestQueue));
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(context);
}
return mRequestQueue;
}
public ImageLoader getImageLoader() {
return imageLoader;
}
public void addToRequestQueue(Request req) {
getRequestQueue().add(req);
}
/*
* 取消TAG标记的request
*/
public void cancleRequest(String TAG) {
RequestQueue queue = getRequestQueue();
if (queue != null) {
queue.cancelAll(TAG);
}
}
封装一个GsonRequest的操作类:
public class VolleyCallback {
/**
* get请求
* @param url
* @param tag
* @param mclass
* @param callback
* @param
*/
public static void sendRequest(String url,String tag, Class mclass, final GsonParseCallback callback){
sendRequest(Request.Method.GET,url,tag,null,mclass,callback);
}
/**
* post 请求
* 参数: jsonObject
* @param method
* @param url
* @param tag
* @param jsonObject
* @param mclass
* @param callback
* @param
*/
public static void sendRequest(int method, String url,String tag,JSONObject jsonObject, Class mclass, final GsonParseCallback callback){
sendRequest(method,url,tag,null,jsonObject,mclass,callback);
}
/**
* post 请求
* 参数: jsonObject
* coockie
*
* @param method
* @param url
* @param tag
* @param cookie
* @param jsonObject
* @param mclass
* @param callback
* @param
*/
public static void sendRequest(int method, String url, String tag,String cookie, JSONObject jsonObject, Class mclass, final GsonParseCallback callback){
final T result;
GsonRequest request=new GsonRequest<>(method, url,jsonObject, mclass, new Response.Listener() {
@Override
public void onResponse(T t) {
callback.sucess(t);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
callback.error( volleyError);
}
});
if(!TextUtils.isEmpty(cookie)){
//设置coockie
request.setHeader("Charset","UTF-8");
request.setHeader("Cookie",cookie);
}
request.setTag(tag);
request.setRetryPolicy(new DefaultRetryPolicy(50000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
VolleySingleton.getInstance().addToRequestQueue(request);
}
/**
* volley请求结果的回调,回调在调用者所在的上下文环境
*/
public interface GsonParseCallback{
public void sucess(T t);
public void error(Exception e);
}
}
在Activity中的使用:
public class DataActivity extends AppCompatActivity {
private final static String TAG=DataActivity.class.getSimpleName();
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView=new TextView(this);
setContentView(textView);
sendPersonequest();
}
private String url="http://192.168.1.101:8080/SSMProject/person/app";
public void sendPersonequest(){
JSONObject jsonObject=null;
try{
jsonObject=new JSONObject();
jsonObject.put("name","haha");
jsonObject.put("age",2);
}catch ( Exception e){
e.printStackTrace();
}
VolleyCallback.sendRequest(Request.Method.POST,url, TAG,jsonObject,
JsonBean.class, new VolleyCallback.GsonParseCallback() {
@Override
public void sucess(T t) {
JsonBean jsonBean=(JsonBean) t;
textView.setText("发送一条信息到后台,成功插入到mysql:"+jsonBean.state);
}
@Override
public void error(Exception e) {
e.printStackTrace();
}
});
}
@Override
protected void onStop() {
//activity处于onstope状态时,停止网络操作,线程操作,这里取消Volley#Request
VolleySingleton.getInstance().cancleRequest(TAG);
super.onStop();
}
}
Web后台接口的部分代码:接收客户端传递过来数据,插入mysql中,返回状态success.
@RequestMapping("/app")
public void appRequest(HttpServletRequest request,HttpServletResponse response){
try {
String postParamter=IOUtils.toString(request.getInputStream(),"utf-8");
//String postParamter=stremToString(request.getInputStream(),"utf-8");
JSONObject postJsonObject=stringToJson(postParamter);
if(postJsonObject.containsKey("age")&&postJsonObject.containsKey("name")){
Person person=new Person();
person.setName(postJsonObject.getString("name"));
person.setAge(postJsonObject.getInteger("age"));
operation.insertPerson(person);
}
JSONObject resultJsonObject=new JSONObject();
resultJsonObject.put("state", "success");
setResponse(response,resultJsonObject);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置客户端返回值
* @param response
* @param jsonObject
*/
public void setResponse(HttpServletResponse response,JSONObject jsonObject){
try {
response.setCharacterEncoding("utf-8");
response.setContentType("charset=UTF-8");
String result=jsonObject.toString();
response.getWriter().write(result, 0, result.length());;
} catch (Exception e) {
e.printStackTrace();
}
}
Gson解析对应的实体类代码:
public class JsonBean {
public String state;
}
gradle中的配置:
dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile files('libs/volley.jar')
compile 'com.google.code.gson:gson:2.2.4'
}
权限配置:
<uses-permission android:name="android.permission.INTERNET">uses-permission>
最后项目运行的效果如下:
运行前的myslq中personmsg表中数据:
运行app后,服务端的效果:在mysq中新增了一条数据
运行app后,android客户端:返回成功标示success
项目代码:http://download.csdn.net/detail/hexingen/9681762
相关知识点参考:
Java网络编程之HttpURLConnection
Android网络编程之HttpUrlConnection.
下一篇,文件上传的MultiPartRequest