Android中Volley的使用及部分源码分析

本篇博客主要记录一下Volley的基本用法,为之后分析Volley源码做一下铺垫。


在分析Volley的具体用法前,我们先来简单了解一下Volley:

Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。

Volley非常适合进行数据量不大,但通信频繁的网络操作,包括图片下载等。
对于大数据量的网络操作,比如说下载文件等,Volley的表现比较糟糕。

在Android源码中,Volley相关的代码被放置在frameworks/volley目录下。


我们还是以一个Demo的形式,来看看Volly的用法,demo的主界面如下图所示:

如图所示,界面的上方有一排按键,对应着使用Volley访问网络时,常用的一些功能。
界面左下方是用ScrollView包裹的TextView,以字符的形式显示网络访问的结果;
界面的右下方,将以图片的形式显示网络访问的结果。

界面布局及初始化组件的代码,就不在博客中记录了,我们主要看一下不同按键被点击后的操作。


在使用Volley前,应用需要配置网络访问的权限:

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

然后在应用启动时,创建Volley的RequestQueue,我的demo在onCreate函数中创建的:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //使用Volley的静态方法即可,传入Context
    //这里我用的是ApplicationContext,因为我还没仔细看过Volley的源码,
    //所以给它ApplicationContext最稳妥,避免内存泄露
    mRequestQueue = Volley.newRequestQueue(getApplicationContext());

    findChildView();
    configChildView();
}

按照网上的说法,Volley的RequestQueue是一个请求队列,
它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。
因此我们不必为每一次HTTP请求都创建一个RequestQueue。


有了RequestQueue后,我们就可构造网络请求Request了。
这就如同Handler的Looper运行后,就可以往里放Message了。

我们首先看看第一个按键被点击后的操作,
这个按键主要用于向网络发送一个StringRequest,
对应代码如下:

    .............
    private void makeStringRequest() {
        //构造StringRequest
        StringRequest stringRequest = new StringRequest(
                //指定Url
                "https://www.baidu.com/",
                //成功回调接口
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        mTextView.setText(response);

                    }
                },
                //失败回调接口
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        mTextView.setText(error.toString());
                    }
                });

        //将StringRequest加入到RequestQueue中
        mRequestQueue.add(stringRequest);
    }
    .............

StringRequest的含义其实就是,网络返回的结果为String。

