Android 网络(三) Volley使用解析

前言

前一篇我们讲解了HttpURLConnection的使用,通过它我们可以发送和接收网络数据,在实际项目中使用率很高。不过可能你们也发现了,其用法还是有些复杂的,我们实际使用时还是需要对其再封装。
本文将讲述一个使用很简单的网络通信框架Volley。

文章篇幅略长,示例略多,建议收藏 & 下载Demo配合阅读

Demo地址:

Github:DemoVolley

相关文章

Android 网络(一) HTTP协议
Android 网络(二) HttpURLConnection用法解析
Android 网络(四) Volley源码解析
Android 网络(五) OkHttp用法解析
Android 网络(六) OkHttp源码解析
Android 网络(七) Retrofit用法解析
Android 网络(八) Retrofit源码解析

Volley简介

在2013年Google I/O大会上推出了一个新的网络通信框架VolleyVolley既可以访问网络取得数据,也可以加载图片,并且在性能方面也进行了大幅度的调整。

特点:

  • 自动调度网络请求
  • 支持多个并发的网络连接
  • 支持请求优先级
  • 可以取消单个或一系列请求
  • 易于定制,如自定义请求方式
  • 调试和跟踪工具

使用场景:

  • 非常适合去进行数据量不大,但通信频繁的网络操作
  • 不适合大数据量的网络操作,比如说下载文件等

如何导入

  • 可以通过下载Volley库导入到libs\下并add as a library
  • 也可以通过Maven
  • 当然还是更推荐通过Gradle添加依赖
    implementation 'com.android.volley:volley:1.1.0'

使用前提

  • 项目添加网络访问权限
    <uses-permission android:name="android.permission.INTERNET"/>

Volley网络请求队列

Volley请求网络是基于请求队列的,我们只要简单的将请求放到请求队列中就可以了,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。其内部设计非常适合高并发,因此基本上一个应用或一个Activity持有一个请求队列就足够了。

Request

Volley内部为我们提供了一系列不同数据的Request API,同时由于其良好的扩展性,我们也可以自定义我们需要的Request,对Request的分类如下图所示:

类型 相关API 说明
基础请求 StringRequestJsonRequest Volley提供
图片请求 ImageRequestImageLoaderNetworkImageView Volley提供
自定义请求 XMLRequestGSONRequest 开发者自己实现

下面我们一个一个进行学习

StringRequest的用法

StringRequest是继承自Request类的,属于最基本的实现。

使用步骤:
1. 创建RequestQueue对象
2. 创建StringRequest对象
3. 添加StringRequest对象到RequestQueue对象中

是不是很简单!
下面展示一个实例,注释已经很详细了,就不再赘述:

//StringRequest
    private void sendStringRequest(){
        //当然首先尝试一下访问百度了
        String url = "https://www.baidu.com";

        /**
         * 创建一个StringRequest对象
         * 参数说明:
         * 1.请求方法
         * 2.目标服务器的URL地址
         * 3.服务器响应成功的回调
         * 1.服务器响应失败的回调
        **/
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                mTextView.setText("The StringRequest's response is "+ response.substring(0,500));
            }
        }, new Response.ErrorListener(){
            @Override
            public void onErrorResponse(VolleyError error) {
                mTextView.setText("The StringRequest's response is: That didn't work!" );
            }
        });
        //将StringRequest放入请求队列中即可
        mQueue.add(stringRequest);
        /*以下为POST方式,需要传递数据时的处理
        StringRequest stringRequestPost = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

            }
        }){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String,String> map = new HashMap<String,String>();
                map.put("param1","value1");
                map.put("param2","value2");
                return map;
            }
        }
        mQueue.add(stringRequestPost);
        */

    }

JsonRequest

JsonRequest是很类似StringRequest的,同样继承自Request类,这也是为什么我把二者都归为简单Request的原因。
不过JsonRequest是一个抽象类,其有两个实现类分别为JsonObjectRequestJsonArrayRequest,从字面上我们都可以才得到:前者请求一段JSON数据,后者请求一段JSON数组

使用步骤:
1. 创建RequestQueue对象
2. 创建JsonObjectRequestJsonArrayRequest对象
3. 2放入1中

实例:

