Volley官方地址
http://stackoverflow.com/ refreshNeeded
https://developer.android.com/training/volley/index.html
Volley概述
Volley是Google 2013年I/O大会推出的针对Android的HTTP网络请求框架,让网络请求更简单,更快。
特点:
l 自动调度网络请求
l 支持并发网络连接
l 支持标准的HTTP缓存协议(由服务器来决定是否缓存数据)
l 支持请求优先级设置(4级)
l 支持取消单个或多个请求
l 易于定制,扩展性强。比如Retry&Backoff机制
l 强大的网络请求能力让你轻松的发送异步请求来填充UI数据
l 提供调试和跟踪工具
优点:
擅长将RPC(远程过程调用协议,C/S模式)类型的操作,用来展示服务器数据。比如以某种数据格式获取一页搜索结果。支持任意的数据传输格式,比如图片,字符串,json,你也可以定义自己的数据格式。其实就是自定义Request。Volley让你不再写这些重复的模板代码(网络请求逻辑,不再重复造轮子),这样你就可以专注于你应用本身的业务逻辑.
缺点:
由于Volley都是在内存中解析和处理数据,所以不适合大数据量的下载操作。如果需要下载大文件,可以考虑使用系统的DownloadManager。
Volley如何使用
Volley存在于AOSP的frameworks/volley 里。包含了主要的网络请求核心框架,toolbox包下是一些帮你实现的基于核心框架封装的网络请求工具。
1. 直接把代码clone下来,把volley的代码直接copy到你的project里,
git clone https://android.googlesource.com/platform/frameworks/volley
2. 作为一个library项目存在或者jar包存在
Sending a Simple Request(发送一个简单的请求)
首先你需要通过Volley创建一个RequestQueue
,用来添加Request 对象。RequestQueue
管理着网络请求的work thread,执行读写缓存,解析响应结果这些操作。Request负责原始响应的解析,另外还要将解析后的响应抛回给主线程处理。
这一小节的学习内容为,如何通过Volley.newRequestQueue 给我们创建的RequestQueue
发送一个Request请求。同样也会介绍如何把一个Request添加到队列,如何取消一个请求。
1. Add the INTERNET Permission(添加网络权限)
在清单文件中添加权限:
<uses-permission android:name="android.permission.INTERNET"/> |
2. Use newRequestQueue(使用RequestQueue发Request)
使用Volley.newRequestQueue
初始化一个RequestQueue,并发送一个StringRequest。使用Volley执行网络请求,开发者不需要关心线程的问题,只需要在响应的Listener里处理结果,直接更新UI就可以了。
final TextView mTextView = (TextView) findViewById(R.id.text); ... // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this); String url ="http://www.google.com"; // Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { // Display the first 500 characters of the response string. mTextView.setText("Response is: "+ response.substring(0,500)); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { mTextView.setText("That didn't work!"); } }); // Add the request to the RequestQueue. queue.add(stringRequest); |
步骤:
1.初始化一个RequestQueue
2.构建一个Request,这个Demo里我们使用的是StringRequest
3.将Request对象添加到RequestQueue中
/** * 使用Volley发送一个Request请求 */ public class StringRequestActivity extends AppCompatActivity implements View.OnClickListener { /** 显示网络响应的TextView */ private TextView mTvContent; /** 要访问的URL地址*/ private static final String URL = Constants.SERVER + "volleyTest.txt"; /** Volley的请求队列*/ private RequestQueue mRequestQueue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_string_request); mTvContent = (TextView) findViewById(R.id.tv_content); findViewById(R.id.btn_request).setOnClickListener(this); // 1. 初始化RequestQueue mRequestQueue = Volley.newRequestQueue(this); } @Override public void onClick(View v) { // 2. 构建Request,这里因为想获取的是字符串,因此我们构建一个StringRequest StringRequest request = new StringRequest(URL, new Response.Listener<String>() { @Override public void onResponse(String response) { // 请求成功 mTvContent.setText("请求成功:" + response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // 请求失败 mTvContent.setText("请求失败:" + error.getMessage()); } }); // 3. 将Request添加到RequestQueue mRequestQueue.add(request); } } |
3. Send a Request(Volley网络请求交互流程)
当调用add()方法的时候,Volley会运行一个缓存处理线程和一池子网络分发线程。
当添加Request到RequestQueue的时候,请求先被缓存线程处理和鉴别:如果请求在缓存中有,则将缓存的response响应在缓存线程中进行解析并将解析后的响应分发给主线程。
如果缓存中没有这个Request请求,则将这个Request放到网络队列中。第一个可用的网络线程将Request请求从队列中取出来,执行HTTP传输,并在工作线程中解析响应,将响应写进缓存,并将解析好的响应内容传给主线程进行分发。
Volley将像IO、解析等耗时操作都放到了工作线程(子线程)来执行。你可以在任何线程去添加请求,Volley会将响应直接在主线程去进行分发。
4. Cancel a Request(取消请求)
调用Request.cancel()方法可以取消一个请求。
一旦请求被取消,Volley会确保你的响应操作不会被执行。这意味着在实际操作中你可以在activity的 onStop() 方法中取消所有等待在队列中的请求。你不需要通过检测getActivity() == null 这样的判断来略过处理结果,其他类似onSaveInstanceState() 等保护性的方法里面也都不需要检测。
为了利用这种优势,你应该跟踪所有已经发送的请求,以便在需要的时候,可以取消他们。有一个简便的方法 :你可以为每一个请求对象都绑定一个tag对象。你可以使用这个tag来提供取消的范围。
例如,你可以为你的所有请求都绑定到执行的Activity上(以Activity的this作为Request的tag),然后你可以在onStop() 方法执行 requestQueue.cancelAll(this) 来取消这些请求。同样的,你可以为ViewPager中的所有请求图片的Request对象分别打上对应Tab的tag。并在滑动时取消这些请求,用来确保新生成的tab不会被前面tab的请求任务阻塞。
示例:
1. Define yourtag and add it to your requests.
public static final String TAG = "MyTag"; StringRequest stringRequest; // Assume this exists. RequestQueue mRequestQueue; // Assume this exists. // Set the tag on the request. stringRequest.setTag(TAG); // Add the request to the RequestQueue. mRequestQueue.add(stringRequest);
2. In youractivity's onStop() method,cancel all requests that have this tag.
@Override protected void onStop () { super.onStop(); if (mRequestQueue != null) { mRequestQueue.cancelAll(TAG); } }
取消Request要谨慎,如果你的程序执行逻辑依赖这次请求的结果,那么就要慎重了。
重申一次,取消以后,响应处理不会被调用。
Setting Up a RequestQueue(配置请求队列)
Set Up a Network and Cache (配置网络和缓存)
一个RequestQueue需要2个东西才能完成它的工作:
1.一个Network来完成Request请求的通信传输
2.一个执行缓存操作的Cache
Volley的toolbox中提供了Network和Cache的标准实现:
1.DiskBasedCache
提供了文件和响应一对一的缓存
2.BasicNetwork
提供了基于你偏向的HTTP Client的网络传输
BasicNetwork
是Volley默认的网络实现,一个BasicNetwork
必须先使用你的应用联网所使用的HTTP Client进行初始化。代表性的HTTP Client就是HttpURLConnection。
RequestQueue源码解析
我们是通过“Volley.newRequestQueue”方法来获取RequestQueue的实例的,我们来看一下newRequestQueue方法的实现:
/** * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. * * @param context A {@link Context} to use for creating the cache dir. * @param stack An {@link HttpStack} to use for the network, or null for default. * @return A started {@link RequestQueue} instance. */ public static RequestQueue newRequestQueue(Context context, HttpStack stack) { // 指定缓存文件夹,这里默认采用data/data/包名/cache/volley作为缓存路径 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } // 根据当前SDK版本选择Network,2.3以下使用HttpClient,2.3以上选用HttpURLConnection if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } // 构建BasicNetWork Network network = new BasicNetwork(stack); // 通过DiskBasedCache和BasicNetwork构建RequestQueue实例 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); // 开启消息队列 queue.start(); return queue; } |
从上面的代码可知,Volley先构建了BasicNetwork和DiskBasedCache,然后将这两个对象作为参数传递给了RequestQueue,构建出了RequestQueue对象。紧接着,调用了RequestQueue.start()方法,开启了消息队列。
我们来看下这个“RequestQueue.start()”中做了什么:
/** // 开启之前先停止所有的操作 // 构建缓存线程,并开启缓存线程 // 构建网络线程,默认构建了4个网络线程,并依次开启这4个网络线程 |
在“RequestQueue.start()”中,我们先在start之前进行了stop操作,然后先后构建了缓存线程CacheDispatcher和网络线程NetworkDispatcher,并将它们开启了。
说CacheDispatcher和NetworkDispatcher是线程,是因为它们实际上都是Thread的子类:
public class CacheDispatcher extends Thread{…} |
public class NetworkDispatcher extends Thread{…} |
因此,调用CacheDispatcher或者NetworkDispatcher对象的start()方法,最终关键逻辑一定是走的是run()方法。
CacheDispatcher源码解析
我们先看一下CacheDispatcher的run方法做了什么:
@Override // 从缓存队列中取到Request消息 // 如果这个Request被取消了,则不做任何操作 // 根据Request获取缓存的数据实体对象 request.addMarker("cache-miss"); // 如果没有这个Request的缓存,则将这个Request加入到网络队列中 // 如果能取到Request的缓存,但是缓存已经过期了,也将Request加入到网络队列去 // 能获取到缓存,将缓存中存储的网络响应的原始数据(字节数组)转换成对应的响应数据 // 将Request的响应数据Response分发到主线程 |
由代码可知:
1.CacheDispatcher被开启的时候,内部实际上是维护这一个while(true)死循环,不停的从队列中取Request;
2. 取到Request之后,判断Request的状态是否是被取消的状态,若被取消,则不做任何处理;
3. 若Request没有被取消,则看是否有这个Request对应的缓存数据,若没有缓存数据,则将Request添加到网络队列中去;
4. 有缓存数据,则判断缓存数据是否已经过期,若数据过期,则添加Request到网络队列;
5. 若缓存数据没有过期,则将缓存的原始二进制数据解析成对应的Response数据,比如如果请求的是StringRequest就将二进制数据解析成String;
6. 将解析好的Response响应分发到主线程
将解析好的Response分发到主线程调用的是“mDelivery.postResponse(request, response);”
我们来看一下这个postResponse方法做了什么:
@Override |
而这个mResponsePoster实际上是一个线程池,它重写了自己的execute方法,在execute方法执行时,将对应的Runnable对象借助Handler分发到了主线程去执行:
public class ExecutorDelivery implements ResponseDelivery { …… } |
因此,我们的Response响应最终会在主线程被调用方接收并进行处理。
NetworkDispatcher源码解析
我们再来看一下NetworkDispatcher的run方法做了什么:
@Override // 开启死循环,不停的去队列中取Request // 从缓存队列中取到Request消息 // 如果这个Request被取消了,则不做任何操作 // 传送请求,获取响应 //将网络响应的原始数据(字节数组)转换成对应的响应数据 // 缓存响应数据 // 将响应的消息分发到主线程 |
由代码可知:
1.NetworkDispatcher被开启的时候,内部实际上是维护这一个while(true)死循环,不停的从队列中取Request;
2. 取到Request之后,判断Request的状态是否是被取消的状态,若被取消,则不做任何处理;
3. 若Request没有被取消,则根据Request去执行网络请求,获取响应Response;
4. 将响应的原始二进制数据解析成对应的Response数据,比如如果请求的是StringRequest就将二进制数据解析成String;
5. 缓存响应的数据Response;
6. 将响应的信息分发到主线程
执行网络网络请求,获取Request对应的响应Response,调用的是“mNetwork.performRequest(request)”方法,实际上底层执行的是HurlStack或者HttpClientStack中的performRequest方法。若是执行HurlStack的performRequest方法,则底层其实是调用的HttpURLConnection;若是执行HttpClientStack的performRequest方法,则底层其实是调用的HttpConnection。
分发Response到主线程的逻辑与CacheDispatcher中的分发逻辑完全一致。
Use a Singleton Pattern(使用单例来管理RequestQueue)
创建一个RequestQueue是非常消耗资源的,在一个Application中只需维护一个RequestQueue的实例就可以了。
在写应用的时候,我们可以采用单例模式来管理RequestQueue的实例,节省资源的开销。
Making a Standard Request(发起一个标准的Volley网络请求)
标准的Volley请求有:
StringRequest、ImageRequest、JsonObjectRequest (JsonArrayRequest)
发起一个ImageRequest
/** |
发起一个JsonObjectRequest
/** new Response.Listener<JSONObject>() { |
通过ImageLoader加载图片
ImageLoader是一个帮助类,用于加载和缓存网络图片。
ImageLoader适用于大量的ImageRequest请求,比如在ListView中加载大量的缩略图。ImageLoader提供了内存缓存,这个内存缓存在Volley的缓存之前被使用,来防止图片闪烁。
ImageLoader不会阻塞主线程,当执行IO操作的时候,这一点尤为重要。
ImageLoader可以合并网络响应,使它可以同时分发多个网络响应,极大地改善了性能。
/** |
NetworkImageView
NetworkImageView是基于ImageLoader的,它可以替代ImageView去加载网络图片。
在布局文件中使用NetworkImageView:
<com.android.volley.toolbox.NetworkImageView |
在Java代码中获取NetworkImageView的实例,并调用其setImageUrl方法:
/** * 使用NetworkImageView加载网络图片 */ public class NetworkImageViewActivity extends AppCompatActivity implements View.OnClickListener { private NetworkImageView mNiv; /** 要访问的URL地址*/ private static final String URL = Constants.SERVER + "volley.png"; /** Volley的请求队列*/ private RequestQueue mRequestQueue; /** ImageLoader对象*/ private ImageLoader mImageLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_network_image_view); mNiv = (NetworkImageView) findViewById(R.id.niv); findViewById(R.id.btn_load).setOnClickListener(this); // 1. 初始化RequestQueue mRequestQueue = Volley.newRequestQueue(this); // 2. 初始化ImageLoader,不使用内存缓存,则给一个默认的ImageCache,不重写里面的方法 mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() { @Override public Bitmap getBitmap(String url) { return null; } @Override public void putBitmap(String url, Bitmap bitmap) { } }); } @Override public void onClick(View v) { // 使用NetworkImageView加载网络图片 mNiv.setImageUrl(URL, mImageLoader); } } |
Implementing a Custom Request(自定义Request)
自定义Request需要实现两步:
1.继承Request<T>,其中T表示转换成的Response的类型
2.实现抽象方法 parseNetworkResponse()
和 deliverResponse()
parseNetworkResponse:
解析相应数据并进行封装,然后将响应分发出去。
换句话说就是,将响应返回的原始的字节数组解析成想要的数据类型,然后将解析好的数据分发出去。
@Override protected Response<T> parseNetworkResponse( NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response)); } // handle errors ... } |
deliverResponse:
Volley将你从parseNetworkResponse中返回的对象分发到主线程。
protected void deliverResponse(T response) { listener.onResponse(response); } |
范例:自定义GsonRequest
public class GsonRequest<T> extends Request<T> { private final Gson gson = new Gson(); private final Class<T> clazz; private final Map<String, String> headers; private final Response.Listener<T> listener; /** * Make a GET request and return a parsed object from JSON. * * @param url URL of the request to make * @param clazz Relevant class object, for Gson's reflection * @param headers Map of request headers */ public GsonRequest(String url, Class<T> clazz, Map<String, String> headers, Response.Listener<T> listener, Response.ErrorListener errorListener) { super(Method.GET, url, errorListener); this.clazz = clazz; this.headers = headers; this.listener = listener; } @Override public Map<String, String> getHeaders() throws AuthFailureError { return headers != null ? headers : super.getHeaders(); } @Override protected void deliverResponse(T response) { listener.onResponse(response); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } } |