Android学习之网络请求(volley)二

Volley

Volley的设计目标非常适合去进行数据量不大,但通信频繁的网络 操作,而对于大数据量的网络操作,比如下载文件等,Volley的表现就会非常糟糕。
下图所示的这些应用都是属于数据量不大,但网络通信频繁的
这里写图片描述

volley可以自动缓存请求,它可以记住前一个调用同时处理Activity的销毁和重建

内部架构

volley分为三层,每一层都工作在自己的线程中
这里写图片描述

主线程
在主线程,只允许触发请求与处理返回结构,不能多也不能少
其结果就是我们实际上可以忽略在使用AsyncTask的时候boInBackground方法里面所做的事情。volley自动管理http传输同时捕获网络错误

缓存与网络线程
党我们向队列中添加一个请求,背后发生了几件事情。volley会检查这个请求是否可以从缓存中得到。如果可以,缓存负责读取,解析,和分发。否则将传递给网络线程。
在网络线程中,一些列的轮询线程不断的在工作。第一个可用的网络线程,线程让请求队列出列,发出http请求,解析返回的结果,并写入到缓存中。最后,把解析的结果分发给主线程的listener中。

导包

在module中添加依赖:

compile 'com.android.volley:volley:1.0.0'

或者下载volley的jar包,将它导入

使用

通常volley只会用到两个类RequestQueue和Request,我们首先创建一个ResquestQueue,RequestQueue管理工作线程并将解析的结果发送给主线程。然后我们传递一个或者多个Request对象给它
Request的构造函数的参数总是包含类型(GET,POST,等等),数据源的url,以及事件监听者。根据请求类型的不同,可能还需要一些其他的参数。
通过下面方法得到一个RequestQueue对象

RequestQueue mQueue = Volley.newRequestQueue(context);  

注意这里拿到的RequestQueue是一个请求队列对象,它可以缓存所有HTTP请求,然后按着一定的算法并发地发出这些请求。RequestQueue内部的设计就是非常适合高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的activity中创建一个RequestQueue对象就足够了

接下来为了发出一条HTTP请求,我们还需要创建一个StringRequest对象

  StringRequest request = new StringRequest(Request.Method.GET,"https://www.baidu.com/",
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                Log.d("A", response);
                            }
                        }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d("AAA",error.getMessage(),error);
                    }
                });

可以看到,这里new出了一个StringRequest对象,StringRequest的构造函数需要传入三个参数,第一个就是目标服务器的URL地址,第二个是服务器响应成功的回调,第三个是服务器响应失败的回调。

最后将StringRequest对象添加到RequestQueue里面就可以了

queue.add(request);

另外,由于Volley是要访问网络的,因此不要忘记在AndroidManifest.xml中添加如下权限:

<uses-permission android:name="android.permission.INTERNET" />  

结果:

!DOCTYPE html>
                                                       <html><!--STATUS OK--><head><script>var actionSta = 1;if (actionSta === 1) {var startTime = Date.now();} else {var endTime = Date.now();}if (actionSta === 1 || (endTime && (endTime - startTime) > 3000)) {actionSta = actionSta == 1 ? 'start' : 'end';new Image().src = '//hpd.baidu.com/v.gif?tid=365&funcSta=whiteScreenEx&sourceSta=wiseindex&actionSta='+ actionSta + '&logid=2310090276&ssid=0'+ '&ct=1&cst=9&logFrom=mid_news&logInfo=stability';}</script><meta name="referrer" content="always" /><meta charset='utf-8' /><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/><meta http-equiv="x-dns-prefetch-control" cont.............

百度给我们返回一串HTML代码

处理标准的请求

volley实现了三种常见的请求类型:

  • StringRequest
  • ImageRequest
  • JsonRequest

每个类都是继承自Request类。
下面看看JsonRequest怎么工作:

String url = "https://api.seniverse.com/v3/weather/now.json?key=5ekxy0g1gzc7nfl5&location=beijing&language=zh-Hans&unit=c";

JsonObjectRequest jsonObjectRequest  = new JsonObjectRequest(
               Request.Method.GET,  url,null,  new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject jsonObject) {
                try{
                    JSONArray response = jsonObject.getJSONArray("results");
                    JSONObject loc = response.getJSONObject(0);
                    JSONObject locc = loc.getJSONObject("location");
                    String city = locc.getString("name");
                    String country  = locc.getString("country");
                    Log.d("city",city+"  "+country);
                    //Log.d("ccc",loc.toString());
                }catch (JSONException e){
                    e.printStackTrace();
                }

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Log.d("cccc",volleyError.getMessage());
            }
        }
        );

        Volley.newRequestQueue(this).add(jsonObjectRequest);

    }

结果:

city: 北京 CN

获取图片则需要更多的工作。
有三种请求图片的方法。ImageRequest是标准方式。通过提供的url,它将你请求的图片显示在一个普通的ImageView中。压缩与大小调整的操作都发生在工作线程中。第二种是ImageLoader类,我们可以将之想象成数量庞大的ImageRequests,比如生成一个带有图片的ListView。第三种选择是NetworkImageView。

ImageRequest

