现在的位置: 首页 > 移动开发 > 正文

在Android中使用Volley进行异步HTTP请求

2019年09月05日 移动开发 ⁄ 共 11232字 ⁄ 字号 评论关闭
文章目录

前言

其实CSDN上有位前辈已经将该博客翻译过来了,链接在下面;我自己翻译一遍只是为了加深一下理解,仅此而已!

由于个人不但英文水平很差,技术水平方面也是菜鸟,因此文中有很多词不达意的地方,如果您的英文水平较好,请移步到原文阅读,以免我蹩脚的翻译对您产生误导。

另外,也可以到这里看看:http://blog.csdn.net/xyz_lmn/article/details/12177005


原文链接:http://arnab.ch/blog/2013/08/asynchronous-http-requests-in-android-using-volley/

Volley对Android开发者来说是一把新的瑞士军刀,它提供了一些使Android应用网络请求更简单快速的功能。Volley的好处在于它对低级的HTTP请求细节进行了抽象,以帮助你关注于编写优美干净的RESTful样式HTTP请求。另外,Volley中所有的请求都在一个额外线程中执行,而不会阻塞你的“主线程”。

Volley的特性

Volley的主要特性:

  • 创建异步RESTful HTTP 请求的高级API
  • 优雅健壮的请求队列
  • 可扩展的架构,允许开发者实现自定义的请求和响应处理机制
  • 使用外部HTTP客户端库的能力
  • 健壮的请求缓存策略
  • 从网络加载缓存图片的自定义视图(NetworkImageView ,ImageLoader 等)

为什么使用异步HTTP请求

在Android中采用异步HTTP请求一直是一个好的做法,实际上从HoneyComb 开始这已经不仅仅是“好的做法”,而是你必须将HTTP请求做成异步的否则你会得到一个android.os.NetworkOnMainThreadException 异常。阻塞主线程会导致一些严重的后果,它妨碍UI渲染,降低应用的用户体验,这些都会引发可怕的ANR(Application Not Responding)。为了避免这些陷阱,作为开发者的你应该总是保证你的HTTP请求是异步的。

怎样使用Volley

此篇博客中我们准备涵盖以下条目,读完本文后你应该对Volley有一个清晰的认识,并且可以将它应用在你的项目中。

  • 安装和使用Volley
  • 使用请求队列
  • 创建异步的JSON和String类型的HTTP请求
  • 取消请求
  • 重试失败的请求,自定义请求超时
  • 设置请求头(HTTP headers)
  • 使用Cookie
  • 错误处理

安装和使用Volley

Volley是AOSP(Android Open-Source Project)的一部分,现在已经不再作为jar包发布,将Volley包含进你项目的最简单方式是克隆Volley仓库并将其作为一个库项目引入。

作为库项目使用

使用下面的命令从git上克隆Volley然后将其作为Android库项目导入:

$ git clone https://android.googlesource.com/platform/frameworks/volley

作为jar包使用

使用上面的命令克隆Volley后,再运行下面的命令将Volley导出为一个jar包,最后将导出的jar包入到你项目的/libs目录:

# in Volley project root (ensure the `android` executable is in your path)
$ android update project -p .
# the following command will create a JAR file and put it in ./bin
$ ant jar

使用请求队列

在Volley中所有的请求都会先放进一个队列然后处理,下面代码演示了如何创建一个请求队列:

RequestQueue mRequestQueue = Volley.newRequestQueue(this); // 'this' is Context

理想情况下你应该有一个集中HTTP请求的地方来放置你的队列,最好的初始化队列的地方是在你的Application类中。下面演示了如何做到这一点:

public class ApplicationController extends Application {

    /**
     * Log or request TAG
     */
    public static final String TAG = "VolleyPatterns";

    /**
     * Global request queue for Volley
     */
    private RequestQueue mRequestQueue;

    /**
     * A singleton instance of the application class for easy access in other places
     */
    private static ApplicationController sInstance;

    @Override
    public void onCreate() {
        super.onCreate();

        // initialize the singleton
        sInstance = this;
    }

