Volley传输网络数据

概述

Volley是一个HTTP的库,能够让Android Apps的网络请求更容易,更迅速。Vollery在github可以获取到。

Volley有以下优点:

  • 自动调度网络请求
  • 多个并发的网络连接
  • 标准的Http cache coherence
  • 支持请求优先级
  • 取消请求的API,可以取消简单的请求,也可以取消一组网络请求
  • 可定制性,比如重试,重定向等
  • 强大的排序,可以轻松地使用网络请求的数据正确的填充到UI上。
  • 调试和跟踪的工具

Volley的优点在于使用RPC-type的操作来填充UI,比如获取到一页的搜索结果作为结构化的数据。它可以轻松地与任何协议集成,Volley自带了字符串,图片和JSON的支持。通过内嵌一些特性,Volley可以让你从模板的代码中摆脱出来,而只需关注App的特定的逻辑。

Volley不适合大型的下载或者流操作。因为Volley在解析的时候会把所有的响应保存到内存中,对于大型的下载操作,可以考虑使用DownloadManager

Vollery核心库是在github上开发的,主要有请求分发以及一系列的通用的工具类,可以在Volley的工具箱中获取到。在项目中添加Volley最容易的方式是在gradle文件中添加以下内容。

1
2
3
4
dependencies {
    ...
    compile 'com.android.volley:volley:1.0.0'
}

你也可以克隆Volley仓库并且设置它作为一个library工程

  1. 通过下列的命令来克隆Volley的仓库

    git clone https://github.com/google/volley

  2. 导入源码到你的app工程里作为Android library,在Create an Android Library查看如何创建Android library库。

发送简单请求

更高层次上,可以创建一个RequestQueue传递给它Request对象。RequestQueue管理工作线程,这些工作线程用于运行网络请求,读写缓存和解析网络响应。Request解析原始的响应并且Volley分派解析后的响应返回到主线程中。

这节描述如何使用Volley.newRequestQueue方法发送请求,此方法设置一个RequestQueue供我们使用。看设置RequestQueue一节也可以了解如何自己设置RequestQueue

这节描述如何使用RequestQueue去添加请求和取消请求。

添加网络权限

使用Volley,必须在manifest文件中添加android.permission.INTERNET权限。

使用newRequestQueue

Volley提供了方便的方法Volley.newRequestQueue去创建RequestQueue,使用默认行为启动队列。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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是很方便的,同时你也在响应监听器中可以修改UI控件。

发送请求

为了发送request,需要简单地构建一个Request并且使用add方法添加到RequestQueue,正如显示的,一旦添加了Request,它将被传递,获取服务,有它自己解析响应,最终传递结果。

当调用add方法的时候,Volley会运行一个缓存处理线程和网络分发的线程。当添加请求到队列中,请求就会被cache线程选取并且分类:如果请求能从cache中获取,缓存的响应将会在cache线程中解析并把解析后的响应传递到主线程中。如果请求不能从cache中获取,请求将被放置到网络队列里。首先网络线程会从网络队列中获取Request,执行HTTP传输,在工作线程中解析响应,把响应写入到缓存,然后把解析后的响应传递到主线程中。

注意这些昂贵的开销像阻塞的I/O和解析和解码是在工作线程完成的。你可以在任意线程中添加Request,但是响应总是被传递到主线程中。

图1展示了request的生命周期

这里写图片描述

取消Request

为了取消Request,调用cancel()方法。一旦Request被取消,Volley保证响应的handler不会被调用。这意味着在实践中你能在Activity的onStop()方法取消所有的request并且也不需要在响应的Handler中有getActivity()==null的检测,是否onSaveInstanceState()已经被调用了,或者其他的防御的编码。

为了利用这个行为,需要做的就是跟踪所有的request,能够在恰当的时机取消它们。一个比较简单的方法:可以给每个Request对象分配一个tag对象。你可以使用这个tag去支持取消一定范围的Request。例如,可以使用activity类标记所有请求,然后在onStop()方法中调用requestQueue.cancelAll(this)。类似情景是可以在ViewPager的当前的Tab标记所有的缩略图的请求,在滑动到新的Tab的时候然后取消request。

下面的例子使用字符串来添加tag

  1. 定义tag,并在request中添加tag
1
2
3
4
5
6
7
8
9
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()方法中,使用tag取消所有的Request

    1
    2
    3
    4
    5
    6
    7
    
    @Override
    protected void onStop () {
        super.onStop();
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(TAG);
        }
    }
    

取消请求时请留意。如果你依赖于你的响应处理程序(也就是回调)来推进状态或启动另一个进程,则需要解决此问题。再次提醒,一旦Request被取消响应的处理代码(也就是回调)将不被调用。

设置RequestQueue

前面的章节讲解了如何使用Volley.newRequestQueue去设置一个默认行为的RequestQueue,这节将会带你领略创建RequestQueue的真正的步骤,设置自定义的RequestQueue。