//JSONRequest
    private void sendJSONRequest(){
        //网上找到的一个天气的API,暂时可用,稳定性待测= =
        String url = "https://www.sojson.com/open/api/weather/json.shtml?city=北京";
        //JsonObjectRequest和JsonArrayRequest是JsonRequest(抽象类)的子类
        //前者请求JSON数据,后者请求JSON数组
        /**
         * 参数说明:
         * 1:请求方法
         * 2:服务器的URL地址
         * 3:POST方式传递的JSON数据,如果为空则表示POST方式没有要提交的参数
         * 4:接收响应成功时返回的JSON数据的监听器
         * 4:接收响应失败时返回的错误信息的监听器
         */
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                mTextView.setText("The JSONRequest's response is " + response.toString());
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                mTextView.setText("The JSONRequest's response is: That didn't work!" );
            }
        });
        //将请求放入请求队列
        mQueue.add(jsonObjectRequest);
    }

是不是发现JsonObjectRequest的用法和StringRequest的用法基本一致?这也是Volley的强大之处,我们只要了解最基本的StringRequest,就很容易举一反三。
提前剧透以下,后续的自定义Request本质上就是变了形的StringRequest

ImageRequest

ImageRequest是一个图片请求对象,继承自Request<Bitmap>,得到的结果是一个Bitmap。不过现在已经是过时的方法了。推荐后续下面两种图片请求方式。

使用步骤:
1. 创建RequestQueue对象。
2. 创建ImageRequest对象。
3. 将ImageRequest对象添加到RequestQueue里面。

实例

/ImageRequest
    private void useImageRequest() {
        //url地址,我的简书的头像= =
        String url = "https://upload.jianshu.io/users/upload_avatars/11024422/9960fc0a-0e86-4a1b-ba25-bca296e674c9.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/300";
        //创建ImageRequest对象
        /**
         * 参数说明:
         * 1. URL
         * 2. 图片请求成功的回调
         * 34. 指定图片最大的宽度和高度,如果网络图片高度或宽度大于这里的最大值,就会对图片进行压缩
         * 如果指定为0则表示不管图片多大都不会压缩图片
         * 5. 指定颜色属性
         * 6. 图片请求失败的回调
         */
        ImageRequest imageRequest = new ImageRequest(url, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                mImageView.setImageBitmap(response);
            }
        }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                mImageView.setImageResource(R.mipmap.ic_launcher);
            }
        });
        mQueue.add(imageRequest);
    }

是不是又双发现,这个使用步骤惊人的相似!这是因为它和上述两种基本Request同继承自Request,因此处理方式自然不会有很大差异。

ImageLoader

ImageLoader同样用来加载网络图片,其内部基于ImageRequest,但是比ImageRequest高效的多。它的构造器可以传入一个ImageCache缓存形参,实现了图片缓存的功能,同时还可以过滤重复链接,避免重复发送请求。

  • 有一点显著的不同的是,ImageLoader加载图片会先显示默认的图片,等待图片加载完成才会显示在ImageView上。

使用步骤
1. 创建RequestQueue对象。
2. 创建ImageLoader对象。
3. 获取ImageListener对象。
4. 调用ImageLoader.get()方法加载网络上的图片。

实例:

private void useImageLoader() {
        //简书头像的url
        String url = "https://upload.jianshu.io/users/upload_avatars/11024422/9960fc0a-0e86-4a1b-ba25-bca296e674c9.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/300";
        //创建ImageLoader
        /**
         * 参数说明:
         * 1. 请求队列
         * 2. ImageCache,显然想实现一个性能好的缓存,避免不了使用LruChcae
         * 由于现在对LruCache不甚了解,就放上一个空ImageCache
         */
        ImageLoader imageLoader = new ImageLoader(mQueue, new ImageLoader.ImageCache() {
            @Override
            public Bitmap getBitmap(String url) {
                return null;
            }

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

            }
        });
        //创建ImageListener对象
        /**
         * 参数说明:
         * 1. 指定显示图片的ImageView控件
         * 2. 加载过程中显示的图片
         * 3. 加载失败时显示的图片
         */
        ImageLoader.ImageListener listener = ImageLoader.getImageListener(
                mImageView,R.mipmap.load,R.mipmap.failure);
        //加载图片
        imageLoader.get(url,listener);
        /*
        //重载方法,可对大小进行限制
        imageLoader.get(url, listener, 200, 200);
        */
    }