String url = "http://img0.imgtn.bdimg.com/it/u=1430510032,2634676844&fm=26&gp=0.jpg";

        final ImageRequest imageRequest = new ImageRequest(
                url, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap bitmap) {
                imageView.setImageBitmap(bitmap);

            }
        }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                imageView.setImageResource(R.mipmap.ic_launcher);
            }
        }
        );

        Volley.newRequestQueue(this).add(imageRequest);

效果:
这里写图片描述

可以看到,ImageRequest的构造函数接受六个参数,第一个参数是图片的URL地址,第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三个第四个参数分别用于指定图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定为0的话就表示不管图片多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占4个字节大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调

ImageLoader
ImageLoader也可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageLoader更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。

由于ImageLoader已经不是继承自Request的了,所以它的用法不同:

  1. 创建一个RequestQueue对象
  2. 创建一个ImageLoader对象
  3. 获取一个ImageListener对象
  4. 调用ImageLoader的get()方法加载网络上的图片
 String url = "http://img0.imgtn.bdimg.com/it/u=1430510032,2634676844&fm=26&gp=0.jpg";
        RequestQueue queue = Volley.newRequestQueue(this);
        ImageLoader loader = new ImageLoader(queue, new ImageLoader.ImageCache() {
            @Override
            public Bitmap getBitmap(String s) {
                return null;
            }

            @Override
            public void putBitmap(String s, Bitmap bitmap) {

            }
        });

可以看到,ImageLoader的构造函数接收两个参数,第一个参数就是RequestQueue对象,第二个参数是一个ImageCache对象,这里我们先new出一个空的ImageCache的实现即可

ImageListener listener = ImageLoader.getImageListener(imageView,  
        R.drawable.default_image, R.drawable.failed_image);  

我们通过上面方法得到ImageListener对象,getImageListener()方法接受三个参数,第一个参数指定用于显示图片的Imageview,第二个参数指定加载图片过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片

loader.get(url,listener);

最后调用ImageLoader的get()方法来加载图片
get()方法接收两个参数,第一个参数是图片URL地址,第二个参数则是刚刚获取的ImageListener对象,如果我们想对图片进行大小限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度:

loder.get(url,listener, 200, 200);  

这里写图片描述

刚才我们创建的ImageCache对象使一个空的实现,完成没有起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想写一个性能非常好的ImageCache,最好就要借助android提供的LruCache

class BitmapCache implements ImageLoader.ImageCache{

        private LruCache<String,Bitmap> mCache;

        public BitmapCache(){
            int maxSize = 10*1024*1024;
            mCache = new LruCache<String, Bitmap>(maxSize){
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes()*value.getHeight();
                }
            };
        }

        @Override
        public Bitmap getBitmap(String url) {
            return mCache.get(url);
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            mCache.put(url,bitmap);
        }
    }

我们将缓存图片的大小设置为10M,接着修改ImageLoader实例的代码:

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

这样我们就把ImageLoader的功能优势充分利用起来

NetworkImageView
NetworkImageView是一个自定义控件,它是继承自ImageView的,具备ImageView控件的所有功能,并且在原生的基础之上加入了加载网络图片的功能。

  1. 创建一个RequestQueue对象
  2. 创建一个ImageLoader对象
  3. 在布局文件中添加一个NetworkImageView控件
  4. 在代码中获取该控件的实例
  5. 设置要加载图片的地址

第一步和第二步我们已经学习了,开始第三步,首先在布局中加入控件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:orientation="vertical" >  

    <Button  
        android:id="@+id/button"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Send Request" />  

    <com.android.volley.toolbox.NetworkImageView   
        android:id="@+id/network_image_view"  
        android:layout_width="200dp"  
        android:layout_height="200dp"  
        android:layout_gravity="center_horizontal"  
        />  

</LinearLayout>  

在activity中得到实例后,我们可以调用它的setDefaultImageRedId(),setErrorImageResId(),setImageUrl()方法分别来设置加载中显示的图片,加载失败时显示的图片,以及目标图片URL地址

networkImageView.setDefaultImageResId(R.drawable.default_image);  
networkImageView.setErrorImageResId(R.drawable.failed_image);  
networkImageView.setImageUrl("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",  
                imageLoader);  

NetworkImageView并补需要提供任何设置最大宽度的方法也能够对加载的图片进行压缩。这是由于NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心

自定义XMLRequest

首先我们先看看StringRequest是怎么实现的

/** 
 * A canned request for retrieving the response body at a given URL as a String. 
 */  
public class StringRequest extends Request<String> {  
    private final Listener<String> 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<String> 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<String> listener, ErrorListener errorListener) {  
        this(Method.GET, url, listener, errorListener);  
    }  

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

    @Override  
    protected Response<String> 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));  
    }  
}  

可以看到,StringReque的源码很简练。首先StringRequest是继承Request类的,Request可以指定一个泛型类,接下来StringRequest中提供了两个有参的构造函数,一定要在构造函数中调用super()方法将这几个参数传给父类,因为HTTP的请求和响应都是在父类中自动处理的

