Android网络框架Volley

时间:2021-02-21 01:46:40
Volley是Google I/O 2013推出的网络通信库,在volley推出之前我们一般会选择比较成熟的第三方网络通信库,如:

他们各有优劣,之前个人则比较喜欢用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

Android网络框架Volley

以上是在Google IO的演讲上ppt的配图,从上面这张图我们可以看出,volley适合快速,简单的请求(Json对象,图片加载)。

volley的特性:

  • JSON,图像等的异步下载;
  • 网络请求的排序(scheduling)
  • 网络请求的优先级处理
  • 缓存
  • 多级别取消请求
  • 和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)

接下来,我们来学习简单的使用下volley给我提供的API吧。

1.首先拿到一个请求队列(RequestQueue只需要一个实例即可,不像AsyncTask每次使用都要new一个)

  1. // 初始化RequestQueue一个activity只需要一个
  2. private void initRequestQueue() {
  3. mQueue = Volley.newRequestQueue(getApplicationContext());
  4. }

2.实现volley的异步请求类(JsonObjectRequest,JsonArrayRequest,StringRequest,ImageRequest)

由于用法都相差不大,我就不一一举例了,举几个常用有代表性的例子:

以下代码是StringRequest的get请求:

  1. // get请求
  1. private void loadGetStr(String url) {
  2. StringRequest srReq = new StringRequest(Request.Method.GET, url,
  3. new StrListener(), new StrErrListener()) {
  4. protected final String TYPE_UTF8_CHARSET = "charset=UTF-8";
  5. // 重写parseNetworkResponse方法改变返回头参数解决乱码问题
  6. // 主要是看服务器编码,如果服务器编码不是UTF-8的话那么就需要自己转换,反之则不需要
  7. @Override
  8. protected Response<String> parseNetworkResponse(
  9. NetworkResponse response) {
  10. try {
  11. String type = response.headers.get(HTTP.CONTENT_TYPE);
  12. if (type == null) {
  13. type = TYPE_UTF8_CHARSET;
  14. response.headers.put(HTTP.CONTENT_TYPE, type);
  15. } else if (!type.contains("UTF-8")) {
  16. type += ";" + TYPE_UTF8_CHARSET;
  17. response.headers.put(HTTP.CONTENT_TYPE, type);
  18. }
  19. } catch (Exception e) {
  20. }
  21. return super.parseNetworkResponse(response);
  22. }
  23. };
  24. srReq.setShouldCache(true); // 控制是否缓存
  25. startVolley(srReq);
  26. }

以下代码是JsonObjectRequest的post请求:

  1. // post请求
  2. private void loadPostJson(String url) {
  3. // 第二个参数说明:
  4. // Constructor which defaults to GET if jsonRequest is null, POST
  5. // otherwise.
  6. // 默认情况下设成null为get方法,否则为post方法。
  7. JsonObjectRequest srReq = new JsonObjectRequest(url, null,
  8. new JsonListener(), new StrErrListener()) {
  9. @Override
  10. protected Map<String, String> getParams() throws AuthFailureError {
  11. Map<String, String> map = new HashMap<String, String>();
  12. map.put("w", "2459115");
  13. map.put("u", "f");
  14. return map;
  15. }
  16. };
  17. srReq.setShouldCache(false); // 控制是否缓存
  18. startVolley(srReq);
  19. }

大家注意看的话,无论是JsonObjectReques的postt还是StringRequest的get都需要传入两个监听函数一个是成功一个是失败,成功监听他们会返回相应类型的数据:

  1. // Str请求成功回调
  2. private class StrListener implements Listener<String> {
  3. @Override
  4. public void onResponse(String arg0) {
  5. Log.e(Tag, arg0);
  6. }
  7. }
  8. // Gson请求成功回调
  9. private class GsonListener implements Listener<ErrorRsp> {
  10. @Override
  11. public void onResponse(ErrorRsp arg0) {
  12. Toast.makeText(mContext, arg0.toString(), Toast.LENGTH_LONG).show();
  13. }
  14. }
  15. // 共用失败回调
  16. private class StrErrListener implements ErrorListener {
  17. @Override
  18. public void onErrorResponse(VolleyError arg0) {
  19. Toast.makeText(mContext,
  20. VolleyErrorHelper.getMessage(arg0, mContext),
  21. Toast.LENGTH_LONG).show();
  22. }
  23. }

