android volley对于AsyncTask和httpclient的比较
Volley是谷歌2013年在I/O大会期间推出的网络库。开发Volley是因为在Android SDK中缺乏一个用户体验良好的网络加载类。
在Volley发布之前,开发具有客户端与服务端交互程序的唯一工具是标准的java类java.net.HttpURLConnection以及Apache 的 org.apache.http.client。
除开这两个工具的bug不说,所有http链接之外的事情都必须完全自己写,如果你想缓存图片或者发送一个请求,你必须从零开始开发。
幸运的是,我们现在有了Volley,完全填补了我们的这些需求。
1. 为什么是Volley?
避开HttpUrlConnection 和HttpClient
在较低的api版本中(多数是在Gingerbread和Froyo中),HttpUrlConnection和HttpClient 远未达到完美。有一些已知的问题 和bugs 一直未被修复,HttpClient 自上次api更新(API 22)之后就已经过时,意味着不再维护,后续可能也会被移除。
这就是需要转到一个更稳定的处理网络请求的方法的主要原因。
也为了避开AsyncTask
自从引入Honeycomb(API 11)以来,系统强制网络操作必须运行在单独的线程中,UI线程之外。这个重大的变化导致AsyncTask的大量使用。
要使用AsyncTask,首先你得在onPreExecute做一些准备工作,比如定义context。然后在doInBackground 方法中执行后台任务,最后在onPostExecute中处理结果。这要比实现service更简单直接,因此有大量的例子和文章出现。
但是AsyncTask的主要问题是调用的顺序。你无法决定哪个请求走在最前面,哪个请求必须等待,所有的请求都是按照先进先出的顺序,FIFO。
在某些情况下,问题就出来了,比如当你需要加载一个item中带有图片的列表。当用户向下滚动想获得新的数据时,你无法告诉Activity先加载下一页的json数据,只有慢慢等待上一页的图片加载完。对于像facebook和Twiitter这种item数据的重要性远大于相关图片的应用来说,这会导致严重的用户体验问题。
Volley用其强大的cancellation api解决了这个问题。当调用的时候,你不再需要在onPostExecute中检查Activity是否被销毁。这帮助我们避免了不想要的NullPointerException。
它速度更快
前些时候,Google+团队针对每种可以使用的网络请求方式做了一些列的性能测试。当应用在RESTful 风格的应用中时,Volley的得分比其他候选方法高近10倍。
它可以缓存所有东西
Volley可以自动缓存请求,这点确实是关乎生死的问题。让我们暂时回到上面所提到的那个例子。你有一个item的列表 - 假设是JSON 数组 - 并且每个item都包括一段描述和一个缩略图。现在设想一下用户旋转屏幕之后会发生的事情:activity销毁,列表重新加载,同样的还有图片。长话短说就是:严重的资源浪费以及糟糕的用户体验。
事实证明,Volley在克服这种困难中是相当有用的。它可以记住前一个调用同时处理Activity的销毁与重建。它缓存所有的东西,这点你也不用担心。
Small Metadata Operations
Volley在轻量级的调用中堪称完美,比如请求JSON对象,或者列表的一部分,或者被选中item的详情等等。它是为RESTful应用设计的,在这种特定场景下可以发挥自己最好的优势。
但是当把它应用在数据流或者大文件下载中,就不是那么好了。这就是为什么它叫Volley(万箭齐发),而不是叫加农炮。
2. 内部架构
Volley分为三层,每一层都工作在自己的线程中。
underthehood.jpg
主线程
在主线程中,你只允许触发请求与处理返回结果,不能多也不能少。
其结果就是你实际上可以忽略在使用AsyncTask的时候doInBackground 方法里面所做的事情。Volley 自动管理http传输同时捕获网络错误,这些都是以前需要我们自己考虑的。
缓存与网络线程
当你向队列中添加了一个请求,背后发生了几件事情。Volley会检查这个请求是否可以从缓存中得到。如果可以,缓存将负责读取,解析,和分发。否则将传递给网络线程。
在网络线程中,一些列的轮询线程不断的在工作。第一个可用的网络线程线程让请求队列出列,发出http请求,解析返回的结果,并写入到缓存中。最后,把解析的结果分发给主线程的listener中。
3. 开始
第一步: 导入Volley
Volley设置起来并不是很方便,貌似没有官方的Maven repository,这让人理解不能。你必须依赖官方的源代码。
第一件事就是,从它的repository 下载Volley源码。这个很简单,下面的Git命令可以为你做完所有的工作:
git clone https://android.googlesource.com/platform/frameworks/volley
几周之前,你还可以使用ant命令行(android update project -p . 然后 ant jar)将所有的东西都打包成jar,然后直接在Android Studio项目中使用compile files(‘libs/volley.jar’)来导入jar。
但是最近,google更新了Volley成Android Studio的构建风格,让它很难创建一个标准的jar,你仍然可以这样做,但是只能使用老版本的库,虽然这种方式也许是最快速的的,但个人不推荐你使用这种方式。
你应该使用经典的方式来设置Volley,将源码作为一个module导入。在Android Studio中,在打开项目的情况下,选择File > New Module,然后选择Import Existing Project。选择你下载的源码的所在目录然后确认。一个名为Volley的文件夹将出现在你的项目结构中。Android Studio会自动的更新settings.gradle文件以包含Volley module,因此你只需添加你的依赖compile project(‘:volley’),然后就完成了。
不过还有第三种方法,你可以在build.gradle 文件的依赖部分添加这行代码:
compile ‘com.mcxiaoke.volley:library-aar:1.0.15’
这是谷歌官方repository的一个镜像,通常会同步更新。这可能是最简单快速的方法,但是记住,这不是官方的Maven repository,没有保证,不被谷歌支持。
在我看来,最好还是多花几分钟导入官方的代码比较好。这样你就可以轻松的跳到原始定义与实现的代码中,如果需要,你还可以修改Volley。
第二步: 使用Volley
通常Volley只会用到两个类RequestQueue 和Request,你首先创建一个RequestQueue,RequestQueue管理工作线程并将解析的结果发送给主线程。然后你传递一个或者多个Request
对象给他。
Request 的构造函数的参数总是包含类型(GET, POST, 等等),数据源的url,以及事件监听者。根据请求类型的不同,可能还需要一些其他的参数。
在接下来的例子中,我通过Volley.newRequestQueue方法创建了一个RequestQueue 对象,使用Volley定义的默认值。
String url = “https://httpbin.org/html“;
// Request a string response StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { // Result handling System.out.println(response.substring(0,100)); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // Error handling System.out.println("Something went wrong!"); error.printStackTrace(); } }); // Add the request to the queue Volley.newRequestQueue(this).add(stringRequest);
正如你看到的那样,这非常直接:创建一个请求,然后将它添加到请求队列,完成。
注意listener的语法类似AsyncTask.onPostExecute,只是变成了onResponse。这并不是巧合。Volley 的开发者故意将库的api做的和AsyncTask 方法类似。这样从AsyncTask转到Volley就会轻松很多。
如果你需要在几个Activity中触发多个请求,你应该避免使用上面的方法Volley.newRequestQueue.add。在整个项目中实例化一个共享的请求队列会更好些。:
MySingletonClass.getInstance().getRequestQueue().add(myRequest);
我们这个系列的下一篇教程中我们将看到这种用法。
4. 动手实践
处理标准的请求
Volley实现了三种常见的请求类型:
StringRequest
ImageRequest
JsonRequest
每个类都是继承自前面使用了的Request类。我们已经在上个例子中使用了StringRequest ,下面让我们来看看JsonRequest是如何工作的:
String url = “https://httpbin.org/get?site=code&network=tutsplus“;
JsonObjectRequest jsonRequest = new JsonObjectRequest (Request.Method.GET, url, null, new Response.Listener() { @Override public void onResponse(JSONObject response) { // the response is already constructed as a JSONObject! try { response = response.getJSONObject("args"); String site = response.getString("site"), network = response.getString("network"); System.out.println("Site: "+site+"\nNetwork: "+network); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { error.printStackTrace(); } }); Volley.newRequestQueue(this).add(jsonRequest);
很漂亮是吧?你可以看到,返回结果的类型已经是JSONObject的了。如果你需要,你还可以请求JSONArray ,只需用JsonArrayRequest 替代aJsonObjectRequest就可以了。
跟之前一样,构造函数的第一个参数是使用的http方法,然后提供获取json的url参数,第三个参数是null,表示请求url不需要带其他请求参数。最后是用于接收返回JSON的listener以及错误listener。如果你想忽略错误,可以为null。
获取图片则需要更多的工作。有三种请求图片的方法。ImageRequest 是标准方法。通过提供的Url,她将你请求的图片显示在一个普通的ImageView中。压缩与大小调整的操作都发生在工作线程中。第二种选择是ImageLoader
类。你可以将之想象成数量庞大的ImageRequests,比如生成一个带有图片的ListView。第三种选择是NetworkImageView,
下面来看一个例子:
String url = "https://i.imgur.com/Nwk25LA.jpg"; mImageView = (ImageView) findViewById(R.id.image); ImageRequest imgRequest = new ImageRequest(url, new Response.Listener() { @Override public void onResponse(Bitmap response) { mImageView.setImageBitmap(response); } }, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.ARGB_8888, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { mImageView.setBackgroundColor(Color.parseColor("#ff0000")); error.printStackTrace(); } });
Volley.newRequestQueue(this).add(imgRequest);
第一个参数是图片的url,第二个是结果的listener,第三、第四个参数是maxWidth(最大宽度) 和 maxHeight(最大高度),你可以设置为0来忽略他们。然后是用于计算图片所需大小的ScaleType,然后是用于指定图片压缩方式的参数,我建议总是使用 Bitmap.Config.ARGB_8888,最后是一个错误listener。
注意Volley默认会将这种请求的优先级设置为low。
// Snippet taken from ImageRequest.java, // in the Volley source code @Override public Priority getPriority() { return Priority.LOW; }
注:因为是请求图片,所以没有http类型参数,图片请求总是get的。
POST 请求
从get请求切换到post请求是非常简单的。你只需要在request的构造方法中改变Request.Method,同时重写getParams方法,返回包含请求参数的Map
String url = "https://httpbin.org/post"; StringRequest postRequest = new StringRequest(Request.Method.POST, url, new Response.Listener() { @Override public void onResponse(String response) { try { JSONObject jsonResponse = new JSONObject(response).getJSONObject("form"); String site = jsonResponse.getString("site"), network = jsonResponse.getString("network"); System.out.println("Site: "+site+"\nNetwork: "+network); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { error.printStackTrace(); } } ) { @Override protected Map getParams() { Map params = new HashMap<>(); // the POST parameters: params.put("site", "code"); params.put("network", "tutsplus"); return params; } }; Volley.newRequestQueue(this).add(postRequest);
取消一个请求
如果你想取消所有的请求,在onStop方法中添加如下代码:
@Override protected void onStop() { super.onStop(); mRequestQueue.cancelAll(new RequestQueue.RequestFilter() { @Override public boolean apply(Request request) { // do I have to cancel this? return true; // -> always yes } }); }
这样你就不必担心在onResponse被调用的时候用户已经销毁Activity。这种情况下会抛出NullPointerException异。但是post请求则需要继续,即使用户已经改变了Activity。我们可以通过使用tag来做到,在构造GET请求的时候,添加一个tag给它。
// after declaring your request
request.setTag(“GET”);
mRequestQueue.add(request);
如果要取消GET请求,只需简单的添加下面的一行代码:
mRequestQueue.cancelAll(“GET”);
这样你就只会取消GET请求,让其它请求不受影响。注意你必须手动在销毁的Activity中处理这种情况。
管理Cookies和Request优先级
Volley doesn’t provide a method for setting the cookies of a request, nor its priority. It probably will in the future, since it’s a serious omission. For the time being, however, you have to extend the Request class.
Volley并没有提供设置一个请求的cookies以及优先级的方法。也许在将来会有,毕竟这是一个很严重的疏忽。但是目前,你需要继承Request类。
对于cookies的管理,你需要添加请求的header,重写getHeaders方法:
public class CustomRequest extends JsonObjectRequest { // Since we're extending a Request class // we just use its constructor public CustomRequest(int method, String url, JSONObject jsonRequest, Response.Listener listener, Response.ErrorListener errorListener) { super(method, url, jsonRequest, listener, errorListener); } private Map headers = new HashMap<>(); /** * Custom class! */ public void setCookies(List cookies) { StringBuilder sb = new StringBuilder(); for (String cookie : cookies) { sb.append(cookie).append("; "); } headers.put("Cookie", sb.toString()); } @Override public Map getHeaders() throws AuthFailureError { return headers; } }
有了上面的代码,你就可以直接使用setCookies方法为请求提供cookie列// Firstly, you create the list of the cookies,
// conformed to the HTTP conventions
// i.e. key=value
List cookies = new ArrayList<>();
cookies.add(“site=code”);
cookies.add(“network=tutsplus”);
// then you invoke your custom method
customRequest.setCookies(cookies);
// and finally add the request to the queue
Volley.newRequestQueue(this).add(customRequest);优先级,你同样需要继承Request类,重写getPriority方法。下面是代码的样子:
Priority mPriority;
public void setPriority(Priority priority) { mPriority = priority; } @Override public Priority getPriority() { // If you didn't use the setPriority method, // the priority is automatically set to NORMAL return mPriority != null ? mPriority : Priority.NORMAL; }
然后,在主线程中,调用下面的一行代码来设置请求的优先级:
customRequest.setPriority(Priority.HIGH); 你可以从如下的优先级中选择一个: Priority.LOW // images, thumbnails, ... Priority.NORMAL // residual Priority.HIGH // descriptions, lists, ... Priority.IMMEDIATE // login, logout, ...
总结
在这篇文章中,我们看到了Volley是如何工作的。我们首先看到了为什么使用Volley而不是Android SDK中自带的其他解决方案。然后我们深入到库的内部,查看它的工作流程以及支持的请求类型。最后,我们动手创建了几个简单的Request,并实现了一个可以设置优先级与cookies的自定义Request。