Volley 网络请求框架介绍与使用说明

一、前言

Volley 是一个基于 HTTP 的网络开源库,让 Android 应用更快更容易地连接网络,在 GitHub 上可以找到它的源项目。Volley 具有以下优点:

* 自动调度网络请求。

* 支持多并发网络连接。

* 支持缓存。

* 支持请求优先级。

* 支持取消请求,可以取消单个请求,也可以取消包含多个请求的请求块。

* 支持自定义。

* 支持异步数据排序功能。
 
* 支持调试和具备跟踪工具。

Volley 适用于 RPC(远程过程调用:Remote Procedure Call)类型操作,例如将搜索结果页面作为结构化数据获取。Volley 可以很容易与所有协议集成,支持原始字符串,图像和 JSON。

Volley 不适合大型下载或流媒体操作,因为 Volley 在解析期间将所有响应保存在内存中。对于大型下载操作,可以考虑使用 DownloadManager

最简单添加 Volley 的方法是将以下依赖项添加到应用程序的 build.gradle 文件中:

dependencies {
    ...
    compile 'com.android.volley:volley:1.1.1'
}

除此之外,你还可以克隆 Volley 项目库并将其设置为库项目:

  1. 通过在命令行键入以下内容来克隆项目库:
git clone https://github.com/google/volley
  1. 将下载的源作为 Android 库模块导入到应用项目中。

二、使用 Volley 发送请求

使用 Volley 之前,必须将 android.permission.INTERNET 权限添加到应用的清单中。不然,应用无法连接到网络。

1. 使用 newRequestQueue

Volley 提供了一个便捷的方法 Volley.newRequestQueue 为你设置并启动 RequestQueue 队列,该 RequestQueue 使用默认值。例如:

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);

Volley 总是在主线程上传递已解析的响应,这样可以很方便地使用接收到的数据填充 UI 控件。

2. 发送请求

要发送请求,只需构建一个请求并使用 add() 将其添加到 RequestQueue,如上所示。添加请求后,请求将通过网络获取服务,解析并传递其原始响应。

当调用 add() 时,Volley 运行一个高速缓存处理线程和一个网络分派线程池。当向队列添加请求时,它会被缓存线程拾取并进行分类:如果请求可以从缓存中获取服务,则缓存响应将在缓存线程上进行解析,并将解析后的响应在主线程上传递。如果无法从缓存中为请求提供服务,则将其置于网络队列中。第一个可用的网络线程从队列中获取请求,执行 HTTP 事务,在子线程上解析响应,然后将响应写入缓存,并将解析的响应发送回主线程来进行传递。

可以在任意线程中添加请求,但响应始终在主线程上传递。

请求的生命周期

3. 取消请求

要取消请求,请对 Request对象调用 cancel()。一旦取消,Volley 保证你的响应处理回调永远不会被调用。一般可以在 Activity 的 onStop() 方法中取消所有待处理的请求。

但是,这样的话你必须跟踪所有正在进行的请求。有一种更简单的方法:你可以使用一个标记对象与每个请求进行关联。然后,使用此标记对象获得取消请求的范围。例如,使用 Activity 将所有由它发出的请求进行标记,并从 onStop() 中调用 requestQueue.cancelAll(this)。同样,在 ViewPager 选项卡中使用各自的选项卡标记所有缩略图图像请求,并在滑动时取消,以确保新选项卡不会被另一个选项卡的请求持有。

以下是使用字符串标记的示例:

  1. 定义标记并将其添加到你的请求中。
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);
  1. 在 Activity 的 onStop() 方法中,取消所有具有此标记的请求。
@Override
protected void onStop () {
    super.onStop();
    if (mRequestQueue != null) {
        mRequestQueue.cancelAll(TAG);
    }
}

取消请求时要注意,该请求的响应是否是必要的。

三、设置 RequestQueue

1. 设置网络和缓存

RequestQueue 需要两样东西都完成它的工作:一个是用于执行请求传输的网络,一个是用于处理缓存的缓存。Volley 工具箱中已经有标准的实现:DiskBasedCache 提供带有内存索引的单文件响应缓存,BasicNetwork 根据你首选的 HTTP 客户端提供网络传输。

BasicNetwork 是 Volley 的默认网络实现。BasicNetwork 必须被用来连接到网络的 HTTP 客户端初始化。这个客户端通常是 HttpURLConnection

下面代码段显示了初始化 BasicNetwork 的步骤包括设置 RequestQueue

RequestQueue mRequestQueue;

// Instantiate the cache
Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap

// Set up the network to use HttpURLConnection as the HTTP client.
Network network = new BasicNetwork(new HurlStack());

// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(cache, network);

// Start the queue
mRequestQueue.start();

String url ="http://www.example.com";

// Formulate the request and handle the response.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
        new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Do something with the response
    }
},
    new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            // Handle error
    }
});

// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);

// ...

你可以在任何时候创建 RequestQueue,并在得到响应后调用 stop() 来实现单次请求。

但更常见的情况是创建一个 RequestQueue 的单例,使得它在应用的生命周期内保持运行。

2. 使用单例模式

设置一个 RequestQueue 的单例通常效率最高。推荐的方法是实现封装 RequestQueue 和其他 Volley 功能的单例类。另一种方法是创建 Application 的子类并在 Application.onCreate() 方法中设置 RequestQueue,但是这种方法并不推荐,因为静态单例可以以更模块化的方式提供相同的功能。

一个关键概念是 RequestQueue 必须使用 Application 上下文进行实例化,而不是 Activity 上下文。这样可以确保 RequestQueue在应用的生命周期内持续使用,而不是每次重新创建 Activtiy 时重新实例化(例如当用户旋转设备时)。

这里是一个单类,它提供 RequestQueueImageLoader 功能:

public class MySingleton {
    private static MySingleton mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
            private final LruCache<String, Bitmap>
                    cache = new LruCache<String, Bitmap>(20);

            @Override
            public Bitmap getBitmap(String url) {
                return cache.get(url);
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {
                cache.put(url, bitmap);
            }
        });
    }

    public static synchronized MySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

以下是使用单例类执行 RequestQueue 操作的一些示例:

// Get a RequestQueue
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();

// ...

// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest);

四、提出标准请求

Volley 支持的常见请求类型:

  • StringRequest。指定 URL 并接收原始字符串作为响应。

  • JsonObjectRequest 和 JsonArrayRequest(两个都是 JsonRequest 的子类)。指定 URL 并分别获取 JSON 对象或数组作为响应。

如果你的预期响应是这些类型之一,则不必实现自定义请求。

1. 请求 JSON

Volley 为 JSON 请求提供以下类:

  • JsonArrayRequest - 使用给定的 URL 获取 JSONArray 响应体。

  • JsonObjectRequest - 使用给定的 URL 获取 JSONObject 响应体,允许将 JSONObject 作为请求体的一部分传入。

这两个类都基于公共基类 JsonRequest。下面代码段是提取 JSON 数据并在 UI 中将其显示为文本:

String url = "http://my-json-feed";

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
        (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

    @Override
    public void onResponse(JSONObject response) {
        mTextView.setText("Response: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO: Handle error

    }
});

// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest);

五、实现自定义请求

对于那些不支持开箱即用的数据类型,我们需要实现自定义请求类型。

1. 写一个自定义请求

大多数请求在工具箱中都有现成的实现,如果响应是字符串、图像或 JSON,则不需要实现自定义请求。

对于需要实现自定义请求的情况,你只需执行以下操作:

  • 扩展 Request<T> 类,其中 <T> 表示请求所期望的已解析响应的类型。因此,如果解析后的响应是字符串,则通过扩展 Request<String> 创建自定义请求。

  • 实现抽象方法,parseNetworkResponse()deliverResponse()

1.1 parseNetworkResponse

对于给定类型(例如字符串,图像或 JSON),Response 封装解析的响应用来传递。以下是一个示例实现 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
// ...
}

请注意:parseNetworkResponse()NetworkResponse 作为参数 ,其中包含响应有效负载作为 byte []、HTTP 状态代码和响应头。

自定义实现必须返回一个 Response<T>,其中包含你自定义的响应对象、缓存元数据或错误。

如果你的协议具有非标准缓存语义,你可以自己构造一个 Cache.Entry,但大多数请求如下使用:

return Response.success(myDecodedObject,
        HttpHeaderParser.parseCacheHeaders(response));

Volley 在工作线程中调用 parseNetworkResponse()。这确保了耗时的解析操作(例如将 JPEG 解码为 Bitmap)不会阻塞 UI 线程。

1.2 deliverResponse

Volley 在 parseNetworkResponse() 方法中携带返回的对象回到主线程。大多数请求在此处调用回调接口,例如:

protected void deliverResponse(T response) {
        listener.onResponse(response);

六、示例:GsonRequest

Gson 是一个通过反射将 Java 对象转换为 JSON 或者将 JSON 转换为 Java 对象的开源库。你可以定义拥有相同 JSON 键字段的 Java 对象,将类对象传给 Gson,Gson 自动使用响应数据填充字段。

下面是使用 Gson 解析 Volley 请求的完整实现:

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 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,
            Listener<T> listener, 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));
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容