Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)

本篇博客内容:

  • 介绍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 <T> The type of parsed response this request expects.
 *
 *  全部请求的超类
 *  参数<T>是响应解析后返回的数据类型
 */
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<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        //创建一个Map来,装载Http中标头
        HashMap<String, String> map = new HashMap<String, String>();
        //添加本次请求中标头(来源开发者手动设置)
        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<T> clazz;

    private final Response.Listener<T> listener;
    /**
     * 自定义header:
     */
    private Map<String, String> headers;
    /**
     * post传递的参数
     */

    private final String mRequestBody;

    public GsonRequest(int method, String url, JSONObject   
          jsonRequest,Class<T> clazz,Response.Listener<T> 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<String, String> getHeaders() throws AuthFailureError {
        return headers;
    }  


    /**
     * 设置请求的header
     * "Charset", "UTF-8"://编码格式:utf-8
     * "Cookie", coockie:设置coockie
     * @param
     * @return
     */
    public Map<String, String> 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<T> 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<T> clazz;

    private final Response.Listener<T> listener;
    /**
     * 自定义header:
     */
    private Map<String, String> headers;
    /**
     * post传递的参数
     */

    private final String mRequestBody;

    public GsonRequest(int method, String url, JSONObject jsonRequest,
                       Class<T> clazz,
                       Response.Listener<T> 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<String, String> getHeaders() throws AuthFailureError {
        return headers;
    }
    /**
     * 工作线程中解析
     *
     * @param response
     * @return
     */
    @Override
    protected Response<T> 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<String, String> 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 <T> void addToRequestQueue(Request<T> 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 <T>
     */
    public static <T> void  sendRequest(String url,String tag, Class<T> 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 <T>
     */
    public static <T> void sendRequest(int method, String url,String tag,JSONObject jsonObject, Class<T> 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 <T>
     */
    public static <T> void  sendRequest(int method, String url, String tag,String cookie, JSONObject jsonObject, Class<T> mclass, final GsonParseCallback  callback){
        final T result;
        GsonRequest<T>  request=new GsonRequest<>(method, url,jsonObject, mclass, new Response.Listener<T>() {
            @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 <T> 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 <T> 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>

最后项目运行的效果如下:

相关知识点参考:

下一篇,文件上传的MultiPartRequest

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值