我们看看源码中StringRequest的构造函数:

    /**
     * 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);
    }

    /**
     * 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;
    }

从StringRequest中源码的注释可以看出,
StringRequest可以携带网络对应的地址、访问成功和失败的回调接口,
同时可以指定本次请求对应的Http Method Type。

在demo中,我们发送的是一个Http Get请求,即试图从网络中得到信息;
同样,我们可以发送一个Http Post请求,只要指定StringRequest的method为Method.Post即可。

不过StringRequest并不能直接携带Post参数,如果需要只能手动创建一个匿名类,
并重写其父类的getParams方法,代码类似于:

    StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {  
            @Override  
            protected Map<String, String> getParams() throws AuthFailureError {  
                Map<String, String> map = new HashMap<String, String>();  
                map.put("params1", "value1");  
                map.put("params2", "value2");  
                return map;  
            }  
    };  

源码中关于getParams的注释如下:

    /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }

打开网络的前提下,StringRequest的demo运行效果如下:

ScrollView可以滑动显示全部信息。


点击第二个按键,demo将向网络发送一个JSONRequest。
其基本操作与StringRequest类似,差别仅是要求网络以JSONObject的格式返回结果。
对应代码如下:

    private void makeJsonRequest() {
        //构造一个url,我这里用的是百度图片的url
        String url = Uri.parse("http://image.baidu.com/search/index?")
                .buildUpon()
                .appendQueryParameter("tn", "resultjson")
                .appendQueryParameter("word", "微距摄影")
                .build().toString();

        //构造一个JSONObjectRequest
        JsonObjectRequest objectRequest = new JsonObjectRequest(
                //多了第二个参数,这里可以传入一个JSONObject
                //为null时,表示我们发送的是Http Get
                //若不为null,表示发送的是Http Post
                //也可以显示指定Http Method Type
                url, null,
                new Response.Listener<JSONObject>(){
                    @Override
                    public void onResponse(JSONObject response) {
                        mTextView.setText(response.toString());
                    }
                },
                new Response.ErrorListener(){
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        mTextView.setText(error.toString());
                    }
                });

        //加入队列
        mRequestQueue.add(objectRequest);
    }

demo发送JSONRequest后的效果如下图:

按照同样的写法,还可以使用JSONArrayRequest,此处就不做赘述了。


点击第三个按键,demo将向网络发送一个Image Request,用于下载图片。
对应代码如下:

private void makeImageRequest() {
        //随便找了张百度图片,看看网页的Html代码,就可盗用其url
        String url = "http://img3.imgtn.bdimg.com/it/u=1382485185,1958087964&fm=23&gp=0.jpg";

        ImageRequest imageRequest = new ImageRequest(url,
                new Response.Listener<Bitmap>() {
                    @Override
                    public void onResponse(Bitmap response) {
                        //设置下载得到的图片
                        mImageView.setImageBitmap(response);
                    }
                //与之前的差别主要在这里
                //分别指定下载图片的最大宽度、最大高度及颜色属性
                //如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩
                //指定成0的话就表示不管图片有多大,都不会进行压缩
                }, 200, 200, Bitmap.Config.ARGB_8888,
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        //设置一个失败后的图片
                        //demo偷懒了一下,直接放了个启动图片
                        mImageView.setImageResource(R.mipmap.ic_launcher);
                    }
                }
        );
        mRequestQueue.add(imageRequest);
    }

可以看到除了多了几个修饰图片的参数外,ImageRequest与之前的用法也基本一致。

demo发送ImageRequest后的效果如下:


接下来我们看看Volley中ImageLoader的用法:

    ..................
    private LocalImageCache mLocalImageCache = new LocalImageCache();
    private ImageLoader mImageLoader;

    private void loadImage() {
        if (mImageLoader == null) {
            //ImageLoader需要和RequestQueue、ImageCache绑定
            mImageLoader = new ImageLoader(mRequestQueue, mLocalImageCache);
        }

        //与前面的Request一样,ImageLoader也需要定义对应的回调接口
        //Volley中的ImageLoader提供了创建回调接口的方法
        //可以依次指定图片下载后需要放置的位置,默认图片(下载时间较长时,临时显示的背景图)及下载失败的图片
        ImageLoader.ImageListener imageListener = ImageLoader
                .getImageListener(mImageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);

        String url = "http://img1.imgtn.bdimg.com/it/u=1245538184,752165177&fm=23&gp=0.jpg";
        //最后,利用ImageLoader的get方法获取图片
        //从这里可以看出,ImageLoader也可以指定下载图片的宽、高
        mImageLoader.get(url, imageListener, 200, 200);
    }

    //这里的ImageCache实际上就是一个存储Bitmap的LruCache
    private class LocalImageCache implements ImageLoader.ImageCache {
        private LruCache<String, Bitmap> mCache;

        LocalImageCache() {
            int maxSize = 10 * 1024 * 1024;

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

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

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

从上面的代码不难看出,ImageLoader与ImageRequest相比,最大的优势就是引入了Cache。
这样当终端请求同样网址的信息时,会优先从Cache中取出结果;
Cache中没有对应结果时,才会向网络发起请求。

demo利用ImageLoader下载图片的效果如下:

(换了张图片,显示在同样的位置)


接下来,我们看看Volley中定义的控件NetworkImageView的用法。
先来看看demo中最初使用的代码:

    ..............
    private void refreshNetworkImageView() {
        //在我的布局文件中,并没有直接使用NetworkImageView
        //仅仅用一个FrameLayout作为Container
        if (mNetworkImageViewContainer.getChildCount() != 0) {
            mNetworkImageViewContainer.removeAllViewsInLayout();
        }

        //动态创建NetworkImageView
        NetworkImageView networkImageView = new NetworkImageView(this);
        networkImageView.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));

        if (mImageLoader == null) {
            mImageLoader = new ImageLoader(mRequestQueue, mLocalImageCache);
        }

        //NetworkImageView也能设置默认图片和错误图片
        networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
        networkImageView.setErrorImageResId(R.mipmap.ic_launcher);

        //然后设置url和ImageLoader,不难看出,NetworkImageView的实际下载工作,也将交给ImageLoader来完成
        networkImageView.setImageUrl("http://img1.imgtn.bdimg.com/it/u=494242567,3557760312&fm=11&gp=0.jpg",
                mImageLoader);

        //显示NetworkImageView
        mNetworkImageViewContainer.addView(networkImageView);
    }
    ....................

按照上述代码,每次点击对应按键时,都会重新创建NetworkImageView,然后进行图片下载工作。

这里我为什么选择动态创建NetworkImageView呢?
因为最初时我没有找到更合适的办法。

我自己测试了一下,按照上述代码,当在布局中直接使用NetworkImageView时,
上述代码修改为:

    ........
    if (mImageLoader == null) {
        mImageLoader = new ImageLoader(mRequestQueue, mLocalImageCache);
    }

    //直接从布局中取得networkImageView,不再动态创建了
    networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
    networkImageView.setErrorImageResId(R.mipmap.ic_launcher);
    networkImageView.setImageUrl("http://img1.imgtn.bdimg.com/it/u=494242567,3557760312&fm=11&gp=0.jpg",
            mImageLoader);
    ...........

在这种情况下,当网络处于断开时,点击NetworkImageView对应的按键,NetworkImageView将显示错误图片。
然后连接网络,再次点击对应的按键,发现NetworkImageView并不会进行下载操作。

为了弄懂这个问题,自己看了看NetworkImageView的源码:

    ..................
    //控件被绘制时,会调用loadImageIfNecessary,参数为true
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        loadImageIfNecessary(true);
    }
    ..................

    //给控件设置url和imageLoader时,也会调用loadImageIfNecessary,参数为false
    public void setImageUrl(String url, ImageLoader imageLoader) {
        setImageUrl(url, imageLoader, null);
    }

    public void setImageUrl(String url, ImageLoader imageLoader, ImageURLBuilder urlBuilder) {
        mUrl = url;
        mImageLoader = imageLoader;
        mUrlBuilder = urlBuilder;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }
    ...................

从这部分代码,可以看出当NetworkImageView被绘制后,或调用setImageUrl时,均会调用loadImageIfNecessary方法。
我们跟进该方法:

void loadImageIfNecessary(final boolean isInLayoutPass) {
    //图像宽高处理
    ..........
    // if the URL to be loaded in this view is empty, cancel any old requests and clear the
    // currently loaded image.
    // 这里很重要!,后问分析
    if (TextUtils.isEmpty(mUrl)) {
        if (mImageContainer != null) {
            mImageContainer.cancelRequest();
            mImageContainer = null;
        }
        setDefaultImageOrNull();
        return;
    }
    ...........
    // rebuild url if needed
    String tmpUrl = mUrl;
    if (mUrlBuilder != null) {
        tmpUrl = mUrlBuilder.buildUrl(
                mUrl, maxWidth, maxHeight, getScaleType(), getContext().getApplicationContext());
    }

    // if there was an old request in this view, check if it needs to be canceled.
    // 问题就出在这里,后文分析
    if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
        if (mImageContainer.getRequestUrl().equals(tmpUrl)) {
            // if the request is from the same URL, return.
            return;
         } else {
             // if there is a pre-existing request, cancel it if it's fetching a different URL.
             mImageContainer.cancelRequest();
             setDefaultImageOrNull();
         }
    }

    // The pre-existing content of this view didn't match the current URL. Load the new image
    // from the network.
    // 进行下载工作,并创建ImageContainer
    ImageContainer newContainer = mImageLoader.get(tmpUrl,
            new ImageListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    if (mErrorImageId != 0) {
                        setImageResource(mErrorImageId);
                    }
                }

                @Override
                public void onResponse(final ImageContainer response, boolean isImmediate) {
                    // If this was an immediate response that was delivered inside of a layout
                    // pass do not set the image immediately as it will trigger a requestLayout
                    // inside of a layout. Instead, defer setting the image by posting back to
                    // the main thread.
                    // 这里可以看出,输入参数仅辅助决定是否立即显示回复信息
                    if (isImmediate && isInLayoutPass) {
                        post(new Runnable() {
                            @Override
                            public void run() {
                                onResponse(response, false);
                            }
                        });
                        return;
                    }

                    if (response.getBitmap() != null) {
                        setImageBitmap(response.getBitmap());
                    } else if (mDefaultImageId != 0) {
                        setImageResource(mDefaultImageId);
                    }
                }
            }, maxWidth, maxHeight, scaleType, mTransformation);

    // update the ImageContainer to be the new bitmap container.
    mImageContainer = newContainer;
}

根据上面的代码,我们看一下直接在布局中使用NetworkImageView时的流程:
1、布局被绘制时,我们并没有设置url及ImageLoader,
因此当onLayout被调用时,尽管loadImageIfNecessary被调用,但在上述代码的这个位置将阻止下载工作:

    // if the URL to be loaded in this view is empty, cancel any old requests and clear the
    // currently loaded image.
    if (TextUtils.isEmpty(mUrl)) {
        if (mImageContainer != null) {
            mImageContainer.cancelRequest();
            mImageContainer = null;
        }

        //url为null,因此加载的是defaultnull image
        setDefaultImageOrNull();
        return;
    }

2、首次为NetworkImageView设置url和imageLoader,仍会调用loadImageIfNecessary方法。
这次在loadImageIfNecessary中,将利用mImageLoader创建出mImageContainer,并进行下载操作。
然而一旦网络不可用,将调用setImageResource(mErrorImageId)设置错误图片,详见上文中的代码。

3、当网络可用时,我们再次为NetworkImageView设置同样的url和imageLoader,
触发loadImageIfNecessary方法,此时这部分代码将生效:

    // if there was an old request in this view, check if it needs to be canceled.
    if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
        //由于之前已经创建过mImageContainer,且使用的是相同的url,因此此处直接返回
        //不再进行后面的下载操作了
        if (mImageContainer.getRequestUrl().equals(tmpUrl)) {
            // if the request is from the same URL, return.
            return;
        } else {
            // if there is a pre-existing request, cancel it if it's fetching a different URL.
            mImageContainer.cancelRequest();
            setDefaultImageOrNull();
        }
    }

因此,当网络发生变化时,对于同一个url,必须清除旧有的mImageContainer,才能进行下载工作。
因此demo代码中,最初的设计是每次点击按键,都重新加载新的NetworkImageView。

不过这么做着实麻烦,我们再来回头看看loadImageIfNecessary的代码:

    // if the URL to be loaded in this view is empty, cancel any old requests and clear the
    // currently loaded image.
    // 只要每次点击按键时,先传入一个empty url不就可以清除旧有的ImageContainer么!!!
    if (TextUtils.isEmpty(mUrl)) {
        if (mImageContainer != null) {
            mImageContainer.cancelRequest();
            mImageContainer = null;
        }
        setDefaultImageOrNull();
        return;
   }

最终修改demo如下:

    private void refreshNetworkImageView() {
        if (mImageLoader == null) {
            mImageLoader = new ImageLoader(mRequestQueue, mLocalImageCache);
        }

        mNetworkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
        mNetworkImageView.setErrorImageResId(R.mipmap.ic_launcher);

        //相当于每次点击时,强制刷新一下
        mNetworkImageView.setImageUrl("", mImageLoader);

        mNetworkImageView.setImageUrl("http://img1.imgtn.bdimg.com/it/u=494242567,3557760312&fm=11&gp=0.jpg",
                mImageLoader);
    }

按照这种方式,当网络从断开变为连接时,重新点击对应按键,可以加载正确的图片。
整个demo运行效果如下:

最后提一下:
NetworkImageView并不需要提供任何设置最大宽、高的方法也能够对加载的图片进行压缩。

这是由于NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,
然后对比网络图片的宽度,决定是否需要对图片进行压缩,即压缩过程是在内部完全自动化的。

如果你不想对图片进行压缩的话,只需要在布局文件中,
把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了。
这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。


以上就是Volley中比较基本的用法,接下来看看如何利用Volley框架定制自己的Request。
我们首先看看Volley原生的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);
    }

    //需要覆盖的函数,将结果递交给listener
    @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));
    }
}

有了StringRequest的源码后,我们依葫芦画瓢即可写出自定义的Request。
demo中定义了XMLRequest,用于获取网络的xml文件:

//基本定义与StringRequest一致,只是将模板参数替换为XmlPullParser
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 {
            //这里指定以UTF-8格式,解析返回的结果
            String xmlString = new String(response.data,
                    "UTF-8");

            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();

            //用XmlPullParser封装网络返回的字符
            xmlPullParser.setInput(new StringReader(xmlString));

            //然后返回结果
            return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (XmlPullParserException e) {
            return Response.error(new ParseError(e));
        }
    }

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

我们看看XMLRequest的使用:

    //点击最后一个按键,触发该该函数
    private void makeXmlRequest() {
        XMLRequest xmlRequest = new XMLRequest(
                //设置url
                "http://flash.weather.com.cn/wmaps/xml/china.xml",
                new Response.Listener<XmlPullParser>() {
                    @Override
                    public void onResponse(XmlPullParser response) {
                        //我是在另一个界面显示结果的
                        //getWeatherInfo函数,负责从XmlPullParser中获取数据
                        Intent intent = CustomActivity.getIntent(
                                getApplicationContext(), getWeatherInfo(response));
                        getApplicationContext().startActivity(intent);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Toast.makeText(getApplicationContext(), error.toString(),
                                Toast.LENGTH_LONG).show();
                    }
                });

        //将Request加入到RequestQueue中
        mRequestQueue.add(xmlRequest);
    }

http://flash.weather.com.cn/wmaps/xml/china.xml“对应网址的xml文件格式如下:

根据xml文件格式,对应的解析函数如下:

    private ArrayList<WeatherModel> getWeatherInfo(XmlPullParser xmlPullParser) {
        ArrayList<WeatherModel> rst = new ArrayList<>();
        WeatherModel weatherModel;

        try {
            int eventType = xmlPullParser.getEventType();

            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_TAG:
                        //找到city对应的元素
                        if (xmlPullParser.getName().equals("city")) {
                            weatherModel = new WeatherModel();

                            //读取我们需要的属性,并保存
                            weatherModel.mProvinceName = xmlPullParser.getAttributeValue(0);
                            weatherModel.mCityName = xmlPullParser.getAttributeValue(2);
                            weatherModel.mStateDetailed = xmlPullParser.getAttributeValue(5);
                            weatherModel.mTemperature = xmlPullParser.getAttributeValue(7)
                                    + "~" + xmlPullParser.getAttributeValue(6);

                            weatherModel.mWindState = xmlPullParser.getAttributeValue(8);
                            rst.add(weatherModel);
                        }
                        break;
                }

                eventType = xmlPullParser.next();
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return rst;
    }

解析完成后,就可以在另一个界面显示了。
此处WeatherModel为我定义的保存信息的模型:

//为了便于bundle传输信息,继承了Parcelable接口
public class WeatherModel implements Parcelable{
    String mProvinceName;
    String mCityName;
    String mStateDetailed;
    String mTemperature;
    String mWindState;

    public WeatherModel() {
    }

    public WeatherModel(Parcel in) {
        mProvinceName = in.readString();
        mCityName = in.readString();
        mStateDetailed = in.readString();
        mTemperature = in.readString();
        mWindState = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mProvinceName);
        dest.writeString(mCityName);
        dest.writeString(mStateDetailed);
        dest.writeString(mTemperature);
        dest.writeString(mWindState);
    }

    public static final Parcelable.Creator<WeatherModel> CREATOR =
            new Parcelable.Creator<WeatherModel> () {
                @Override
                public WeatherModel createFromParcel(Parcel source) {
                    return new WeatherModel(source);
                }

                @Override
                public WeatherModel[] newArray(int size) {
                    return new WeatherModel[size];
                }
            };

    @Override
    public String toString() {
        return mProvinceName + ", " + mCityName + ", "
                + mStateDetailed + ", " + mTemperature + ", " + mWindState;
    }
}

最后,demo显示的效果如下:


至此,Volley框架的基本用法记录完毕,之后再来分析一下Volley框架整体的源码。
本文涉及demo地址如下:
https://github.com/ZhangJianIsAStark/Demos/tree/master/volleytest

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值