volley(1)

volley

[TOC]

volley是一个简单的异步http库,特点就是封装了请求,加载,缓存,线程,同步等过程和提供回调方法,使用者只需创建特定类型的Request和传入相应的url,便可在回调中获取到相应类型的对象。除此之外,volley还为图片的加载提供了多种方式,毕竟图片的大量加载在平时开发时需要用到。

优点是比较适合小而频繁的http请求,还结合了ImageLoader的优点。
缺点是不支持同步,不能post大数据,所以不适合用于上传大文件。

1 volley使用方法

学习一个框架最好的一个办法就是先从它的使用入手,知道了它如何使用了,然后再跟着它的使用步骤去看它的源码是怎样实现的。

这里分为三个部分介绍,一个是volley的基本使用方法,一个是将volley加载网络图片的方法单独提取出来介绍,一个是自定义Request的使用。

1.1 volley的基本使用

使用volley获取相应数据是需要3步:

  1. 创建RequestQueue对象
  2. 创建Request对象并在Listener的回调中处理回调对象
    一般会在回调方法中更新UI
  3. 将Request添加至RequeueQueue

注:HTTP的请求类型通常有两种,GET和POST,默认使用的是GET方法,之后在分析源码时可以看到,如果要发出POST请求的话,还应该在创建Request对象时传入Method参数并重写getParams(),在内部返回一个带有数据的Map对象

1.1.1 StringRequest

按照上面的步骤我们可以写出下面的StringRequest使用的步骤(其中访问的数据是在自己搭建的服务器):

//1. 创建RequestQueue对象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

//2. 创建Request对象并在Listener中处理回调对象,在回调方法中更新UI
StringRequest stringRequest = new StringRequest(
    "http://10.132.25.104/JsonData/data1.php", 
    new Response.Listener() {
    @Override
    public void onResponse(String response) {
        // TODO Auto-generated method stub
        textView.setText(response);
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        Log.e(TAG,error.getMessage());        
    }
}); 

//3. 将Request添加至RequestQueue
queue.add(stringRequest);

上面的Request的请求方式是GET,那如何将请求方式转为POST然后添加参数呢?答案就在于创建StringRequest的时候,传入Method.POST将请求方式转为POST,然后重写getParams()将请求参数添加进Map里然后返回,代码如下。

StringRequest stringRequest = new StringRequest(Request.Method.POST, url, listener, errorListener)
{
    @Override
    protected Map getParams() throws AuthFailureError {
        Map map = new HashMap();
        map.put("name", "Shane");
        map.put("age","23");
        return map;
    }
};

1.1.2 JsonRequest

JsonRequest也是继承自Request类,因此使用方法跟StringRequest一样,不过JsonRequest是一个抽象类,JsonRequest有两个直接的子类JsonObjectRequest和JsonArrayRequest,这两个类的区别就是一个的回调方法是JSONObject,另一个是JSONArray。它们的格式分别是:
JSONObject:"{name:lee, age:24}"
JSONArray:"[{name:one, age:1},{name:two, age:2},{name:three, age:3}]"

JsonObjectRequest用法(数据格式为"{name:lee, age:24}")

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null,  
        new Response.Listener() {  
            @Override  
            public void onResponse(JSONObject response) {  
                Log.d("TAG", response.toString());  

                textView.setText("name:"+response.getString("name")
                            ", age:"+response.getInt("age"));
            }  
        }, new Response.ErrorListener() {  
            @Override  
            public void onErrorResponse(VolleyError error) {  
                Log.e("TAG", error.getMessage(), error);  
            }  
        });  

queue.add(jsonObjectRequest);

JsonArrayRequest的用法(数据格式为"[{name:one, age:1},{name:two, age:2},{name:three, age:3}]")

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
                url, new Listener() {
            @Override
            public void onResponse(JSONArray response) {
                tv.setText("");
                for(int i = 0;i

1.2 volley加载网络图片

volley之中为加载图片提供了多种方法,能够实现与UIL一样出色的加载网络图片的功能,下面就来学习一下使用volley加载网络图片的方法。

1.2.1 ImageRequest

一看到Request就想到之前所学的StringRequest和JsonRequest,同样的,ImageRequest也是volley提供的异步加载图片的请求,使用方法也是和之前的一样。

  1. 创建RequestQueue对象
  2. 创建Request对象
  3. 将Request添加至RequeueQueue

不过与之前不同的是,ImageRequest的构造方法所需的参数比较特殊,由下面的代码可以看出,ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

ImageRequest imageRequest = new ImageRequest(
        "http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg", 
        new Response.Listener() {
            @Override
            public void onResponse(Bitmap response) {
                // TODO Auto-generated method stub
                ivPic1.setImageBitmap(response);
            }
        },
        0,
        0,
        Config.RGB_565,
        new ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                // TODO Auto-generated method stub
                ivPic1.setImageResource(R.drawable.ic_launcher);
            }
        }); 

queue.add(imageRequest);

1.2.2 ImageLoader

使用ImageRequest已经十分方便了,因为用法和其他的Request的使用方法一样,因此十分容易记忆。但是ImageRequest就相当于每次调用都去访问网址请求数据,这对于大量加载网络图片来说这是不可取的。回忆一下UIL加载图片的流程,其中使用了缓存来提高加载图片的效率。
同样的,volley也提供了ImageLoader,它不仅可以对图片进行缓存,还可以过滤掉重复的链接来避免重复发送请求。
使用volley的ImageLoader加载图片分为以下几步:

  1. 创建RequestQueue对象
  2. 创建BitmapCache类实现ImageCache接口
    需要自己实现缓存(一般使用LruCache)
  3. 创建ImageLoader对象
  4. 通过ImageLoader获取ImageListener对象
    其中设置显示控件ImageView以及默认显示图片
  5. 通过ImageLoader.get()加载显示图片
    其中可设置图片大小限制
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());

ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.defaultImage, R.drawable.errorImage);

