Android之Volley框架
-
android-async-http
-
retrofit
-
okhttp
他们各有优劣,之前个人则比较喜欢用android-async-http, 如今Google推出了官方的针对Android平台上的网络通信库,能使网络通信更快,更简单,更健壮,Volley在提供了高性能网络通讯功能的同时,对网络图片加载也提供了良好的支持,完全可以满足简单REST客户端的需求, 我们没有理由不跟上时代的潮流
使用Volley
下载Volley源码并build jar包。
$ git clone https://android.googlesource.com/platform/frameworks/volley $ cd volley $ android update project -p $ ant jar
然后把生成的jar包引用到我们的项目中,extras目录下则包含了目前最新的volley源码。
说明
此Demo主要介绍了日常网络开发常用的基本功能,但volley的扩展性很强,可以根据需要定制你自己的网络请求。
volley视频地址:http://www.youtube.com/watch?v=yhv8l9F44qo&feature=player_embedded
以上是在Google IO的演讲上ppt的配图,从上面这张图我们可以看出,volley适合快速,简单的请求(Json对象,图片加载)。
volley的特性:
- JSON,图像等的异步下载;
- 网络请求的排序(scheduling)
- 网络请求的优先级处理
- 缓存
- 多级别取消请求
- 和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)
接下来,我们来学习简单的使用下volley给我提供的API吧。
1.首先拿到一个请求队列(RequestQueue只需要一个实例即可,不像AsyncTask每次使用都要new一个)
[java] view plain copy
- //初始化RequestQueue一个activity只需要一个
- privatevoidinitRequestQueue(){
- mQueue=Volley.newRequestQueue(getApplicationContext());
- }
2.实现volley的异步请求类(JsonObjectRequest,JsonArrayRequest,StringRequest,ImageRequest )
由于用法都相差不大,我就不一一举例了,举几个常用有代表性的例子:
以下代码是StringRequest的get请求:
[java] view plain copy- //get请求
- privatevoidloadGetStr(Stringurl){
- StringRequestsrReq=newStringRequest(Request.Method.GET,url,
- newStrListener(),newStrErrListener()){
- protectedfinalStringTYPE_UTF8_CHARSET="charset=UTF-8";
- //重写parseNetworkResponse方法改变返回头参数解决乱码问题
- //主要是看服务器编码,如果服务器编码不是UTF-8的话那么就需要自己转换,反之则不需要
- @Override
- protectedResponse<String>parseNetworkResponse(
- NetworkResponseresponse){
- try{
- Stringtype=response.headers.get(HTTP.CONTENT_TYPE);
- if(type==null){
- type=TYPE_UTF8_CHARSET;
- response.headers.put(HTTP.CONTENT_TYPE,type);
- }elseif(!type.contains("UTF-8")){
- type+=";"+TYPE_UTF8_CHARSET;
- response.headers.put(HTTP.CONTENT_TYPE,type);
- }
- }catch(Exceptione){
- }
- returnsuper.parseNetworkResponse(response);
- }
- };
- srReq.setShouldCache(true);//控制是否缓存
- startVolley(srReq);
- }
以下代码是 JsonObjectRequest的post 请求: [java] view plain copy
- //post请求
- privatevoidloadPostJson(Stringurl){
- //第二个参数说明:
- //ConstructorwhichdefaultstoGETifjsonRequestisnull,POST
- //otherwise.
- //默认情况下设成null为get方法,否则为post方法。
- JsonObjectRequestsrReq=newJsonObjectRequest(url,null,
- newJsonListener(),newStrErrListener()){
- @Override
- protectedMap<String,String>getParams()throwsAuthFailureError{
- Map<String,String>map=newHashMap<String,String>();
- map.put("w","2459115");
- map.put("u","f");
- returnmap;
- }
- };
- srReq.setShouldCache(false);//控制是否缓存
- startVolley(srReq);
- }
大家注意看的话,无论是 JsonObjectReques的postt还是StringRequest的get都需要传入两个监听函数一个是成功一个是失败,成功监听他们会返回相应类型的数据:
[java] view plain copy
- //Str请求成功回调
- privateclassStrListenerimplementsListener<String>{
- @Override
- publicvoidonResponse(Stringarg0){
- Log.e(Tag,arg0);
- }
- }
- //Gson请求成功回调
- privateclassGsonListenerimplementsListener<ErrorRsp>{
- @Override
- publicvoidonResponse(ErrorRsparg0){
- Toast.makeText(mContext,arg0.toString(),Toast.LENGTH_LONG).show();
- }
- }
- //共用失败回调
- privateclassStrErrListenerimplementsErrorListener{
- @Override
- publicvoidonErrorResponse(VolleyErrorarg0){
- Toast.makeText(mContext,
- VolleyErrorHelper.getMessage(arg0,mContext),
- Toast.LENGTH_LONG).show();
- }
- }
- /**
- *第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,
- *指定成0的话就表示不管图片有多大,都不会进行压缩。
- *
- *@paramurl
- *图片地址
- *@paramlistener
- *@parammaxWidth
- *指定允许图片最大的宽度
- *@parammaxHeight
- *指定允许图片最大的高度
- *@paramdecodeConfig
- *指定图片的颜色属性,Bitmap.Config下的几个常量.
- *@paramerrorListener
- */
- privatevoidgetImageRequest(finalImageViewiv,Stringurl){
- ImageRequestimReq=newImageRequest(url,newListener<Bitmap>(){
- @Override
- publicvoidonResponse(Bitmaparg0){
- iv.setImageBitmap(arg0);
- }
- },60,60,Bitmap.Config.ARGB_8888,newStrErrListener());
- startVolley(imReq);
- }
[java] view plain copy
- //添加及开始请求
- privatevoidstartVolley(Requestreq){
- //设置超时时间
- //req.setRetryPolicy(newDefaultRetryPolicy(20*1000,1,1.0f));
- //将请求加入队列
- mQueue.add(req);
- //开始发起请求
- mQueue.start();
- }
volley不仅提供了这些请求的方式,还提供了加载图片的一些方法和控件:
比如我们一个列表需要加载很多图片我们可以使用volley给我们提供的ImageLoader(ImageLoader比ImageRequest更加高效,因为它不仅对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。)
[java] view plain copy
- publicclassImageAdapterextendsArrayAdapter<String>{
- privateRequestQueuemQueue;
- privateImageLoadermImageLoader;
- publicImageAdapter(Contextcontext,List<String>objects){
- super(context,0,objects);
- mQueue=Volley.newRequestQueue(getContext());
- mImageLoader=newImageLoader(mQueue,newBitmapCache());
- }
- @Override
- publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
- Stringurl=getItem(position);
- ImageViewimageView;
- if(convertView==null){
- imageView=newImageView(getContext());
- }else{
- imageView=(ImageView)convertView;
- }
- //getImageListener(imageView控件对象,默认图片地址,失败图片地址);
- ImageListenerlistener=ImageLoader.getImageListener(imageView,android.R.drawable.ic_menu_rotate,android.R.drawable.ic_delete);
- //get(图片地址,listener,宽,高);自动帮你处理图片的宽高再也不怕大图片的oom了
- mImageLoader.get(url,listener,100,200);
- returnimageView;
- }
- }
[java] view plain copy
- publicclass<spanstyle="font-family:Arial;">BitmapCache</span><spanstyle="font-family:Arial;">extendsLruCache<String,Bitmap>implementsImageCache{</span>
- //LruCache原理:Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。当cache已满的时候加入新的item时,在队列尾部的item会被回收。
- //解释:当超出指定内存值则移除最近最少用的图片内存
- publicstaticintgetDefaultLruCacheSize(){
- //拿到最大内存
- finalintmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);
- //拿到内存的八分之一来做图片内存缓存
- finalintcacheSize=maxMemory/8;
- returncacheSize;
- }
- publicBitmapLruCache(){
- this(getDefaultLruCacheSize());
- }
- publicBitmapLruCache(intsizeInKiloBytes){
- super(sizeInKiloBytes);
- }
- @Override
- protectedintsizeOf(Stringkey,Bitmapvalue){
- returnvalue.getRowBytes()*value.getHeight()/1024;
- }
- @Override
- publicBitmapgetBitmap(Stringurl){
- returnget(url);
- }
- @Override
- publicvoidputBitmap(Stringurl,Bitmapbitmap){
- put(url,bitmap);
- }
- }
Volley还提供的加载图片的控件 com.android.volley.NetworkImageView。(这个控件在被从父控件detach的时候,会自动取消网络请求的,即完全不用我们担心相关网络请求的生命周期问题,而且NetworkImageView还会根据你对图片设置的width和heigh自动压缩该图片不会产生多的内存,还有NetworkImageView在列表中使用不会图片错误 )
[html] view plain copy
- <com.android.volley.toolbox.NetworkImageView
- android:id="@+id/network_image_view"
- android:layout_width="100dp"
- android:layout_height="100dp"/>
[java] view plain copy
- privatevoidnetworkImageViewUse(NetworkImageViewiv,Stringurl){
- ImageLoaderimLoader=newImageLoader(mQueue,newBitmapLruCache());
- iv.setDefaultImageResId(R.drawable.ic_launcher);
- iv.setErrorImageResId(R.drawable.ic_launcher);
- iv.setImageUrl(url,imLoader);
- }
我们说了这么多都是请求,那么如何取消请求呢?
1.activity自动销毁时它会自定取消所有请求。
2.给请求设置标签:
[java] view plain copy
- request.setTag("MyTag");
取消所有指定标记的请求:
[java] view plain copy
- request.cancelAll("MyTag");
其中蓝色部分代表主线程,绿色部分代表缓存线程,橙色部分代表网络线程。我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
接下来我们要看看如何把volley使用到实战项目里面,我们先考虑下一些问题:
从上一篇来看mQueue 只需要一个对象即可,new RequestQueue对象对资源一种浪费,我们应该在application,以及可以把取消请求的方法也在application进行统一管理,看以下代码:
[java] view plain copy
- packagecom.chronocloud.lib.base;
- importandroid.app.Application;
- importandroid.text.TextUtils;
- importcom.android.volley.Request;
- importcom.android.volley.RequestQueue;
- importcom.android.volley.VolleyLog;
- importcom.android.volley.toolbox.Volley;
- publicclassApplicationControllerextendsApplication{
- /**
- *LogorrequestTAG
- */
- publicstaticfinalStringTAG="VolleyPatterns";
- /**
- *GlobalrequestqueueforVolley
- */
- privateRequestQueuemRequestQueue;
- /**
- *Asingletoninstanceoftheapplicationclassforeasyaccessinother
- *places
- */
- privatestaticApplicationControllersInstance;
- @Override
- publicvoidonCreate(){
- super.onCreate();
- //initializethesingleton
- sInstance=this;
- }
- /**
- *@returnApplicationControllersingletoninstance
- */
- publicstaticsynchronizedApplicationControllergetInstance(){
- returnsInstance;
- }
- /**
- *@returnTheVolleyRequestqueue,thequeuewillbecreatedifitisnull
- */
- publicRequestQueuegetRequestQueue(){
- //lazyinitializetherequestqueue,thequeueinstancewillbe
- //createdwhenitisaccessedforthefirsttime
- if(mRequestQueue==null){
- //1
- //2
- synchronized(ApplicationController.class){
- if(mRequestQueue==null){
- mRequestQueue=Volley
- .newRequestQueue(getApplicationContext());
- }
- }
- }
- returnmRequestQueue;
- }
- /**
- *Addsthespecifiedrequesttotheglobalqueue,iftagisspecifiedthen
- *itisusedelseDefaultTAGisused.
- *
- *@paramreq
- *@paramtag
- */
- public<T>voidaddToRequestQueue(Request<T>req,Stringtag){
- //setthedefaulttagiftagisempty
- req.setTag(TextUtils.isEmpty(tag)?TAG:tag);
- VolleyLog.d("Addingrequesttoqueue:%s",req.getUrl());
- getRequestQueue().add(req);
- }
- /**
- *AddsthespecifiedrequesttotheglobalqueueusingtheDefaultTAG.
- *
- *@paramreq
- *@paramtag
- */
- public<T>voidaddToRequestQueue(Request<T>req){
- //setthedefaulttagiftagisempty
- req.setTag(TAG);
- getRequestQueue().add(req);
- }
- /**
- *CancelsallpendingrequestsbythespecifiedTAG,itisimportantto
- *specifyaTAGsothatthepending/ongoingrequestscanbecancelled.
- *
- *@paramtag
- */
- publicvoidcancelPendingRequests(Objecttag){
- if(mRequestQueue!=null){
- mRequestQueue.cancelAll(tag);
- }
- }
- }
接下来我们来看看,如何自定义Request
XmlRequest:
[java] view plain copy
- publicclassXMLRequestextendsRequest<XmlPullParser>{
- privatefinalListener<XmlPullParser>mListener;
- publicXMLRequest(intmethod,Stringurl,Listener<XmlPullParser>listener,
- ErrorListenererrorListener){
- super(method,url,errorListener);
- mListener=listener;
- }
- publicXMLRequest(Stringurl,Listener<XmlPullParser>listener,ErrorListenererrorListener){
- this(Method.GET,url,listener,errorListener);
- }
- @Override
- protectedResponse<XmlPullParser>parseNetworkResponse(NetworkResponseresponse){
- try{
- StringxmlString=newString(response.data,
- HttpHeaderParser.parseCharset(response.headers));
- XmlPullParserFactoryfactory=XmlPullParserFactory.newInstance();
- XmlPullParserxmlPullParser=factory.newPullParser();
- xmlPullParser.setInput(newStringReader(xmlString));
- returnResponse.success(xmlPullParser,HttpHeaderParser.parseCacheHeaders(response));
- }catch(UnsupportedEncodingExceptione){
- returnResponse.error(newParseError(e));
- }catch(XmlPullParserExceptione){
- returnResponse.error(newParseError(e));
- }
- }
- @Override
- protectedvoiddeliverResponse(XmlPullParserresponse){
- mListener.onResponse(response);
- }
- }
GsonRequest(注意需要自行导入gson.jar):
[java] view plain copy
- publicclassGsonRequest<T>extendsRequest<T>{
- privatefinalListener<T>mListener;
- privateGsonmGson;
- privateClass<T>mClass;
- publicGsonRequest(intmethod,Stringurl,Class<T>clazz,Listener<T>listener,
- ErrorListenererrorListener){
- super(method,url,errorListener);
- mGson=newGson();
- mClass=clazz;
- mListener=listener;
- }
- publicGsonRequest(Stringurl,Class<T>clazz,Listener<T>listener,
- ErrorListenererrorListener){
- this(Method.GET,url,clazz,listener,errorListener);
- }
- @Override
- protectedResponse<T>parseNetworkResponse(NetworkResponseresponse){
- try{
- StringjsonString=newString(response.data,
- HttpHeaderParser.parseCharset(response.headers));
- returnResponse.success(mGson.fromJson(jsonString,mClass),
- HttpHeaderParser.parseCacheHeaders(response));
- }catch(UnsupportedEncodingExceptione){
- returnResponse.error(newParseError(e));
- }
- }
- @Override
- protectedvoiddeliverResponse(Tresponse){
- mListener.onResponse(response);
- }
- }
[java] view plain copy
- //共用失败回调
- privateclassStrErrListenerimplementsErrorListener{
- @Override
- publicvoidonErrorResponse(VolleyErrorarg0){
- Toast.makeText(mContext,
- VolleyErrorHelper.getMessage(arg0,mContext),
- Toast.LENGTH_LONG).show();
- }
- }
[java] view plain copy
- packagecom.example.volley;
- importjava.util.HashMap;
- importjava.util.Map;
- importandroid.content.Context;
- importcom.android.volley.AuthFailureError;
- importcom.android.volley.NetworkError;
- importcom.android.volley.NetworkResponse;
- importcom.android.volley.NoConnectionError;
- importcom.android.volley.ServerError;
- importcom.android.volley.TimeoutError;
- importcom.android.volley.VolleyError;
- importcom.google.gson.Gson;
- importcom.google.gson.reflect.TypeToken;
- //正如前面代码看到的,在创建一个请求时,需要添加一个错误监听onErrorResponse。如果请求发生异常,会返回一个VolleyError实例。
- //以下是Volley的异常列表:
- //AuthFailureError:如果在做一个HTTP的身份验证,可能会发生这个错误。
- //NetworkError:Socket关闭,服务器宕机,DNS错误都会产生这个错误。
- //NoConnectionError:和NetworkError类似,这个是客户端没有网络连接。
- //ParseError:在使用JsonObjectRequest或JsonArrayRequest时,如果接收到的JSON是畸形,会产生异常。
- //SERVERERROR:服务器的响应的一个错误,最有可能的4xx或5xxHTTP状态代码。
- //TimeoutError:Socket超时,服务器太忙或网络延迟会产生这个异常。默认情况下,Volley的超时时间为2.5秒。如果得到这个错误可以使用RetryPolicy。
- publicclassVolleyErrorHelper{
- /**
- *Returnsappropriatemessagewhichistobedisplayedtotheuseragainst
- *thespecifiederrorobject.
- *
- *@paramerror
- *@paramcontext
- *@return
- */
- publicstaticStringgetMessage(Objecterror,Contextcontext){
- if(errorinstanceofTimeoutError){
- returncontext.getResources().getString(
- R.string.generic_server_down);
- }elseif(isServerProblem(error)){
- returnhandleServerError(error,context);
- }elseif(isNetworkProblem(error)){
- returncontext.getResources().getString(R.string.no_internet);
- }
- returncontext.getResources().getString(R.string.generic_error);
- }
- /**
- *Determineswhethertheerrorisrelatedtonetwork
- *
- *@paramerror
- *@return
- */
- privatestaticbooleanisNetworkProblem(Objecterror){
- return(errorinstanceofNetworkError)
- ||(errorinstanceofNoConnectionError);
- }
- /**
- *Determineswhethertheerrorisrelatedtoserver
- *
- *@paramerror
- *@return
- */
- privatestaticbooleanisServerProblem(Objecterror){
- return(errorinstanceofServerError)
- ||(errorinstanceofAuthFailureError);
- }
- /**
- *Handlestheservererror,triestodeterminewhethertoshowastock
- *messageortoshowamessageretrievedfromtheserver.
- *
- *@paramerr
- *@paramcontext
- *@return
- */
- privatestaticStringhandleServerError(Objecterr,Contextcontext){
- VolleyErrorerror=(VolleyError)err;
- NetworkResponseresponse=error.networkResponse;
- if(response!=null){
- switch(response.statusCode){
- case404:
- case422:
- case401:
- try{
- //servermightreturnerrorlikethis{"error":
- //"Someerroroccured"}
- //Use"Gson"toparsetheresult
- HashMap<String,String>result=newGson().fromJson(
- newString(response.data),
- newTypeToken<Map<String,String>>(){
- }.getType());
- if(result!=null&&result.containsKey("error")){
- returnresult.get("error");
- }
- }catch(Exceptione){
- e.printStackTrace();
- }
- //invalidrequest
- returnerror.getMessage();
- default:
- returncontext.getResources().getString(
- R.string.generic_server_down);
- }
- }
- returncontext.getResources().getString(R.string.generic_error);
- }
- }
以上代码中引用的xml是:
[html] view plain copy
- <stringname="no_internet">无网络连接~!</string>
- <stringname="generic_server_down">连接服务器失败~!</string>
- <stringname="generic_error">网络异常,请稍后再试~!</string>
下面主要是讲Volley在某些细节方面的选择和实现.值得我们学习的地方以及如果更好的使用Volley。
1.Volley本地缓存为什么有时候不会进行缓存?
缓存使用前提服务器必须支持,缓存,配置Cache-Control头信息,
因为Volley需要从这些头信息判断缓存是否已经过期。如果已经过期Volley将会重新从网络获取数据。
本人用抓包工具抓了无法缓存的返回头信息
可以支持缓存的头信息
2.如果我们自己写一个网络请求框架,我们内部实现会选择使用HttpURLConnection还是HttpClient?
我们通过源码来看看Volley是如何选择使用的
[java] view plain copy
- publicstaticRequestQueuenewRequestQueue(Contextcontext,HttpStackstack){
- FilecacheDir=newFile(context.getCacheDir(),DEFAULT_CACHE_DIR);
- StringuserAgent="volley/0";
- try{
- StringpackageName=context.getPackageName();
- PackageInfoinfo=context.getPackageManager().getPackageInfo(packageName,0);
- userAgent=packageName+"/"+info.versionCode;
- }catch(NameNotFoundExceptione){
- }
- if(stack==null){
- if(Build.VERSION.SDK_INT>=9){
- stack=newHurlStack();
- }else{
- stack=newHttpClientStack(AndroidHttpClient.newInstance(userAgent));
- }
- }
- Networknetwork=newBasicNetwork(stack);
- RequestQueuequeue=newRequestQueue(newDiskBasedCache(cacheDir),network);
- queue.start();
- returnqueue;
- }
从这点我们可以学习到,要针对不同SDK版本做去相应更优的处理方式,这样才能达到最好的效果。
3.Volley给我们提供了ImageRrequest,ImageLoader,NetworkImageView,它们分别使用于什么场景为什么?
单张图片的加载可以通过发起 ImageReuqst 请求来实现,但为了应用内存缓存,推荐使用ImageLoader
NetwoekImageView专门用于批量图片加载的场景:
[java] view plain copy
- publicclassNetworkImageViewextendsImageView{
- privateStringmUrl;
- //默认显示的图片
- privateintmDefaultImageId;
- //加载失败时显示的图片
- privateintmErrorImageId;
- //主方法入口
- publicvoidsetImageUrl(Stringurl,ImageLoaderimageLoader){
- mUrl=url;
- mImageLoader=imageLoader;
- //这个方法将会对ImageView的尺寸是否有效、是否为同一张图片进行判断
- //在执行新请求前,也会取消上一次在这个View里启动的另一个已经失效的请求
- //由于篇幅的限制以及代码行数太多,这里不贴出具体实现的代码
- loadImageIfNecessary(false);
- }
- //如果图片已经滑离屏幕,变为不可见,将执行取消请求的操作
- @Override
- protectedvoidonDetachedFromWindow(){
- if(mImageContainer!=null)mImageContainer.cancelRequest();
- super.onDetachedFromWindow();
- }
- }
更多相关文章
- Android(安卓)Volloy 网络请求框架图
- Android调用系统分享功能总结
- 【Android】ListView RecyclerView
- Android图形图画学习(5)——解码图片
- Android(安卓)ImageView.ScaleType设置图解
- android 在你的UI中显示Bitmap - 开发文档翻译
- android-image-slide-panel图片照片墙的加载和滑动特效
- Android多点触控实现图片自由缩放
- Android不能进行http请求,https无法抓包的解决