    /**
     * @return ApplicationController singleton instance
     */
    public static synchronized ApplicationController getInstance() {
        return sInstance;
    }

    /**
     * @return The Volley Request queue, the queue will be created if it is null
     */
    public RequestQueue getRequestQueue() {
        // lazy initialize the request queue, the queue instance will be
        // created when it is accessed for the first time
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    /**
     * Adds the specified request to the global queue, if tag is specified
     * then it is used else Default TAG is used.
     * 
     * @param req
     * @param tag
     */
    public <T> void addToRequestQueue(Request<T> req, String tag) {
        // set the default tag if tag is empty
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);

        VolleyLog.d("Adding request to queue: %s", req.getUrl());

        getRequestQueue().add(req);
    }

    /**
     * Adds the specified request to the global queue using the Default TAG.
     * 
     * @param req
     * @param tag
     */
    public <T> void addToRequestQueue(Request<T> req) {
        // set the default tag if tag is empty
        req.setTag(TAG);

        getRequestQueue().add(req);
    }

    /**
     * Cancels all pending requests by the specified TAG, it is important
     * to specify a TAG so that the pending/ongoing requests can be cancelled.
     * 
     * @param tag
     */
    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

使用异步的HTTP请求

Volley提供了下面的工具类,用来创建异步的HTTP请求:

  • JsonObjectRequest – 发送及从服务器接收JSON Object类型的数据
  • JsonArrayRequest – 接收JSON Array类型的数据
  • StringRequest – 以String类型得到响应体(如果你希望自己解析响应)

注意:如果需要在HTTP请求体中携带参数,需要重写请求类的getParams()和getBody()两个方法中的任何一个。

JsonObjectRequest

此类用来发送和接收Json对象。重载的构造方法允许设置适当的请求方法(DELETE,GET,POST及PUT)。如果你的后端是RESTful结构这将是你频繁使用的类。

下面的例子演示了怎样发送GET和POST请求。

HTTP GET方式

final String URL = "/volley/resource/12";
// pass second argument as "null" for GET requests
JsonObjectRequest req = new JsonObjectRequest(URL, null,
       new Response.Listener<JSONObject>() {
           @Override
           public void onResponse(JSONObject response) {
               try {
                   VolleyLog.v("Response:%n %s", response.toString(4));
               } catch (JSONException e) {
                   e.printStackTrace();
               }
           }
       }, new Response.ErrorListener() {
           @Override
           public void onErrorResponse(VolleyError error) {
               VolleyLog.e("Error: ", error.getMessage());
           }
       });

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

HTTP POST方式

final String URL = "/volley/resource/12";
// Post params to be sent to the server
HashMap<String, String> params = new HashMap<String, String>();
params.put("token", "AbCdEfGh123456");

JsonObjectRequest req = new JsonObjectRequest(URL, new JSONObject(params),
       new Response.Listener<JSONObject>() {
           @Override
           public void onResponse(JSONObject response) {
               try {
                   VolleyLog.v("Response:%n %s", response.toString(4));
               } catch (JSONException e) {
                   e.printStackTrace();
               }
           }
       }, new Response.ErrorListener() {
           @Override
           public void onErrorResponse(VolleyError error) {
               VolleyLog.e("Error: ", error.getMessage());
           }
       });

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

JsonArrayRequest

本类可以用来接收JSON数组而非Json对象格式的数据,并且到目前为止只支持HTTP GET请求。由于只支持GET请求,因此如果指定一些参数。构造函数不接收请求参数。

final String URL = "/volley/resource/all?count=20";
JsonArrayRequest req = new JsonArrayRequest(URL, new Response.Listener<JSONArray> () {
    @Override
    public void onResponse(JSONArray response) {
        try {
            VolleyLog.v("Response:%n %s", response.toString(4));
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        VolleyLog.e("Error: ", error.getMessage());
    }
});

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

StringRequest

本类可以用来以字符串形式从服务器获取响应,当需要自己解析服务器响应时可以使用该类,比如XML响应。它同样提供了重载的构造方法以支持自定义的请求。

final String URL = "/volley/resource/recent.xml";
StringRequest req = new StringRequest(URL, new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        VolleyLog.v("Response:%n %s", response);
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        VolleyLog.e("Error: ", error.getMessage());
    }
});

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