imageLoader.get("http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg",
        listener,200,200);

/**
 * 自定义缓存类实现ImageCache接口
 */
class BitmapCache implements ImageCache
{
    private LruCache lruCache;
    
    public BitmapCache()
    {
        lruCache = new LruCache(10 * 1024 * 1024)
                    {
                        @Override
                        protected int sizeOf(String key, Bitmap value) {
                            // TODO Auto-generated method stub
                            return value.getRowBytes() * value.getHeight();
                        }
                    };
    }
            
    @Override
    public Bitmap getBitmap(String url) {
        // TODO Auto-generated method stub
        return lruCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        // TODO Auto-generated method stub
        lruCache.put(url, bitmap);
    }
    
}

1.2.3 NetworkImageView

volley还提供了第三种加载网路图片的方法,自定义了继承于ImageView的NetworkImageView,通过setImageUrl可以直接从url中加载图片,当然这里面也需要RequestQueue和ImageLoader。
使用NetworkImageView需要以下几步(前面几步跟上面一节的一样,只是后面不需要Listener了):

  1. 创建RequestQueue对象
  2. 创建BitmapCache类实现ImageCache接口
    需要自己实现缓存(一般使用LruCache)
  3. 创建ImageLoader对象
  4. 在xml布局文件创建NetworkImageView控件
  5. 在代码中使用NetworkImageView的setImageUrl加载图片

注:在之前的加载网络图片的方法中,都可以自定义最终图片的大小从而压缩图片,NetworkImageView的压缩则是直接利用xml中的控件的宽高,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽高,再决定是否需要对图片进行压缩。在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。因此NetworkImageView的压缩过程是在内部自动化的,并不需要我们关心

前3步与前一节一样,所以省去了,只看NetworkImageView是怎么设置工作的。
在xml布局文件中:


    
    

在java代码中:

NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
networkImageView.setImageUrl("http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg", imageLoader);

1.3 自定义Request

上面介绍了volley中的一些基本网络请求使用方法,可以发现,volley主要是通过建立Request对象,然后将其添加进工作的RequestQueue之中。但是在volley之中仅仅提供了StringRequest、JsonRequest及其子类、还有针对图片的ImageRequest。而开发中还有许多其他类型的返回数据,比如说xml数据类型,此时就是展现volley的强大之处的时候了。volley是一个开源框架,因此我们可以仿照这StringRequest的实现来自定义XMLRequest,GsonRequest等Request子类,这样我们只需将该Request添加至RequestQueue当中便可以在回调中获取到相应的数据类型,而不用每次都进行封装。

1.3.1 StringRequest源码

要自定义Request,首先先查看volley自带的StringRequest是怎么实现的,然后我们便可以仿照着自定义Request类。以下是StringRequest的源码。

//StringRequest.java
public class StringRequest extends Request {
    private final Listener mListener;

    /**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    /**
     * Creates a new GET request.
     *
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(String url, Listener listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}

从上面的代码中可以看出实现一个Request的子类只需要关注3个地方,构造方法、deliverResponse和parseNetworkResponse。

  1. 其中构造方法中自定义了一个Response.Listener,用于主线程中的回调;
  2. deliverResponse是返回解析的结果,该方法运行在主线程当中,一般该方法内部利用Listener将解析数据回调出去;
  3. parseNetworkResponse是将网络返回的字节数组型的数据(response.data)解析成想要的类型,比如StringRequest就解析成String,JsonObjectRequest就解析成JSONObject,最后通过调用Response.success()来将解析结果重新包装成Response对象返回。

1.3.2 JsonObjectRequest源码

JsonObjectRequest跟StringRequest类似,不过在JsonObjectRequest的上面多了一层JsonRequest,原因是JsonArrayRequest和JsonObjectRequest有些相同的地方,比如deliverResponse(),因此JsonObjectRequest中就不用实现deleverResponse了。

public class JsonObjectRequest extends JsonRequest {

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
     *   indicates no parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
            Listener listener, ErrorListener errorListener) {
        super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
                    errorListener);
    }

    /**
     * Constructor which defaults to GET if jsonRequest is
     * null, POST otherwise.
     *
     * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener)
     */
    public JsonObjectRequest(String url, JSONObject jsonRequest, Listener listener,
            ErrorListener errorListener) {
        this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
                listener, errorListener);
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString =
                new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }
}