接下来是ImageRequest

  1. /**
  2. * 第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,
  3. * 指定成0的话就表示不管图片有多大,都不会进行压缩。
  4. *
  5. * @param url
  6. *            图片地址
  7. * @param listener
  8. * @param maxWidth
  9. *            指定允许图片最大的宽度
  10. * @param maxHeight
  11. *            指定允许图片最大的高度
  12. * @param decodeConfig
  13. *            指定图片的颜色属性,Bitmap.Config下的几个常量.
  14. * @param errorListener
  15. */
  16. private void getImageRequest(final ImageView iv, String url) {
  17. ImageRequest imReq = new ImageRequest(url, new Listener<Bitmap>() {
  18. @Override
  19. public void onResponse(Bitmap arg0) {
  20. iv.setImageBitmap(arg0);
  21. }
  22. }, 60, 60, Bitmap.Config.ARGB_8888, new StrErrListener());
  23. startVolley(imReq);
  24. }

看到现在大家肯定会疑惑写了这么多不同类型的Request到底如何运行?接下请看:

  1. // 添加及开始请求
  2. private void startVolley(Request req) {
  3. // 设置超时时间
  4. // req.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, 1, 1.0f));
  5. // 将请求加入队列
  6. mQueue.add(req);
  7. // 开始发起请求
  8. mQueue.start();
  9. }

volley不仅提供了这些请求的方式,还提供了加载图片的一些方法和控件:

比如我们一个列表需要加载很多图片我们可以使用volley给我们提供的ImageLoader( ImageLoader比ImageRequest更加高效,因为它不仅对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。)

  1. public class ImageAdapter extends ArrayAdapter<String> {
  2. private RequestQueue mQueue;
  3. private ImageLoader mImageLoader;
  4. public ImageAdapter(Context context, List<String> objects) {
  5. super(context, 0, objects);
  6. mQueue = Volley.newRequestQueue(getContext());
  7. mImageLoader = new ImageLoader(mQueue, new BitmapCache());
  8. }
  9. @Override
  10. public View getView(int position, View convertView, ViewGroup parent) {
  11. String url = getItem(position);
  12. ImageView imageView;
  13. if (convertView == null) {
  14. imageView = new ImageView(getContext());
  15. } else {
  16. imageView = (ImageView) convertView;
  17. }
  18. // getImageListener(imageView控件对象,默认图片地址,失败图片地址);
  19. ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete);
  20. // get(图片地址,listener,宽,高);自动帮你处理图片的宽高再也不怕大图片的oom了
  21. mImageLoader.get(url, listener,100,200);
  22. return imageView;
  23. }
  24. }

当然还需要重写ImageCache这个类 //使用LruCache再也不用怕加载多张图片oom了

  1. public class <span style="font-family: Arial;">BitmapCache</span><span style="font-family: Arial;"> extends LruCache<String, Bitmap> implements ImageCache {</span>
  2. // LruCache 原理:Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。 当cache已满的时候加入新的item时,在队列尾部的item会被回收。
  3. // 解释:当超出指定内存值则移除最近最少用的图片内存
  4. public static int getDefaultLruCacheSize() {
  5. // 拿到最大内存
  6. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
  7. // 拿到内存的八分之一来做图片内存缓存
  8. final int cacheSize = maxMemory / 8;
  9. return cacheSize;
  10. }
  11. public BitmapLruCache() {
  12. this(getDefaultLruCacheSize());
  13. }
  14. public BitmapLruCache(int sizeInKiloBytes) {
  15. super(sizeInKiloBytes);
  16. }
  17. @Override
  18. protected int sizeOf(String key, Bitmap value) {
  19. return value.getRowBytes() * value.getHeight() / 1024;
  20. }
  21. @Override
  22. public Bitmap getBitmap(String url) {
  23. return get(url);
  24. }
  25. @Override
  26. public void putBitmap(String url, Bitmap bitmap) {
  27. put(url, bitmap);
  28. }
  29. }