具体实现见示例注释即可,参数说明很详细。

NetworkImageView

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

使用步骤:
1. 创建RequestQueue对象。
2. 创建ImageLoader对象。
3. 在布局文件中添加NetworkImageView控件。
4. 获取控件的实例。
5. 设置加载图片地址。

实例:

private void useNetworkImageView() {
        //url
        String url = "https://upload.jianshu.io/users/upload_avatars/11024422/9960fc0a-0e86-4a1b-ba25-bca296e674c9.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/300";

        //创建ImageLoader,参数说明不在重复
        ImageLoader imageLoader = new ImageLoader(mQueue, new ImageLoader.ImageCache() {
            @Override
            public Bitmap getBitmap(String url) {
                return null;
            }

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

            }
        });
        //下面两个方法同ImageLoader.ImageListener listener = ImageLoader.getImageListener(mImageView,R.mipmap.load,R.mipmap.failure);
        //作用是设置加载过程中和加载失败时显示的图片
        mNetworkImageView.setDefaultImageResId(R.mipmap.load);
        mNetworkImageView.setErrorImageResId(R.mipmap.failure);

        //设置加载的图片地址
        mNetworkImageView.setImageUrl(url,imageLoader);
    }
 <com.android.volley.toolbox.NetworkImageView
            android:id="@+id/network_image_view"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>

使用方法和具体实现和ImageLoader基本一致,不过需要注意的是:

NetworkImageView并没有提供设置最大宽度和高度的方法,根据我们设置控件的宽和高结合网络图片的宽和高内部会自动去实现压缩,呈现给我们一张大小刚刚好的网络图片,不占用任何一点多于内存,如果我们不想要压缩可以设置NetworkImageView控件的宽和高都为wrap_content

GSONRequest

前面我们提到过JsonRequest,其返回结果是乱糟糟的JSON数据,解析起来太麻烦了,那么有没有什么办法可以让JSON数据解析变得简单呢答案当然是肯定的,我们可以借助GSON这个工具,遗憾的是Volley本身对此并不支持,下面我们就来自己动手写一个GSONRequest

首先我们需要添加GSON依赖

implementation ‘com.google.code.gson:gson:2.8.2’

我们分析一下GSONRequest如何实现
我们的目的是要更简单的解析JSON数据,为此我们引入了GSON工具,那么我们就可以从JsonRequest入手,而JsonRequestStringRequestImageRequest同继承自Request类,因此我们着手分析最基础的StringRequest类,下面放上其源码:

/**
 * A canned request for retrieving the response body at a given URL as a String.
 */
public class StringRequest extends Request<String> {

    /** Lock to guard mListener as it is cleared on cancel() and read on delivery. */
    private final Object mLock = new Object();

    // @GuardedBy("mLock")
    private 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
    public void cancel() {
        super.cancel();
        synchronized (mLock) {
            mListener = null;
        }
    }

    @Override
    protected void deliverResponse(String response) {
        Response.Listener<String> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {
            listener.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));
    }
}

对源码进行分析:

  • Request可以指定泛型
  • StringRequest持有一个mListener,用来接收响应成功的回调
  • mLock用来维护mListener
  • 提供两个有参的构造函数,前面我们不止一次提到参数的意义了,不再赘述

    构造函数必须调用super(),因为请求和响应在父类中自动处理

  • 抽象方法deliverResponse(),调用mListener中的onResponse()方法,传入response内容,这样我们就可以操作网络响应的数据了

  • 抽象方法parseNetworkResponse(),对服务器相应的数据解析,字节形式存入response.data,之后在取出数据组装成String传入Responsesuccess方法。

下面我们就可以开始编写我们的GsonRequest了,示例如下:

public class GsonRequest<T> extends Request<T> {

    private Gson mGson;
    private Class<T> mClass;
    //private final Map<String, String> headers;
    private final Response.Listener<T> mListener;

    public GsonRequest(int method, String url, Class<T> mClass, Response.Listener<T> listener, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.mClass = mClass;
        this.mListener = listener;
        mGson = new Gson();
    }
    public GsonRequest(String url, Class<T> clazz, Response.Listener<T> listener,
                       Response.ErrorListener errorListener){
        this(Method.GET,url,clazz,listener,errorListener);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            return Response.success(mGson.fromJson(json,mClass),
                    HttpHeaderParser.parseCacheHeaders(response));
        }catch (Exception e){
            return Response.error(new ParseError(e));
        }
    }

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