这节也描述了推荐的创建RequestQueue的方式单例,这样可以保证RequestQueue与APP的生命周期一致。

设置Network和Cache

一个RequestQueue需要两件事来完成它的工作:一个是network去执行Request的传输,和一个cache来处理缓存。在Volley的工具库中有相关的标准实现:DiskBasedCache提供了 one-file-per-response的缓存带着内存的索引,和BasicNetwork提供了网络传输依据于你的首选的HTTP客户端。

BasicNetwork是Volley的默认网络实现。一个BasicNetwork必须被一个HTTP客户端初始化。典型的是HttpURLConnection。(也可以使用OkHttp来实现)

下面的代码展示了设置RequestQueue的步骤:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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,并在您的响应或错误返回后在RequestQueue上调用stop()。但是更常用的情景是创建RequestQueue作为单例并且保持RequestQueue在APP的生命周期中一直运行,下节将会讲解。

使用单例模式

如果你的应用一直使用网络,那或许需要设置单例的RequestQueue并且保证RequestQueue与在APP的生命周期一致。你可以通过各种方式实现。推荐的方法是实现单例类包裹着RequestQueue和其他的Volley功能。另一个方式实现Application的子类设置RequeueQueue在 Application.onCreate()。但是这种方式是不鼓励的;静态的单例可以更加模块化的方式提供相同的功能。

一个关键的点是RequestQueue实例化时使用Appliction context而不是Activity context。确保RequestQueue在应用的生命周期中一直在运行,而不是每次随着activity的创建一直被销毁再创建。

下面的单例的例子提供了RequestQueue和ImageLoader的功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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。

1
2
3
4
5
6
7
8
// 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并且在响应中接受到字符串,对于例子可以看设置RequeseQueue

JsonObjectRequest和JsonArrayRequest(二者均为JsonRequest的子类):指定特定的URL并且会获取到JSON或者JSON数据组。

如果你期望的响应是这些类型的一种,那不需要设置自定义的Request。这节将描述怎么去使用这些标准的请求类型,对于如何自定义Request请看下一节。

JSON Request

Volley为JSON请求提供了下面的类:

  • JsonArrayRequest :一个Request给定URL,将会接受到JSONArray的响应body
  • JsonObjectRequest:一个Request给定的URL,接受到JSONObject响应body,允许JSONObject作为请求body的一部分。

上述的两个类均为JSONObject的子类。你使用下面的模板来使用其他的JSON类。例如,下面的代码片段描述了获取JSON并展示到UI上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
TextView mTxtDisplay;
ImageView mImageView;
mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
String url = "http://my-json-feed";

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

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

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO Auto-generated method stub

    }
});

// Access the RequestQueue through yozur singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);

对于实现自定义的Request依赖于Gson, 可以看下节。

实现自定义的Request

本节将描述怎么实现自定义的Request类型。

自定义Request

在工具类中需要的Request都可以拿来即用;如果你的响应的数据类型是String,Image,或者JSON,那不必实现自定义的Request。

对于实现自定义的Request你仅仅需要实现下面的步骤:

  • 继承Request类,为参数化表示期望解析的响应的类型。因此如果希望解析的响应是一个字符串就需要继承Request。可以查看类StringRequest和ImageRequest作为继承Request的例子。
  • 实现parseNetworkResponse() 和 deliverResponse()抽象方法,下面有更多细节。

parseNetworkResponse方法

Response封装了给定类型解析后的响应,(比如String,image或者JSON)。下面是一个parseNetworkResponse() 的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@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
...
}

注意:

  • NetworkResponse作为parseNetworkResponse()的参数,NetworkResponse包含响应作为byte[]类型,HTTP 状态码,和响应头。
  • 实现必须返回一个Response,它包含了实现的响应对象的类型和缓存原始数据或者在解析失败的情况下的错误。

如果你的协议包含非标准的语义,你可以构建自己的Cache.Entry,但是大多数请求都是这样的:

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

Volley调用parseNetworkResponse()方法是在工作线程中。这个确保了耗时的操作不会阻塞主线程,比如解析JPEG成Bitmap。

deliverResponse方法

Volley返回parseNetworkResponse()的对象传递到主线程。大多数的请求会在这里调用回调接口,例如:

1
2
3
protected void deliverResponse(T response) {
        listener.onResponse(response);
}

例如GsonRequest

Gson是一个使用反射技术把java对象转化成JSON,或者JSON转化为Java对象的库。你可以定义java对象与它对应的JSON的key有相同的名字。传递Gson对象,并且Gson将会填充对象的属性。下面是完整的Volley的Request实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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));
        }
    }
}

Volley提供了方便的类JsonArrayReques和JsonArrayObject。可以看标准请求一节来查看更多信息。

updatedupdated2020-06-132020-06-13