有时可能需要设置请求的优先级,在某些情况下你想把某些(某个)请求设置为更高的优先级是可能的。队列中的请求在保证FIFO(先进先出)次序的情况下按照优先级的高低依次处理。设置优先级我们需要重写请求类的getPriority()方法,当前可用的优先级有Priority.LOW、 Priority.NORMAL、Priority.HIGH 和 Priority.IMMEDIATE几种。

取消请求

Volley提供了强大的API来取消挂起或者正在运行的请求,需要取消请求的一个理由是如果用户在HTTP请求处理过程中旋转了设备,这时因为Activity将会重新启动,因此你需要取消这个请求。

取消请求最简单的方式是调用请求队列的cancelAll(tag) 方法,这只会在你在请求被添加到队列之前为请求对象设置了标签时起作用。标记请求是为了允许调用一个方法就可以取消所有标记了本标签的待处理请求。

为请求对象设置标签:

request.setTag("My Tag");

在上面所展示的ApplicationController类中,需要按下面的方式添加请求:

ApplicationController.getInstance().addToRequestQueue(request, "My Tag");

取消指定标签下所有的请求:

mRequestQueue.cancelAll("My Tag");

在上面所展示的ApplicationController类中,需要按下面的方式取消请求:

ApplicationController.getInstance().cancelPendingRequests("My Tag");

重试失败的请求及自定义请求超时时间

在Volley中没有直接设置请求超时时间的方式,但有一个变通方案,就是要为请求对象设置RetryPolicy(重试策略)。构造DefaultRetryPolicy 类对象需要一个名为initialTimeout 的参数,本参数可以用来指定请求超时时间,如果最大重试次数设置为1则Volley在请求超过超时时间时不会再重试该请求。

request.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, 1, 1.0f));