Volley还提供的加载图片的控件com.android.volley.NetworkImageView。(这个控件在被从父控件detach的时候,会自动取消网络请求的,即完全不用我们担心相关网络请求的生命周期问题,而且NetworkImageView还会根据你对图片设置的width和heigh自动压缩该图片不会产生多的内存,还有NetworkImageView在列表中使用不会图片错误)

  1. <com.android.volley.toolbox.NetworkImageView
  2. android:id="@+id/network_image_view"
  3. android:layout_width="100dp"
  4. android:layout_height="100dp" />

使用方法:

  1. private void networkImageViewUse(NetworkImageView iv, String url) {
  2. ImageLoader imLoader = new ImageLoader(mQueue, new BitmapLruCache());
  3. iv.setDefaultImageResId(R.drawable.ic_launcher);
  4. iv.setErrorImageResId(R.drawable.ic_launcher);
  5. iv.setImageUrl(url, imLoader);
  6. }

我们说了这么多都是请求,那么如何取消请求呢?

1.activity自动销毁时它会自定取消所有请求。

2.给请求设置标签:

  1. request.setTag("My Tag");

取消所有指定标记的请求:

  1. request.cancelAll("My Tag");

Volley的架构设计:

Android网络框架Volley

其中蓝色部分代表主线程,绿色部分代表缓存线程,橙色部分代表网络线程。我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。

接下来我们要看看如何把volley使用到实战项目里面,我们先考虑下一些问题:

从上一篇来看 mQueue 只需要一个对象即可,new RequestQueue对象对资源一种浪费,我们应该在application,以及可以把取消请求的方法也在application进行统一管理,看以下代码:

  1. package com.chronocloud.lib.base;
  2. import android.app.Application;
  3. import android.text.TextUtils;
  4. import com.android.volley.Request;
  5. import com.android.volley.RequestQueue;
  6. import com.android.volley.VolleyLog;
  7. import com.android.volley.toolbox.Volley;
  8. public class ApplicationController extends Application {
  9. /**
  10. * Log or request TAG
  11. */
  12. public static final String TAG = "VolleyPatterns";
  13. /**
  14. * Global request queue for Volley
  15. */
  16. private RequestQueue mRequestQueue;
  17. /**
  18. * A singleton instance of the application class for easy access in other
  19. * places
  20. */
  21. private static ApplicationController sInstance;
  22. @Override
  23. public void onCreate() {
  24. super.onCreate();
  25. // initialize the singleton
  26. sInstance = this;
  27. }
  28. /**
  29. * @return ApplicationController singleton instance
  30. */
  31. public static synchronized ApplicationController getInstance() {
  32. return sInstance;
  33. }
  34. /**
  35. * @return The Volley Request queue, the queue will be created if it is null
  36. */
  37. public RequestQueue getRequestQueue() {
  38. // lazy initialize the request queue, the queue instance will be
  39. // created when it is accessed for the first time
  40. if (mRequestQueue == null) {
  41. // 1
  42. // 2
  43. synchronized (ApplicationController.class) {
  44. if (mRequestQueue == null) {
  45. mRequestQueue = Volley
  46. .newRequestQueue(getApplicationContext());
  47. }
  48. }
  49. }
  50. return mRequestQueue;
  51. }
  52. /**
  53. * Adds the specified request to the global queue, if tag is specified then
  54. * it is used else Default TAG is used.
  55. *
  56. * @param req
  57. * @param tag
  58. */
  59. public <T> void addToRequestQueue(Request<T> req, String tag) {
  60. // set the default tag if tag is empty
  61. req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
  62. VolleyLog.d("Adding request to queue: %s", req.getUrl());
  63. getRequestQueue().add(req);
  64. }
  65. /**
  66. * Adds the specified request to the global queue using the Default TAG.
  67. *
  68. * @param req
  69. * @param tag
  70. */
  71. public <T> void addToRequestQueue(Request<T> req) {
  72. // set the default tag if tag is empty
  73. req.setTag(TAG);
  74. getRequestQueue().add(req);
  75. }
  76. /**
  77. * Cancels all pending requests by the specified TAG, it is important to
  78. * specify a TAG so that the pending/ongoing requests can be cancelled.
  79. *
  80. * @param tag
  81. */
  82. public void cancelPendingRequests(Object tag) {
  83. if (mRequestQueue != null) {
  84. mRequestQueue.cancelAll(tag);
  85. }
  86. }
  87. }