没有什么太困难的,照猫画虎即可,除了设置泛型以外,核心是通过调用GSONfromJson()方法将数据组成JavaBean对象,之后通过deliverResponse()方法进行回调。

JSON数据示例:
API地址https://www.sojson.com/open/api/weather/json.shtml?city=北京

{
    "date": "20180404",
    "message": "Success !",
    "status": 200,
    "city": "北京",
    "count": 1365,
    "data": {
        "shidu": "85%",
        "pm25": 14.0,
        "pm10": 0.0,
        "quality": "优",
        "wendu": "2",
        "ganmao": "各类人群可自由活动",
        "yesterday": {
            "date": "03日星期二",
            "sunrise": "05:58",
            "high": "高温 13.0℃",
            "low": "低温 4.0℃",
            "sunset": "18:40",
            "aqi": 88.0,
            "fx": "北风",
            "fl": "3-4级",
            "type": "阴",
            "notice": "不要被阴云遮挡住好心情"
        },
        "forecast": [{
            "date": "04日星期三",
            "sunrise": "05:57",
            "high": "高温 10.0℃",
            "low": "低温 1.0℃",
            "sunset": "18:41",
            "aqi": 39.0,
            "fx": "东北风",
            "fl": "<3级",
            "type": "雨夹雪",
            "notice": "道路湿滑,步行开车要谨慎"
        }, {
            "date": "05日星期四",
            "sunrise": "05:55",
            "high": "高温 11.0℃",
            "low": "低温 1.0℃",
            "sunset": "18:42",
            "aqi": 62.0,
            "fx": "西南风",
            "fl": "<3级",
            "type": "多云",
            "notice": "阴晴之间,谨防紫外线侵扰"
        }, {
            "date": "06日星期五",
            "sunrise": "05:53",
            "high": "高温 12.0℃",
            "low": "低温 4.0℃",
            "sunset": "18:43",
            "aqi": 56.0,
            "fx": "西北风",
            "fl": "3-4级",
            "type": "多云",
            "notice": "阴晴之间,谨防紫外线侵扰"
        }, {
            "date": "07日星期六",
            "sunrise": "05:52",
            "high": "高温 14.0℃",
            "low": "低温 3.0℃",
            "sunset": "18:44",
            "aqi": 48.0,
            "fx": "西北风",
            "fl": "3-4级",
            "type": "晴",
            "notice": "愿你拥有比阳光明媚的心情"
        }, {
            "date": "08日星期日",
            "sunrise": "05:50",
            "high": "高温 16.0℃",
            "low": "低温 5.0℃",
            "sunset": "18:45",
            "aqi": 67.0,
            "fx": "南风",
            "fl": "<3级",
            "type": "多云",
            "notice": "阴晴之间,谨防紫外线侵扰"
        }]
    }
}

对应JavaBean类,强烈建议使用GsonFormat插件自动生成

/GsonFormat对应的JavaBean类
public class Weather {

    /**
     * date : 20180404
     * message : Success !
     * status : 200
     * city : 北京
     * count : 1026
     * data : {"shidu":"85%","pm25":14,"pm10":0,"quality":"优","wendu":"2","ganmao":"各类人群可自由活动","yesterday":{"date":"03日星期二","sunrise":"05:58","high":"高温 13.0℃","low":"低温 4.0℃","sunset":"18:40","aqi":88,"fx":"北风","fl":"3-4级","type":"阴","notice":"不要被阴云遮挡住好心情"},"forecast":[{"date":"04日星期三","sunrise":"05:57","high":"高温 10.0℃","low":"低温 1.0℃","sunset":"18:41","aqi":39,"fx":"东北风","fl":"<3级","type":"雨夹雪","notice":"道路湿滑,步行开车要谨慎"},{"date":"05日星期四","sunrise":"05:55","high":"高温 11.0℃","low":"低温 1.0℃","sunset":"18:42","aqi":62,"fx":"西南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"06日星期五","sunrise":"05:53","high":"高温 12.0℃","low":"低温 4.0℃","sunset":"18:43","aqi":56,"fx":"西北风","fl":"3-4级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"07日星期六","sunrise":"05:52","high":"高温 14.0℃","low":"低温 3.0℃","sunset":"18:44","aqi":48,"fx":"西北风","fl":"3-4级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"08日星期日","sunrise":"05:50","high":"高温 16.0℃","low":"低温 5.0℃","sunset":"18:45","aqi":67,"fx":"南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"}]}
     */