另外,由于Request类中的deliverResponse()和parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入即可,这样就可以将服务器响应的数据进行回调了。parseNetworkResponse()方法中则应该对服务器响应的数据进行解析,其中数据是以字节的形式存放在NetworkResponse的data变量中的,这里将数据取出然后组装成一个String,并传入Response的success()方法中即可。

下面我们手动来尝试XMLRequest:

public class XMLRequest extends Request<XmlPullParser> {
    private  final Response.Listener<XmlPullParser> mListener;

    public XMLRequest(int method, String url, Response.Listener mListener,Response.ErrorListener listener) {
        super(method, url, listener);
        this.mListener = mListener;
    }

    public XMLRequest(String url, Response.Listener mlistener,Response.ErrorListener listener) {
        this(Method.GET,url, mlistener,listener);
    }


    @Override
    protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse networkResponse) {
        try {
            String xmlString = new String(networkResponse.data, HttpHeaderParser.parseCharset(networkResponse.headers));
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(new StringReader(xmlString));
            return Response.success(xmlPullParser,HttpHeaderParser.parseCacheHeaders(networkResponse));

        }catch (UnsupportedEncodingException e){
            e.printStackTrace();
            return Response.error(new ParseError(e));
        }catch (XmlPullParserException e){
            e.printStackTrace();
            return Response.error(new ParseError(e));
        }
    }

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

XMLRequest也是继承Request类的,只不过这里指定的泛型类是XmlPullParser,说明我们准备使用Pull解析的方式来解析XML。在parseNetworkRequest()方法中,先是将服务器响应的数据解析成一个字符串,然后设置到XmlPullParser对象中,在deliverResponse()方法中则是将XmlPullParser对象进行回调。

下面我们尝试使用这个来请求一段XML格式的数据。
http://flash.weather.com.cn/wmaps/xml/china.xml

XMLRequest xmlRequest = new XMLRequest(  
        "http://flash.weather.com.cn/wmaps/xml/china.xml",  
        new Response.Listener<XmlPullParser>() {  
            @Override  
            public void onResponse(XmlPullParser response) {  
                try {  
                    int eventType = response.getEventType();  
                    while (eventType != XmlPullParser.END_DOCUMENT) {  
                        switch (eventType) {  
                        case XmlPullParser.START_TAG:  
                            String nodeName = response.getName();  
                            if ("city".equals(nodeName)) {  
                                String pName = response.getAttributeValue(0);  
                                Log.d("TAG", "pName is " + pName);  
                            }  
                            break;  
                        }  
                        eventType = response.next();  
                    }  
                } catch (XmlPullParserException e) {  
                    e.printStackTrace();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, new Response.ErrorListener() {  
            @Override  
            public void onErrorResponse(VolleyError error) {  
                Log.e("TAG", error.getMessage(), error);  
            }  
        });  
mQueue.add(xmlRequest);  

可以看到,这里XMLRequest的用法和StringRequest几乎是一模一样的,我们先创建出一个XMLRequest的实例,并把服务器接口地址传入,然后在onResponse()方法中解析响应的XML数据,并把每个省的名字打印出来,最后将这个XMLRequest添加到RequestQueue当中。

更多自定义请参考:http://blog.csdn.net/guolin_blog/article/details/17612763

POST请求

从get请求 切换到post请求是非常简单的。我们只需要在request的构造方法中改变Request.Method,同时重写getParams方法,返回包含请求参数的Map

String url = "http://httpbin.org/post";

StringRequest postRequest = new StringRequest(Request.Method.POST, url,
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                try {
                    JSONObject jsonResponse = new JSONObject(response).getJSONObject("form");
                    String site = jsonResponse.getString("site"),
                            network = jsonResponse.getString("network");
                    System.out.println("Site: "+site+"\nNetwork: "+network);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
            }
        }
) {
    @Override
    protected Map<String, String> getParams()
    {
        Map<String, String>  params = new HashMap<>();
        // the POST parameters:
        params.put("site", "code");
        params.put("network", "tutsplus");
        return params;
    }
};
Volley.newRequestQueue(this).add(postRequest);
取消一个请求

如果想取消所有的请求,在onStop方法中添加如下代码:

@Override
protected void onStop() {
    super.onStop();
    mRequestQueue.cancelAll(new RequestQueue.RequestFilter() {
        @Override
        public boolean apply(Request<?> request) {
            // do I have to cancel this?
            return true; // -> always yes
        }
    });
}

这样就不必担心在onResponse被调用的时候,用户已经销毁activity。这种情况下回抛出NullPointerException。但是post请求则需要继续,即使用户已经改变了activity。我们可以通过使用tag来做到,在构造GET请求的时候,添加一个tag给它

// after declaring your request
request.setTag("GET");
mRequestQueue.add(request);

如果想要取消GET请求,只需要:

mRequestQueue.cancelAll("GET");

这样就只会取消GET请求,让其他的不受影响。注意必须手动在销毁的activity中处理这种情况。

从源码分析Volley

Volley的工作流程图:
这里写图片描述

源码分析请参考:
http://blog.csdn.net/guolin_blog/article/details/17656437

本篇还参考:

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0526/2934.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值