接下来就是Volley虽然给我们提供了很多不同的Request(JsonObjectRequest,JsonArrayRequest,StringRequest,ImageRequest),但是还是满足不了我们实战中的需求,我们实战开发中经常用到的是xml格式,Gson解析。

接下来我们来看看,如何自定义Request

XmlRequest:

  1. public class XMLRequest extends Request<XmlPullParser> {
  2. private final Listener<XmlPullParser> mListener;
  3. public XMLRequest(int method, String url, Listener<XmlPullParser> listener,
  4. ErrorListener errorListener) {
  5. super(method, url, errorListener);
  6. mListener = listener;
  7. }
  8. public XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {
  9. this(Method.GET, url, listener, errorListener);
  10. }
  11. @Override
  12. protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {
  13. try {
  14. String xmlString = new String(response.data,
  15. HttpHeaderParser.parseCharset(response.headers));
  16. XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
  17. XmlPullParser xmlPullParser = factory.newPullParser();
  18. xmlPullParser.setInput(new StringReader(xmlString));
  19. return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));
  20. } catch (UnsupportedEncodingException e) {
  21. return Response.error(new ParseError(e));
  22. } catch (XmlPullParserException e) {
  23. return Response.error(new ParseError(e));
  24. }
  25. }
  26. @Override
  27. protected void deliverResponse(XmlPullParser response) {
  28. mListener.onResponse(response);
  29. }
  30. }

GsonRequest(注意需要自行导入gson.jar):

  1. public class GsonRequest<T> extends Request<T> {
  2. private final Listener<T> mListener;
  3. private Gson mGson;
  4. private Class<T> mClass;
  5. public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,
  6. ErrorListener errorListener) {
  7. super(method, url, errorListener);
  8. mGson = new Gson();
  9. mClass = clazz;
  10. mListener = listener;
  11. }
  12. public GsonRequest(String url, Class<T> clazz, Listener<T> listener,
  13. ErrorListener errorListener) {
  14. this(Method.GET, url, clazz, listener, errorListener);
  15. }
  16. @Override
  17. protected Response<T> parseNetworkResponse(NetworkResponse response) {
  18. try {
  19. String jsonString = new String(response.data,
  20. HttpHeaderParser.parseCharset(response.headers));
  21. return Response.success(mGson.fromJson(jsonString, mClass),
  22. HttpHeaderParser.parseCacheHeaders(response));
  23. } catch (UnsupportedEncodingException e) {
  24. return Response.error(new ParseError(e));
  25. }
  26. }
  27. @Override
  28. protected void deliverResponse(T response) {
  29. mListener.onResponse(response);
  30. }
  31. }

接下只差最后一步了就是封装它的错误处理,使用过volley的都知道,volley的监听错误提示都是NoConnectionError。。。等等,这类的错误提示,显然这不是我们想给用户呈现的错误提示,因为就算提示了用户也不明白什么意思,所以我们还要封装一下,能让用户看的更清楚的理解这些错误提示。ym—— Android网络框架Volley(体验篇)我们讲过每个请求都需要设置一个失败的监听:

  1. // 共用失败回调
  2. private class StrErrListener implements ErrorListener {
  3. @Override
  4. public void onErrorResponse(VolleyError arg0) {
  5. Toast.makeText(mContext,
  6. VolleyErrorHelper.getMessage(arg0, mContext),
  7. Toast.LENGTH_LONG).show();
  8. }
  9. }