    private String date;
    private String message;
    private int status;
    private String city;
    private int count;
    private DataBean data;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public DataBean getData() {
        return data;
    }

    public void setData(DataBean data) {
        this.data = data;
    }

    public static class DataBean {
        /**
         * shidu : 85%
         * pm25 : 14.0
         * pm10 : 0.0
         * quality : 优
         * wendu : 2
         * ganmao : 各类人群可自由活动
         * yesterday : {"date":"03日星期二","sunrise":"05:58","high":"高温 13.0℃","low":"低温 4.0℃","sunset":"18:40","aqi":88,"fx":"北风","fl":"3-4级","type":"阴","notice":"不要被阴云遮挡住好心情"}
         * forecast : [{"date":"04日星期三","sunrise":"05:57","high":"高温 10.0℃","low":"低温 1.0℃","sunset":"18:41","aqi":39,"fx":"东北风","fl":"<3级","type":"雨夹雪","notice":"道路湿滑,步行开车要谨慎"},{"date":"05日星期四","sunrise":"05:55","high":"高温 11.0℃","low":"低温 1.0℃","sunset":"18:42","aqi":62,"fx":"西南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"06日星期五","sunrise":"05:53","high":"高温 12.0℃","low":"低温 4.0℃","sunset":"18:43","aqi":56,"fx":"西北风","fl":"3-4级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"07日星期六","sunrise":"05:52","high":"高温 14.0℃","low":"低温 3.0℃","sunset":"18:44","aqi":48,"fx":"西北风","fl":"3-4级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"08日星期日","sunrise":"05:50","high":"高温 16.0℃","low":"低温 5.0℃","sunset":"18:45","aqi":67,"fx":"南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"}]
         */

        private String shidu;
        private double pm25;
        private double pm10;
        private String quality;
        private String wendu;
        private String ganmao;
        private YesterdayBean yesterday;
        private List<ForecastBean> forecast;

        public String getShidu() {
            return shidu;
        }

        public void setShidu(String shidu) {
            this.shidu = shidu;
        }

        public double getPm25() {
            return pm25;
        }

        public void setPm25(double pm25) {
            this.pm25 = pm25;
        }

        public double getPm10() {
            return pm10;
        }

        public void setPm10(double pm10) {
            this.pm10 = pm10;
        }

        public String getQuality() {
            return quality;
        }

        public void setQuality(String quality) {
            this.quality = quality;
        }

        public String getWendu() {
            return wendu;
        }

        public void setWendu(String wendu) {
            this.wendu = wendu;
        }

        public String getGanmao() {
            return ganmao;
        }

        public void setGanmao(String ganmao) {
            this.ganmao = ganmao;
        }

        public YesterdayBean getYesterday() {
            return yesterday;
        }

        public void setYesterday(YesterdayBean yesterday) {
            this.yesterday = yesterday;
        }

        public List<ForecastBean> getForecast() {
            return forecast;
        }

        public void setForecast(List<ForecastBean> forecast) {
            this.forecast = forecast;
        }

        public static class YesterdayBean {
            /**
             * date : 03日星期二
             * sunrise : 05:58
             * high : 高温 13.0℃
             * low : 低温 4.0℃
             * sunset : 18:40
             * aqi : 88.0
             * fx : 北风
             * fl : 3-4级
             * type : 阴
             * notice : 不要被阴云遮挡住好心情
             */

            private String date;
            private String sunrise;
            private String high;
            private String low;
            private String sunset;
            private double aqi;
            private String fx;
            private String fl;
            private String type;
            private String notice;

            public String getDate() {
                return date;
            }

            public void setDate(String date) {
                this.date = date;
            }

            public String getSunrise() {
                return sunrise;
            }

            public void setSunrise(String sunrise) {
                this.sunrise = sunrise;
            }

            public String getHigh() {
                return high;
            }

            public void setHigh(String high) {
                this.high = high;
            }

            public String getLow() {
                return low;
            }

            public void setLow(String low) {
                this.low = low;
            }