可以看出JsonObjectRequest的parseNetworkResponse与StringRequest不同的地方就是,其将String数据里面转换成了JSONObject数据。由此可以知道,自定义的Request子类其实就是在parseNetworkResponse里面将String数据转换成所需的数据格式,比如XML所需的数据格式为XmlPullParser,则只需将String数据转换成XMLPullParser即可。

1.3.3 XMLRequest

有前面的StringRequest和JsonObjectRequest可以知道,自定义一个Request只需要3步,最主要就是在parseNetworkResponse中则将String转换成XmlPullParser,这里就不多说了,直接看代码。

public class XMLRequest extends Request{

    private final Listener mListener;
    
    public XMLRequest(int method, String url, Listener listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
    }
    public XMLRequest(String url,Listener listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected Response parseNetworkResponse(
            NetworkResponse response) {
        
        try {
            String xmlString = new String(response.data, 
                    HttpHeaderParser.parseCharset(response.headers));
            
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(new StringReader(xmlString));
            
            return Response.success(xmlPullParser,
                    HttpHeaderParser.parseCacheHeaders(response));
            
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            return Response.error(new ParseError(e));
        }catch (XmlPullParserException e) {
            // TODO Auto-generated catch block
            return Response.error(new ParseError(e));
        } 
        
    }

    @Override
    protected void deliverResponse(XmlPullParser response) {
        mListener.onResponse(response);
    }

}

1.3.4 GsonRequest

再实现一个Request加深印象,这里采用GsonRequest。因为JsonObjectRequest和JsonArrayRequest还是有点麻烦,因此自行封装一个GsonRequest。

public class GsonRequest extends Request {

    private final Listener mListener;
    private Class mClazz;
    private Gson mGson;
    
    public GsonRequest(int method, String url,Class clazz, Listener listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
        this.mClazz = clazz;
        mGson = new Gson();
        
    }

    public GsonRequest(String url, Class clazz, Listener listener, ErrorListener errorListener) {
        this(Method.GET, url, clazz, listener, errorListener);
    }
    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        
        try {
            String parsed = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            T result = mGson.fromJson(parsed, mClazz);
            
            return Response.success(result, 
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }
    }
    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

下面展示实际的用法:
首先看一下信息类User

public class User {

    private UserInfo userInfo;

    public UserInfo getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserInfo userInfo) {
        this.userInfo = userInfo;
    }
    

    
}

public class UserInfo
{
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
}

在代码中使用GsonRequest,可以发现,GsonRequest中只传入了User而并没有传入UserInfo,这里Gson已经把User里面的类做了封装了,因此可以直接获取到UserInfo类。

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
GsonRequest gsonRequest = new GsonRequest(url,
                User.class,
                new Response.Listener()
                {
                    @Override
                    public void onResponse(User response)
                    {
                        UserInfo userInfo = response.getUserInfo();
                        textView.setText("name:"+userInfo.getName()
                            + ", age:"+userInfo.getAge());
                    }
                },new Response.ErrorListener()
                {
                    @Override
                    public void onErrorResponse(VolleyError error)
                    {
                         Log.e("TAG", error.getMessage(), error); 
                    }
                });
queue.add(gsonRequest);

从url获取到的数据格式如下:

{
    "userInfo":
    {
        "name":"lee",
        "age":23
    }       
}

输出格式如下:

name:23, age:23

1.3.5 JacksonRequest

由于Gson解析Json数据的性能不如Jackson,因此再来自定义一个JacksonRequest,这里需要注意的是,Jackson在解析Json数据的时候,对于字符串类型一定要有双引号引起来,不然会报错,比如{"name":"lee","age":19}

public class JacksonRequest extends Request {

    private final Listener listener;
    private Class mClazz;
    private ObjectMapper mapper;
    
    public JacksonRequest(int method, String url,Class clazz, Listener l, ErrorListener listener) {
        super(method, url, listener);
        this.listener = l;
        this.mClazz = clazz;
        mapper = new ObjectMapper();
    }

    public JacksonRequest(String url,Class clazz, Listener l, ErrorListener listener) {
        this(Method.GET,url,clazz, l, listener);
        
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        
        try {
            String parsed = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            
            T result = mapper.readValue(parsed, mClazz);
            
            return Response.success(result, 
                    HttpHeaderParser.parseCacheHeaders(response));
            
        } catch (Exception e) {
            
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        // TODO Auto-generated method stub
        listener.onResponse(response);
    }
}

最后再总结一下自定义Request的用法,主要分为3步:

  1. 构造带有Response.Listener的构造方法
  2. 重写deliverResponse()方法,一般在该方法里面调用mListener.onResponse(result)将解析结果回调出去
  3. 重写parseNetworkResponse()方法,该方法是实现不同Request子类的关键,因为在该方法中,需要将获取到的String类型的数据转换成特定的类型,比如JsonObjectRequest需要JSONObject类型数据,XMLRequest需要XmlPullParser类型数据等等。

你可能感兴趣的:(volley(1))