以上代码有个VolleyError对象,我们可以从这个对象上下手:

  1. package com.example.volley;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import android.content.Context;
  5. import com.android.volley.AuthFailureError;
  6. import com.android.volley.NetworkError;
  7. import com.android.volley.NetworkResponse;
  8. import com.android.volley.NoConnectionError;
  9. import com.android.volley.ServerError;
  10. import com.android.volley.TimeoutError;
  11. import com.android.volley.VolleyError;
  12. import com.google.gson.Gson;
  13. import com.google.gson.reflect.TypeToken;
  14. //正如前面代码看到的,在创建一个请求时,需要添加一个错误监听onErrorResponse。如果请求发生异常,会返回一个VolleyError实例。
  15. //以下是Volley的异常列表:
  16. //AuthFailureError:如果在做一个HTTP的身份验证,可能会发生这个错误。
  17. //NetworkError:Socket关闭,服务器宕机,DNS错误都会产生这个错误。
  18. //NoConnectionError:和NetworkError类似,这个是客户端没有网络连接。
  19. //ParseError:在使用JsonObjectRequest或JsonArrayRequest时,如果接收到的JSON是畸形,会产生异常。
  20. //SERVERERROR:服务器的响应的一个错误,最有可能的4xx或5xx HTTP状态代码。
  21. //TimeoutError:Socket超时,服务器太忙或网络延迟会产生这个异常。默认情况下,Volley的超时时间为2.5秒。如果得到这个错误可以使用RetryPolicy。
  22. public class VolleyErrorHelper {
  23. /**
  24. * Returns appropriate message which is to be displayed to the user against
  25. * the specified error object.
  26. *
  27. * @param error
  28. * @param context
  29. * @return
  30. */
  31. public static String getMessage(Object error, Context context) {
  32. if (error instanceof TimeoutError) {
  33. return context.getResources().getString(
  34. R.string.generic_server_down);
  35. } else if (isServerProblem(error)) {
  36. return handleServerError(error, context);
  37. } else if (isNetworkProblem(error)) {
  38. return context.getResources().getString(R.string.no_internet);
  39. }
  40. return context.getResources().getString(R.string.generic_error);
  41. }
  42. /**
  43. * Determines whether the error is related to network
  44. *
  45. * @param error
  46. * @return
  47. */
  48. private static boolean isNetworkProblem(Object error) {
  49. return (error instanceof NetworkError)
  50. || (error instanceof NoConnectionError);
  51. }
  52. /**
  53. * Determines whether the error is related to server
  54. *
  55. * @param error
  56. * @return
  57. */
  58. private static boolean isServerProblem(Object error) {
  59. return (error instanceof ServerError)
  60. || (error instanceof AuthFailureError);
  61. }
  62. /**
  63. * Handles the server error, tries to determine whether to show a stock
  64. * message or to show a message retrieved from the server.
  65. *
  66. * @param err
  67. * @param context
  68. * @return
  69. */
  70. private static String handleServerError(Object err, Context context) {
  71. VolleyError error = (VolleyError) err;
  72. NetworkResponse response = error.networkResponse;
  73. if (response != null) {
  74. switch (response.statusCode) {
  75. case 404:
  76. case 422:
  77. case 401:
  78. try {
  79. // server might return error like this { "error":
  80. // "Some error occured" }
  81. // Use "Gson" to parse the result
  82. HashMap<String, String> result = new Gson().fromJson(
  83. new String(response.data),
  84. new TypeToken<Map<String, String>>() {
  85. }.getType());
  86. if (result != null && result.containsKey("error")) {
  87. return result.get("error");
  88. }
  89. } catch (Exception e) {
  90. e.printStackTrace();
  91. }
  92. // invalid request
  93. return error.getMessage();
  94. default:
  95. return context.getResources().getString(
  96. R.string.generic_server_down);
  97. }
  98. }
  99. return context.getResources().getString(R.string.generic_error);
  100. }
  101. }