            public String getSunset() {
                return sunset;
            }

            public void setSunset(String sunset) {
                this.sunset = sunset;
            }

            public double getAqi() {
                return aqi;
            }

            public void setAqi(double aqi) {
                this.aqi = aqi;
            }

            public String getFx() {
                return fx;
            }

            public void setFx(String fx) {
                this.fx = fx;
            }

            public String getFl() {
                return fl;
            }

            public void setFl(String fl) {
                this.fl = fl;
            }

            public String getType() {
                return type;
            }

            public void setType(String type) {
                this.type = type;
            }

            public String getNotice() {
                return notice;
            }

            public void setNotice(String notice) {
                this.notice = notice;
            }
        }

        public static class ForecastBean {
            /**
             * date : 04日星期三
             * sunrise : 05:57
             * high : 高温 10.0℃
             * low : 低温 1.0℃
             * sunset : 18:41
             * aqi : 39.0
             * fx : 东北风
             * fl : <3级
             * type : 雨夹雪
             * notice : 道路湿滑,步行开车要谨慎
             */

            private String date;
            private String sunrise;
            private String high;
            private String low;
            private String sunset;
            private double aqi;
            private String fx;
            private String fl;
            private String type;
            private String notice;

            public String getDate() {
                return date;
            }

            public void setDate(String date) {
                this.date = date;
            }

            public String getSunrise() {
                return sunrise;
            }

            public void setSunrise(String sunrise) {
                this.sunrise = sunrise;
            }

            public String getHigh() {
                return high;
            }

            public void setHigh(String high) {
                this.high = high;
            }

            public String getLow() {
                return low;
            }

            public void setLow(String low) {
                this.low = low;
            }

            public String getSunset() {
                return sunset;
            }

            public void setSunset(String sunset) {
                this.sunset = sunset;
            }

            public double getAqi() {
                return aqi;
            }

            public void setAqi(double aqi) {
                this.aqi = aqi;
            }

            public String getFx() {
                return fx;
            }

            public void setFx(String fx) {
                this.fx = fx;
            }

            public String getFl() {
                return fl;
            }

            public void setFl(String fl) {
                this.fl = fl;
            }

            public String getType() {
                return type;
            }

            public void setType(String type) {
                this.type = type;
            }

            public String getNotice() {
                return notice;
            }

            public void setNotice(String notice) {
                this.notice = notice;
            }
        }
    }
}

具体解析调用:

private void useGSONRequest() {
        String url = "https://www.sojson.com/open/api/weather/json.shtml?city=北京";

        GsonRequest<Weather> gsonRequest = new GsonRequest<Weather>(url, Weather.class,
                new Response.Listener<Weather>() {
                    @Override
                    public void onResponse(Weather response) {
                        Weather.DataBean dataBean = response.getData();
                        List<Weather.DataBean.ForecastBean> forecastList = dataBean.getForecast();
                        Weather.DataBean.YesterdayBean yesterday = dataBean.getYesterday();

                        StringBuilder builder = new StringBuilder();
                        builder.append("日期:"+ response.getDate() + "\n");
                        builder.append("城市:"+ response.getCity() + "\n" + "\n");

                        builder.append("当前天气" + "\n");
                        builder.append("湿度: "+dataBean.getShidu()+"\n");
                        builder.append("PM25: "+dataBean.getPm25()+"\n");
                        builder.append("PM10: "+dataBean.getPm10()+"\n");
                        builder.append("空气质量: "+dataBean.getQuality()+"\n");
                        builder.append("温度: "+dataBean.getWendu()+"\n");
                        builder.append("感冒指数: "+dataBean.getGanmao()+"\n"+ "\n");

                        builder.append("昨日天气" + "\n");
                        builder.append("日期: "+yesterday.getDate()+"\n");
                        builder.append("日出时间: "+yesterday.getSunrise()+"\n");
                        builder.append("最高温度: "+yesterday.getHigh()+"\n");
                        builder.append("最低温度: "+yesterday.getLow()+"\n");
                        builder.append("日落时间: "+yesterday.getSunset()+"\n");
                        builder.append("AQI: "+yesterday.getAqi()+"\n");
                        builder.append("风向: "+yesterday.getFx()+"\n");
                        builder.append("风力: "+yesterday.getFl()+"\n");
                        builder.append("天气: "+yesterday.getType()+"\n");
                        builder.append("注意: "+yesterday.getNotice()+"\n"+ "\n");
                       for (Weather.DataBean.ForecastBean forecast : forecastList){
                            builder.append("未来天气:" + "\n");
                           builder.append("日期: "+forecast.getDate()+"\n");
                           builder.append("日出时间: "+forecast.getSunrise()+"\n");
                           builder.append("最高温度: "+forecast.getHigh()+"\n");
                           builder.append("最低温度: "+forecast.getLow()+"\n");
                           builder.append("日落时间: "+forecast.getSunset()+"\n");
                           builder.append("AQI: "+forecast.getAqi()+"\n");
                           builder.append("风向: "+forecast.getFx()+"\n");
                           builder.append("风力: "+forecast.getFl()+"\n");
                           builder.append("天气: "+forecast.getType()+"\n");
                           builder.append("注意: "+forecast.getNotice()+"\n"+ "\n");
                        }
                        mTextView.setText(builder);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                mTextView.setText(error.getMessage());
            }
        });
        mQueue.add(gsonRequest);
    }

