Volley
常用Request对象使用
StringRequest用于请求一条普通的文本数据,JsonRequest(JsonObjectRequest、JsonArrayRequest)用于请求一条JSON格式的数据,ImageRequest则是用于请求网络上的一张图片。
1.添加网络访问权限
<uses-permission android:name="android.permission.INTERNET" />
2.创建一个RequestQueue对象。RequestQueue是一个请求队列对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。
RequestQueue mRequestQueue = Volley.newRequestQueue(context);
3.发出一条HTTP请求,如创建一个StringRequest对象。StringRequest的构造函数需要传入三个参数,第一个参数就是目标服务器的URL地址,第二个参数是服务器响应成功的回调,第三个参数是服务器响应失败的回调。其中,url是我们访问的地址,如http://www.baidu.com,然后在响应成功的回调里打印出服务器返回的内容,在响应失败的回调里打印出失败的详细信息。
onResponse返回的字符串是用iso8859-1编码的,需要转换为目标编码,才不会出现乱码,百度返回的编码格式为utf-8。onErrorResponse 中返回的VolleyError是Exception子类。这两个方法都是在UI主线程中调用的。
当然也可以使用4个参数版本的构造方法,其第一个参数指定的请求方式,如果不指定,默认为GET请求。
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener)
StringRequest request = new StringRequest(url, new Response.Listener<String>() { @Override public void onResponse(String s) { try { s = new String(s.getBytes("iso8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Log.d(TAG, s); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.e(TAG, "E/StringRequest-ErrorListener", volleyError); } });
4.将这个StringRequest对象添加到RequestQueue对象里面,这样就可以异步的去连接网络并解析返回的数据。
mRequestQueue.add(request);
5.其它Request对象使用
// 下载百度Logo图片 url = "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png"; ImageRequest imageRequest = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { ImageView img = (ImageView) findViewById(R.id.img); img.setImageBitmap(bitmap); } }, 1000, 1000, Bitmap.Config.ARGB_8888, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.e(TAG, "E/ImageRequest-ErrorListener", volleyError); } }); mRequestQueue.add(imageRequest);
// 查询深圳天气 (天气接口使用:http://www.nohacks.cn/post-35.html) url = "http://wthrcdn.etouch.cn/weather_mini?citykey=101280601"; JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject jsonObject) { Log.d(TAG, "jsonObjectRequest-onResponse"); String s = jsonObject.toString(); try { s = new String(s.getBytes("iso8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Log.d(TAG, "" + s); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.e(TAG, "E/JsonObjectRequest-ErrorListener", volleyError); } }); mRequestQueue.add(jsonObjectRequest);
ImageLoader的用法
ImageLoader可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。
1.创建RequestQueue对象
2.创建ImageLoader对象,需要RequestQueue对象,以及一个ImageCache缓存。
public ImageLoader(RequestQueue queue, ImageLoader.ImageCache imageCache)
3.创建ImageListener,通过ImageLoader.getImageListener方法,其中设置显示图片的ImageView。
public static ImageLoader.ImageListener getImageListener(final ImageView view, final int defaultImageResId, final int errorImageResId);
4.使用ImageLoader对象,调用其get方法,传入url,ImageListener,以及图片的最大显示宽高,不设置大小或0,则为原图.
public ImageLoader.ImageContainer get(String requestUrl, ImageLoader.ImageListener listener);
public ImageLoader.ImageContainer get(String requestUrl, ImageLoader.ImageListener imageListener, int maxWidth, int maxHeight);
private RequestQueue mRequestQueue; private ImageCache mImgCache; mRequestQueue = Volley.newRequestQueue(this); mImgCache = new BitmapCache(); final ImageLoader imgLoader = new ImageLoader(mRequestQueue, mImgCache); final ImageListener imgListener = ImageLoader.getImageListener(imgView, R.mipmap.ic_launcher, R.mipmap.ic_launcher); imgLoader.get("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png", imgListener, 100, 100); mHandler.postDelayed(new Runnable() { @Override public void run() { imgLoader.get("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png", imgListener, 0, 0); } }, 2000); /** * Image缓存对象, * 其中key的值不是URL,其包含了图片大小及URL * 形式如同:#W0#H0https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png */ private class BitmapCache implements ImageCache { private static final int MAX_SIZE = 10 << 20; // 10 MB private LruCache<String, Bitmap> mLruCache; public BitmapCache() { mLruCache = new LruCache<String, Bitmap>(MAX_SIZE) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight(); } }; } @Override public Bitmap getBitmap(String key) { return mLruCache.get(key); } @Override public void putBitmap(String key, Bitmap bitmap) { mLruCache.put(key, bitmap); } };
NetworkImageView
不同于ImageRequest和ImageLoader,NetworkImageView是一个自定义控件,继承自ImageView,并且实现了从网络加载图片的功能。使用方法比前面两种更简单。NetworkImageView的优势还在于并不需要设置最大宽高的方法也能够对加载的图片进行压缩。这是由于NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,NetworkImageView会始终呈现给我们一张大小刚刚好的网络图片。
1. 创建一个RequestQueue对象。
2. 创建一个ImageLoader对象。
3. 在布局文件中添加一个NetworkImageView控件。
4. 在代码中获取该控件的实例。
得到了NetworkImageView控件的实例之后,可以调用它的setDefaultImageResId()方法、setErrorImageResId()方法和setImageUrl()方法来分别设置加载中显示的图片,加载失败时显示的图片。
5. 设置要加载的图片地址。
<com.android.volley.toolbox.NetworkImageView android:id="@+id/network_img" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="center"/> mImgCache = new BitmapCache(); final ImageLoader imgLoader = new ImageLoader(mRequestQueue, mImgCache); NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_img); networkImageView.setImageUrl("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png", imgLoader);
自定义Request
1.StringRequest源码分析
StringRequest 继承自 Request 类的,Request可以指定一个泛型类,StringRequest中为String。
接下来StringRequest中提供了两个有参的构造函数,参数包括请求类型,请求地址,以及响应回调等,在构造函数中要调用super()方法将这几个参数传给父类,因为HTTP的请求和响应都是在父类中自动处理的。
Request类中的deliverResponse()和parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。
deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入即可,这样客户端代码就可以处理服务器响应的数据了。
parseNetworkResponse()方法中则应该对服务器响应的数据进行解析,其中数据是以字节的形式存放在NetworkResponse的data变量中的,StringRequest将数据取出然后组装成一个String,然后调用Response的success()方法返回。
需要注意的是,deliverResponse 是在当前执行RequestQueue.add方法的线程中调用的,而 parseNetworkResponse 是在别的线程中调用的,因此耗时任务,因该放到 parseNetworkResponse 中执行完成,然后再由 deliverResponse 方法中回调到主线程。
package com.android.volley.toolbox; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.toolbox.HttpHeaderParser; import java.io.UnsupportedEncodingException; public class StringRequest extends Request<String> { private final Listener<String> mListener; public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); this.mListener = listener; } public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(0, url, listener, errorListener); } protected void deliverResponse(String response) { this.mListener.onResponse(response); } protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException var4) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } }
2.自定义Requst类: XMLRequest
1.构造函数: 设置xml解析的对象XMLParseListener(自定义接口),处理完成之后的回调对象 Response.Listener<T> 和 Response.ErrorListener,然后调用基类的构造方法
public Request(int method, String url, ErrorListener listener);
2.在parseNetworkResponse方法中,通过 XMLParseListener 对象,让调用的客户程序解析xml。通过 Response.error 和 Response.success 静态方法返回处理结果。
3.在 deliverResponse 方法中,调用返回客户程序处理的结果。
import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.toolbox.HttpHeaderParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; public class XMLRequest<T> extends Request<T> { private static final String TAG = "debug"; private final Response.Listener<T> mListener; private final XMLParseListener mXMLParseListener; public XMLRequest(int method, String url, XMLParseListener xmlParseListener, Response.Listener<T> listener, Response.ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; mXMLParseListener = xmlParseListener; } /** * xmlParseListener 为异步解析 */ public XMLRequest(String url, XMLParseListener xmlParseListener, Response.Listener<T> listener, Response .ErrorListener errorListener) { this(Method.GET, url, xmlParseListener, listener, errorListener); } @Override protected Response<T> parseNetworkResponse(NetworkResponse networkResponse) { T obj; try { ByteArrayInputStream is = new ByteArrayInputStream(networkResponse.data); obj = (T) mXMLParseListener.onXMLPasre(is, getParamsEncoding()); } catch (XmlPullParserException e) { return Response.error(new ParseError(e)); } catch (Exception e) { obj = null; } return Response.success(obj, HttpHeaderParser.parseCacheHeaders(networkResponse)); } @Override protected void deliverResponse(T obj) { mListener.onResponse(obj); } public interface XMLParseListener<T> { T onXMLPasre(ByteArrayInputStream is, String paramEncoding) throws XmlPullParserException; } }
客户端程序调用的代码,这里使用 tomcat 在本机进行测试
url = "http://192.168.1.102/HelloWeb/persons.xml"; // 使用 tomcat 测试 XMLRequest request = new XMLRequest<>(url, new XMLRequest.XMLParseListener() { @Override public List<Person> onXMLPasre(ByteArrayInputStream is, String paramEncoding) { PersonXmlPullTools xmlPullTools = new PersonXmlPullTools(); List<Person> persons = xmlPullTools.parseXml(is, paramEncoding); return persons; } }, new Response.Listener<List<Person>>() { @Override public void onResponse(List<Person> persons) { Log.d(TAG, "" + persons); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.d(TAG, "", volleyError); } }); mRequestQueue.add(request);
PersonXmlPullTools 及 Person 类 源码:
import android.content.Context; import android.content.res.XmlResourceParser; import android.util.Log; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; // Pull解析器的运行方式与 SAX,但它需要调用next方法进下一步解析,并返回相应的event类型,并不提供回调。 // 当元素开始解析时,调用parser.nextText()方法可以获取Text类型节点的值。 public class PersonXmlPullTools { private static final String TAG = "System.out"; private static final String TAG_PERSON = "Person"; private static final String TAG_ID = "id"; private static final String TAG_NAME = "name"; private static final String TAG_AGE = "age"; private static final String TAG_MALE = "male"; private static final String TAG_COUNTRY = "country"; public PersonXmlPullTools() { } /** * 使用 android 封装的类型进行解析,id指向的资源定义在res/xml中 * * @param context * @param id * @return */ public List<Person> parseXml(Context context, int id) { XmlResourceParser parser = context.getResources().getXml(id); List<Person> list = parse(parser); return list; } /** * 使用与pull库解析,android系统已包含了相应的库,java中需要自行下载并导入xmlpull_xxx.jar * 解析原生的xml文件,android系统中xml文件放置到assets中 * * @param is * 输入流 * @param encode * 编码格式,必须和is流中的对象编码一致 * @return */ public List<Person> parseXml(InputStream is, String encode) { List<Person> list = null; XmlPullParser parser = null; try { parser = XmlPullParserFactory.newInstance().newPullParser(); parser.setInput(is, encode); list = parse(parser); } catch (XmlPullParserException e) { Log.e(TAG, "", e); } return list; } private List<Person> parse(XmlPullParser parser) { List<Person> list = null; Person person = null; String tag; try { int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_DOCUMENT: list = new ArrayList<Person>(); break; case XmlPullParser.START_TAG: print("[START_TAG]"); tag = parser.getName(); print(tag); if (checkTag(TAG_PERSON, tag)) { person = new Person(); int cnt = parser.getAttributeCount(); for (int i = 0; i < cnt; i++) { tag = parser.getAttributeName(i); print("attr:" + tag); setAttribute(person, tag, parser.getAttributeValue(i)); } } else { if (isDefinedTag(tag)) { String value = parser.nextText(); // 取得该节点的内容 setAttribute(person, tag, value); } } break; case XmlPullParser.END_TAG: print("[END_TAG]"); tag = parser.getName(); print("attr:" + tag); if (checkTag(TAG_PERSON, tag)) { list.add(person); person = null; } break; } eventType = parser.next(); } } catch (XmlPullParserException e) { Log.e(TAG, "", e); return null; } catch (IOException e) { Log.e(TAG, "", e); return null; } return list; } /** * 判断两个tag的值是否相等 * * @param destTag * @param tag * @return */ private boolean checkTag(String destTag, String tag) { print("checktag: " + destTag + "," + tag); if (destTag.equals(tag)) { return true; } return false; } private boolean isDefinedTag(String tag) { if (checkTag(TAG_ID, tag)) return true; if (checkTag(TAG_NAME, tag)) return true; if (checkTag(TAG_AGE, tag)) return true; if (checkTag(TAG_MALE, tag)) return true; if (checkTag(TAG_COUNTRY, tag)) return true; return false; } /** * 设置Person的属性值 * * @param person * @param tag * @param value * @return */ private boolean setAttribute(Person person, String tag, String value) { print("setAttribute--> tag=" + tag + ", value:" + value); if (person == null) return false; if (TAG_ID.equals(tag)) { if (value != null) person.setId(Integer.parseInt(value)); return true; } if (TAG_NAME.equals(tag)) { person.setName(value); return true; } if (TAG_AGE.equals(tag)) { if (value != null) person.setAge(Integer.parseInt(value)); return true; } if (TAG_MALE.equals(tag)) { if (value != null) person.setMale(Boolean.parseBoolean(value)); return true; } if (TAG_COUNTRY.equals(tag)) { person.setCountry(value); return true; } return false; } private void print(String s) { // Log.d(TAG, "" + s); } }
package com.john.webapp; public class Person { int mId; String mName; int mAge; boolean mMale = true; String mCountry; public Person() { } public int getId() { return mId; } public Person setId(int id) { mId = id; return this; } public String getName() { return mName; } public Person setName(String name) { mName = name; return this; } public int getAge() { return mAge; } public Person setAge(int age) { mAge = age; return this; } public boolean isMale() { return mMale; } public Person setMale(boolean male) { mMale = male; return this; } public String getCountry() { return mCountry; } public Person setCountry(String country) { mCountry = country; return this; } @Override public String toString() { return "Person [mId=" + mId + ", mName=" + mName + ", mAge=" + mAge + ", mMale=" + mMale + ", mCountry=" + mCountry + "]"; } }
解析的文件 persons.xml
<?xml version="1.0" encoding="UTF-8"?>
<Persons>
<Person id="1">
<name>张三</name>
<age>29</age>
</Person>
<Person id="2">
<name>李小姐</name>
<age>39</age>
<male>false</male>
</Person>
<Person id="3">
<name>赵五</name>
<age>21</age>
<country>中国</country>
</Person>
</Persons>
Volley 的内部机制
在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
参考:http://blog.csdn.net/guolin_blog/article/details/17656437
http://blog.csdn.net/geolo/article/details/43966171