以上代码中引用的xml是:

  1. <string name="no_internet">无网络连接~!</string>
  2. <string name="generic_server_down">连接服务器失败~!</string>
  3. <string name="generic_error">网络异常,请稍后再试~!</string>

接下来,数据请求这一块已经说完了,我们来说下图片这一块,我个人喜欢使用universal-image-loader而不是volley自己提供的(个人认为使用起来universal-image-loader更便捷一些)。

下面主要是讲Volley在某些细节方面的选择和实现.值得我们学习的地方以及如果更好的使用Volley。

1.Volley本地缓存为什么有时候不会进行缓存?

缓存使用前提服务器必须支持,缓存,配置Cache-Control头信息,

因为Volley需要从这些头信息判断缓存是否已经过期。如果已经过期Volley将会重新从网络获取数据。

本人用抓包工具抓了无法缓存的返回头信息

Android网络框架Volley

可以支持缓存的头信息

Android网络框架Volley

2.如果我们自己写一个网络请求框架,我们内部实现会选择使用HttpURLConnection还是HttpClient?

我们通过源码来看看Volley是如何选择使用的

  1. public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
  2. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  3. String userAgent = "volley/0";
  4. try {
  5. String packageName = context.getPackageName();
  6. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  7. userAgent = packageName + "/" + info.versionCode;
  8. } catch (NameNotFoundException e) {
  9. }
  10. if (stack == null) {
  11. if (Build.VERSION.SDK_INT >= 9) {
  12. stack = new HurlStack();
  13. } else {
  14. stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
  15. }
  16. }
  17. Network network = new BasicNetwork(stack);
  18. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  19. queue.start();
  20. return queue;
  21. }

这里会判断如果手机系统版本号是大于9(Android 2.3)的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例。实际上HurlStack的内部就是使用HttpURLConnection进行网络通讯的,而HttpClientStack的内部则是使用HttpClient进行网络通讯的,这里为什么这样选择呢?参考文章:Android访问网络,使用HttpURLConnection还是HttpClient?这就是它为何这么快的原因。

从这点我们可以学习到,要针对不同SDK版本做去相应更优的处理方式,这样才能达到最好的效果。

3.Volley给我们提供了ImageRrequest,ImageLoader,NetworkImageView,它们分别使用于什么场景为什么?

单张图片的加载可以通过发起
ImageReuqst 请求来实现,但为了应用内存缓存,推荐使用 ImageLoader

NetwoekImageView专门用于批量图片加载的场景:

  1. public class NetworkImageView extends ImageView {
  2. private String mUrl;
  3. // 默认显示的图片
  4. private int mDefaultImageId;
  5. // 加载失败时显示的图片
  6. private int mErrorImageId;
  7. // 主方法入口
  8. public void setImageUrl(String url, ImageLoader imageLoader) {
  9. mUrl = url;
  10. mImageLoader = imageLoader;
  11. // 这个方法将会对ImageView的尺寸是否有效、是否为同一张图片进行判断
  12. // 在执行新请求前,也会取消上一次在这个View里启动的另一个已经失效的请求
  13. // 由于篇幅的限制以及代码行数太多,这里不贴出具体实现的代码
  14. loadImageIfNecessary(false);
  15. }
  16. // 如果图片已经滑离屏幕,变为不可见,将执行取消请求的操作
  17. @Override
  18. protected void onDetachedFromWindow() {
  19. if (mImageContainer != null) mImageContainer.cancelRequest();
  20. super.onDetachedFromWindow();
  21. }
  22. }

在ListView加载多张图片的时候,NetworkImageView可以防止出现图片错误的现象,以及当NetworkImageView滑出屏幕的时候会取消加载图片请求,这样就保证加载多张图片的时候用户快速滑动列表的流畅性。给用户带来更优的体验。