如果你希望重试失败的请求,同样可以使用上面的代码,只增加重试次数就可以了(第二个参数)。注意最后了个参数,它允许指定一个退避乘数可以用来实现“指数退避”来从RESTful服务器请求数据。
(不理解“指数退避”的意思,本句翻译摘抄自http://blog.csdn.net/xyz_lmn/article/details/12177005,表示感谢)。

设置请求头

某些时候有必要为HTTP请求添加头信息,常见的例子就是添加“Authorization”信息。在Volley中Request类提供了一个名为getHeaders()的方法,在必要时可以重写该方法来添加自定义头信息。

添加自定义头信息:

JsonObjectRequest req = new JsonObjectRequest(URL, new JSONObject(params),
           new Response.Listener<JSONObject>() {
               @Override
               public void onResponse(JSONObject response) {
                   // handle response
               }
           }, new Response.ErrorListener() {
               @Override
               public void onErrorResponse(VolleyError error) {
                   // handle error                        
               }
           }) {

       @Override
       public Map<String, String> getHeaders() throws AuthFailureError {
           HashMap<String, String> headers = new HashMap<String, String>();
           headers.put("CUSTOM_HEADER", "Yahoo");
           headers.put("ANOTHER_CUSTOM_HEADER", "Google");
           return headers;
       }
   };

使用Cookie

Volley没有直接提供设置cookies的的API。这是有道理的,因为Volley的核心理念是提供干净的API来实现RESTful HTTP请求,目前很多RESTful API提供者更倾向于tokens验证来代替cookies。因此在Volley中使用Cookie需要做一些额外的工作。

下面是一个来自上面所展示的ApplicationController 类的getRequestQueue()方法,在这里作了一些修改,使它包含了设置cookie所必须的粗糙代码:

// http client instance
private DefaultHttpClient mHttpClient;
public RequestQueue getRequestQueue() {
    // lazy initialize the request queue, the queue instance will be
    // created when it is accessed for the first time
    if (mRequestQueue == null) {
        // Create an instance of the Http client. 
        // We need this in order to access the cookie store
        mHttpClient = new DefaultHttpClient();
        // create the request queue
        mRequestQueue = Volley.newRequestQueue(this, new HttpClientStack(mHttpClient));
    }
    return mRequestQueue;
}

/**
 * Method to set a cookie
 */
public void setCookie() {
    CookieStore cs = mHttpClient.getCookieStore();
    // create a cookie
    cs.addCookie(new BasicClientCookie2("cookie", "spooky"));
}


// add the cookie before adding the request to the queue
setCookie();

// add the request to the queue
mRequestQueue.add(request);

错误处理

如你所见,在上面示例代码中,当你创建一个请求对象时你需要指定一个错误监听器,Volley将在请求处理过程中出现错误时调用该监听器的onErrorResponse回调方法,并传入一个VolleyError对象作为参数。

下面是Volley中的异常列表:

  • AuthFailureError - 如果请求是尝试做HTTP身份验证,可能会导致这个错误
  • NetworkError – Socket没有连接,服务器宕机,DNS问题都会导致这个错误
  • NoConnectionError – 与NetworkError类似,但它是在设备没有网络连接时触发;你的错误处理逻辑可以将NetworkError和NoConnectionError结合在一起同等对待。
  • ParseError - 当使用JsonObjectRequest 或者 JsonArrayRequest 时,如果接收到的JSON数据格式不正确可能导致这个错误
  • ServerError - 服务器响应给出一个错误,很可能带有4xx或者5xx等HTTP状态码
  • TimeoutError – Socket连接超时,可能是服务器太忙或者网络延迟。Volley的默认超时时间是2.5秒,如果一直得到这个错误可以使用RetryPolicy

可以使用类似于下面的帮助类来显示某个异常出现时的错误消息:

public class VolleyErrorHelper {
     /**
     * Returns appropriate message which is to be displayed to the user 
     * against the specified error object.
     * 
     * @param error
     * @param context
     * @return
     */
  public static String getMessage(Object error, Context context) {
      if (error instanceof TimeoutError) {
          return context.getResources().getString(R.string.generic_server_down);
      }
      else if (isServerProblem(error)) {
          return handleServerError(error, context);
      }
      else if (isNetworkProblem(error)) {
          return context.getResources().getString(R.string.no_internet);
      }
      return context.getResources().getString(R.string.generic_error);
  }
  
  /**
  * Determines whether the error is related to network
  * @param error
  * @return
  */
  private static boolean isNetworkProblem(Object error) {
      return (error instanceof NetworkError) || (error instanceof NoConnectionError);
  }
  /**
  * Determines whether the error is related to server
  * @param error
  * @return
  */
  private static boolean isServerProblem(Object error) {
      return (error instanceof ServerError) || (error instanceof AuthFailureError);
  }
  /**
  * Handles the server error, tries to determine whether to show a stock message or to 
  * show a message retrieved from the server.
  * 
  * @param err
  * @param context
  * @return
  */
  private static String handleServerError(Object err, Context context) {
      VolleyError error = (VolleyError) err;
  
      NetworkResponse response = error.networkResponse;
  
      if (response != null) {
          switch (response.statusCode) {
            case 404:
            case 422:
            case 401:
                try {
                    // server might return error like this { "error": "Some error occured" }
                    // Use "Gson" to parse the result
                    HashMap<String, String> result = new Gson().fromJson(new String(response.data),
                            new TypeToken<Map<String, String>>() {
                            }.getType());

                    if (result != null && result.containsKey("error")) {
                        return result.get("error");
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
                // invalid request
                return error.getMessage();

            default:
                return context.getResources().getString(R.string.generic_server_down);
            }
      }
        return context.getResources().getString(R.string.generic_error);
  }
}

结论

Volley真的是一个优雅的库,而且你应该认真地去试用一下;它将助你简化网络请求并带来很多其它的益处。

事实上我曾试图尽可能全面了解Volley,我准备在另外的博客中介绍如何使用Volley做图片加载,以及我在项目中使用Volley时所认识到的一些gotchas(性能和可伸缩性??)。

谢谢阅读,希望你喜欢!

参考

NetworkOnMainThreadException:http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html

Application:http://developer.android.com/reference/android/app/Application.html

抱歉!评论已关闭.