XMLRequest

了解上面的原理之后,我们可以很容易的编写XMLRequest
实例:

public class XMLRequest extends Request<XmlPullParser> {

    private final Response.Listener<XmlPullParser> mListener;

    public XMLRequest(int method, String url, Response.Listener<XmlPullParser> listener, Response.ErrorListener errorListener) {

        super(method, url, errorListener);
        mListener = listener;
    }

    public XMLRequest(String url, Response.Listener<XmlPullParser> listener, Response.ErrorListener errorListener){
        this(Method.GET,url,listener,errorListener);
    }

    @Override
    protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {
        try {
            String xmlString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser parser = factory.newPullParser();
            parser.setInput(new StringReader(xmlString));
            return Response.success(parser,HttpHeaderParser.parseCacheHeaders(response));
        }catch (Exception e){
            return Response.error(new ParseError(e));
        }
    }

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

我们通过PULL方式进行解析,因此最后返回一个XmlPullParser对象。

XML文件示例
API地址https://www.sojson.com/open/api/weather/xml.shtml?city=北京

<resp>
<city>北京</city>
<updatetime>23:31</updatetime>
<wendu>3</wendu>
<fengli>
<![CDATA[ 2级 ]]>
</fengli>
<shidu>85%</shidu>
<fengxiang>东风</fengxiang>
<sunrise_1>05:57</sunrise_1>
<sunset_1>18:41</sunset_1>
<sunrise_2/>
<sunset_2/>
<environment>
<aqi>34</aqi>
<pm25>13</pm25>
<suggest>各类人群可自由活动</suggest>
<quality></quality>
<MajorPollutants/>
<o3>44</o3>
<co>0</co>
<pm10>10</pm10>
<so2>2</so2>
<no2>27</no2>
<time>23:00:00</time>
</environment>
<yesterday>
<date_1>2日星期一</date_1>
<high_1>高温 26℃</high_1>
<low_1>低温 11℃</low_1>
<day_1>
<type_1>多云</type_1>
<fx_1>东北风</fx_1>
<fl_1>
<![CDATA[ <3级 ]]>
</fl_1>
</day_1>
<night_1>
<type_1>多云</type_1>
<fx_1>东北风</fx_1>
<fl_1>
<![CDATA[ 3-4级 ]]>
</fl_1>
</night_1>
</yesterday>
<forecast>
<weather>
<date>3日星期二</date>
<high>高温 13℃</high>
<low>低温 2℃</low>
<day>
<type></type>
<fengxiang>北风</fengxiang>
<fengli>
<![CDATA[ 3-4级 ]]>
</fengli>
</day>
<night>
<type></type>
<fengxiang>东北风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</night>
</weather>
<weather>
<date>4日星期三</date>
<high>高温 10℃</high>
<low>低温 1℃</low>
<day>
<type>雨夹雪</type>
<fengxiang>东北风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</day>
<night>
<type>雨夹雪</type>
<fengxiang>北风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</night>
</weather>
<weather>
<date>5日星期四</date>
<high>高温 11℃</high>
<low>低温 1℃</low>
<day>
<type>多云</type>
<fengxiang>西南风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</day>
<night>
<type>多云</type>
<fengxiang>西风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</night>
</weather>
<weather>
<date>6日星期五</date>
<high>高温 12℃</high>
<low>低温 4℃</low>
<day>
<type>多云</type>
<fengxiang>西北风</fengxiang>
<fengli>
<![CDATA[ 3-4级 ]]>
</fengli>
</day>
<night>
<type>多云</type>
<fengxiang>西北风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</night>
</weather>
<weather>
<date>7日星期六</date>
<high>高温 14℃</high>
<low>低温 3℃</low>
<day>
<type></type>
<fengxiang>西北风</fengxiang>
<fengli>
<![CDATA[ 3-4级 ]]>
</fengli>
</day>
<night>
<type>多云</type>
<fengxiang>东北风</fengxiang>
<fengli>
<![CDATA[ <3级 ]]>
</fengli>
</night>
</weather>
</forecast>
<zhishus>
<zhishu>
<name>晨练指数</name>
<value>较不宜</value>
<detail>有降水,较不宜晨练,室外锻炼请携带雨具。建议年老体弱人群适当减少晨练时间。</detail>
</zhishu>
<zhishu>
<name>舒适度</name>
<value>较舒适</value>
<detail>白天会有降雪,这种天气条件下,人们会感到有些凉意,但大部分人完全可以接受。</detail>
</zhishu>
<zhishu>
<name>穿衣指数</name>
<value>较冷</value>
<detail>建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。</detail>
</zhishu>
<zhishu>
<name>感冒指数</name>
<value>较易发</value>
<detail>天气较凉,较易发生感冒,请适当增加衣服。体质较弱的朋友尤其应该注意防护。</detail>
</zhishu>
<zhishu>
<name>晾晒指数</name>
<value>不宜</value>
<detail>有降雪,不适宜晾晒。若需要晾晒,请在室内准备出充足的空间。</detail>
</zhishu>
<zhishu>
<name>旅游指数</name>
<value>适宜</value>
<detail>温度适宜,同时又有微风伴您一路同行。比较适宜旅游,但有降雪,出行请注意携带雨具。</detail>
</zhishu>
<zhishu>
<name>紫外线强度</name>
<value>最弱</value>
<detail>属弱紫外线辐射天气,无需特别防护。若长期在户外,建议涂擦SPF在8-12之间的防晒护肤品。</detail>
</zhishu>
<zhishu>
<name>洗车指数</name>
<value>不宜</value>
<detail>不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。</detail>
</zhishu>
<zhishu>
<name>运动指数</name>
<value>较不宜</value>
<detail>有降雪,推荐您在室内进行低强度运动;若坚持户外运动,请选择合适运动并注意保暖。</detail>
</zhishu>
<zhishu>
<name>约会指数</name>
<value>较不适宜</value>
<detail>室外有风,而且有降雪,会给室外约会带来不便,如果外出约会,请一定做好准备。</detail>
</zhishu>
<zhishu>
<name>雨伞指数</name>
<value>带伞</value>
<detail>将有降雪,您在外出的时候一定要带雨伞,以免弄湿衣物着凉。</detail>
</zhishu>
</zhishus>
</resp>

代码中具体解析示例

private void useXMLRequest() {
        String url = "https://www.sojson.com/open/api/weather/xml.shtml?city=%E5%8C%97%E4%BA%AC";
        XMLRequest xmlRequest = new XMLRequest(url, 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 name = response.getName();
                                if ("city".equals(name)){
                                    mTextView.setText(response.nextText());                                }
                                break;
                        }
                        eventType = response.next();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                mTextView.setText(error.getMessage());
            }
        });
        mQueue.add(xmlRequest);
    }

偷个懒,我就不具体解析这段XML了(笑~),大家可以自己练练手,熟悉XMLRequest的同时也可以复习以下基于PULL方式的XML解析。


总结

  • 本文详细介绍了Volley,包括简单请求、图片请求以及自定义请求的使用。
  • 笔者水平有限,如有错漏,欢迎指正。
  • 接下来我将持续推出Android网络相关的一系列文章,包括HttpURLConnection、Volley、OkHttp3、Retrofit2的使用等,有兴趣可以关注whd_Alive的Android开发笔记
  • 不定期分享Android开发相关的技术干货,期待与你的交流,共勉。

猜你喜欢

转载自blog.csdn.net/whdalive/article/details/80262430