Volley官方指南

1. Volley概述

Volley是一个Http网络库,可以让你的Android应用能够更简单、更快速的进行网络请求。

源码在GitHub上托管

Volley

Volley有如下有点:

  • 自动发起网络请求
  • 并发网络连接请求
  • 使用Http标准的缓存一致性透明的磁盘和内存缓存
  • 支持请求优先级
  • 支持请求取消
  • 支持定制,例如重试和回退
  • 强大的异步加载
  • Debugging和Tracing工具

Volley执行RPC类型的操作用于填充UI,例如获取一个分页加载的结构化数据。可以很容易与任何协议和输入出为strings、Images及JSON。通过内建的支持你需要的功能,Volley将你从写样板代码中解放出来,让你集中精力在APP的逻辑上来。

Volley不适合大下载和流的操作,由于Volley将所有的响应在内存中解析。对于下载操作,考虑使用DownloadManager

Volley核心库包含主要的请求分发管道以及一系列公共的应用工具,在Volley的“toolbox”。在AS中通过下面的代码将Volley加入到你的App中

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

你也可以将Volley作为你应用的一个库,将源码导入你的工程中。

2. 发送一个简单的请求

在应用层,你可以使用Volley创建一个RequestQueue,将Request对象传递给它。RequestQueue管理网络操作的工作线程,这些工作线程主要工作为读取网络数据并写入缓存,然后解析。Requests解析响应,Volley将解析后的响应回调给主线程中分发。

2.1 添加INTERNET permission

为了使用Volley,你必需添加android.permission.INTERNET权限到你的app的Manifest中。

2.2 使用newRequestQueue

Volley提供了Volley.newRequestQueue方法用于设置RequestQueue,使用默认的值,开始一个queue。例如:

KOTLIN

val textView = findViewById<TextView>(R.id.text)
// ...

// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(this)
val url = "http://www.google.com"

// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
        Response.Listener<String> { response ->
            // Display the first 500 characters of the response string.
            textView.text = "Response is: ${response.substring(0, 500)}"
        },
        Response.ErrorListener { textView.text = "That didn't work!" })

// Add the request to the RequestQueue.
queue.add(stringRequest)

JAVA

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,你可以自由的在响应的handler中修改UI控制,但是这个库中定义的一些重要的API是很关键的,尤其是取消请求的API。

请看设置一个RequestQueue关于如何创建一个RequestQueue,替代Volley.newRequestQueue

2.3 发送一个请求

为了发送一个请求,通常很简单地构造一个Request,然后通过add()方法把它加入到RequestQueue中,如下所示。一旦你将一个Request加入到RequestQueue中,该请求将经历一下过程:通过管道、获取服务、解析响应以及响应传递给上层。

当你调用add()方法,Volley将运行一个Cache处理的线程及一个网络请求分发的线程。当你将一个Request加入到队列中,它将被加入到Cache线程中和被触发:如果请求请求在Cache中就被处理了,Cache的响应将被在Cache线程中解析,被解析的响应将被传递给主线程。如果请求不能在Cache中处理,它将被加入网络请求队列中。第一个可用的网络线程从Queue中取出Request,通过http传输,在工作线程中解析响应,将响应加入response缓存中,并将解析的响应传递给UI线程。

注意:耗时操作,例如I/O及解析、解码在工作线程中完成。你可以在任何线程中添加Request,但是响应总是被传递到主线程。

一个请求的声明周期

2.4 取消一个请求

为了取消一个请求,调用cancel()在你的Request对象上。一旦取消,Volley将会确保你的响应将不会被处理。这意味着你可以取消所有请求在Activity调用onStop方法时,不需要在Handles中做非空判定getActivity()==null.

为了充分利用这种行为的优点,你必须为每个请求制定一个跟踪机制,以确保在合适的时机可以取消它们。有一种简单的方法:你可以关联一个tag对象到每个请求,你可以使用这个tag获取请求实例,并取消。例如,你可以用Activity标记所有的请求,在Activity执行onStop()时,调用requestQueue.cancelAll(this) 。类似的,你可以标记所有的thumbnail图片请求在一个ViewPager 页中,在却换到,取消它们,确保新的tab不会持有另一个tab的Request。

下面的示例展示如何使用一个string tag来标记一个Request

  1. 定义你的tag,并将他加入到你的Requests中
    KOTLIN
val TAG = "MyTag"
val stringRequest: StringRequest // Assume this exists.
val requestQueue: RequestQueue? // Assume this exists.

// Set the tag on the request.
stringRequest.tag = TAG

// Add the request to the RequestQueue.
requestQueue?.add(stringRequest)

JAVA

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

2, 在你的activity的onStop()方法中,取消持有这个tag的所有的请求

KOTLIN

protected fun onStop() {
    super.onStop()
    requestQueue?.cancelAll(TAG)
}

JAVA

@Override
protected void onStop () {
    super.onStop();
    if (mRequestQueue != null) {
        mRequestQueue.cancelAll(TAG);
    }
}

3. 设置你的RequestQueue

上一节课程中展示了如何使用Volley.newRequestQueue方法设置一个RequestQueue,利用了Volley默认的行为的优点。这篇课程教你如何创建一个RequestQueue,让你可以提供你自定义的行为。

这节课也推荐了通过Singleton方式创建一个RequestQueue,使得RequestQueue和App生命周期一致。

3.1 设置网络并缓存

一个RequestQueue需要两件事情完成它的工作:网络用于传递请求,Cache用于处理缓存。在Volley toolbox中,这些都有默认实现。BasicNetwork必须通过一个HTTP Client被初始化。典型的如HttpUrlConnection。
下面的例子教你设置一个RequestQueue:

KOLTION

// Instantiate the cache
val cache = DiskBasedCache(cacheDir, 1024 * 1024) // 1MB cap

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

// Instantiate the RequestQueue with the cache and network. Start the queue.
val requestQueue = RequestQueue(cache, network).apply {
    start()
}

val url = "http://www.example.com"

// Formulate the request and handle the response.
val stringRequest = StringRequest(Request.Method.GET, url,
         Response.Listener<String> { response ->
            // Do something with the response
        },
        Response.ErrorListener { error ->
            // Handle error
            textView.text = "ERROR: %s".format(error.toString())
        })

// Add the request to the RequestQueue.
requestQueue.add(stringRequest)

// ...

JAVA

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

// ...

如果你只需要一次性请求,那么Sending a Simple Request就足够了。但是大多数情况下,你需要创建一个单例模式的RequestQueue,使得它和app生命周期保持一致。

3.2 使用单例模式

如果你的App需要持续使用网络,使用单例模式的RequestQueue更加适合。

一个关键点是RequestQueue必须使用Application 的context进行初始化,而不是一个Activity的context。这个确保了RequestQueue的生命周期和App保持一致,不用跟随Activity每次重建。

下面的例子使用单例模式提供一个RequestQueue和ImageLoader功能:

KOTLIN

class MySingleton constructor(context: Context) {
    companion object {
        @Volatile
        private var INSTANCE: MySingleton? = null
        fun getInstance(context: Context) =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: MySingleton(context).also {
                    INSTANCE = it
                }
            }
    }
    val imageLoader: ImageLoader by lazy {
        ImageLoader(requestQueue,
                object : ImageLoader.ImageCache {
                    private val cache = LruCache<String, Bitmap>(20)
                    override fun getBitmap(url: String): Bitmap {
                        return cache.get(url)
                    }
                    override fun putBitmap(url: String, bitmap: Bitmap) {
                        cache.put(url, bitmap)
                    }
                })
    }
    val requestQueue: RequestQueue by lazy {
        // applicationContext is key, it keeps you from leaking the
        // Activity or BroadcastReceiver if someone passes one in.
        Volley.newRequestQueue(context.applicationContext)
    }
    fun <T> addToRequestQueue(req: Request<T>) {
        requestQueue.add(req)
    }
}

JAVA

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操作代码:

KOTLIN

// Get a RequestQueue
val queue = MySingleton.getInstance(this.applicationContext).requestQueue

// ...

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

JAVA

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

4. 一个标准请求

Volley支持下面类型的请求:

  • StringRequest。接收一个URL,并且获取一个字符串响应。
  • JsonObjectRequest 和JsonArrayRequest(JsonRequest的子类)。接收一个URL获取一个JSON对象或者数组的响应。

如果你希望获取以上类型的响应,有可能不需要实现一个自定义的request.这节课教你如何使用标准请求类型。

4.1 请求JSON

Volley提供以下类用于JSON请求:

  • JsonArrayRequest-一个获取JSONArray的响应体的请求。
  • JsonObjectRequest- 一个获取JSONObject响应的请求,允许传递一个JSONObject作为请求体的一部分。

两个类都共同继承自JsonRequest。使用方式如下:

KOTLIN

val url = "http://my-json-feed"

val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null,
        Response.Listener { response ->
            textView.text = "Response: %s".format(response.toString())
        },
        Response.ErrorListener { error ->
            // TODO: Handle error
        }
)

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

JAVA

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

下面举例说明实现一个自定义的JSON Request,基于Gson

5. 实现一个自定义请求

5.1 写一个自定义请求

大部分请求已经在toolbox实现,如果你的响应是一个string,image或者JSON,可能你不需要实现一个自定义的Request。

为了满足可能需要自定义Request的情形,你需要照下面步骤执行:

  • 继承Request 类, 代表解析的响应类型。所以如果你的解析类型是string,创建一个自定义请求类型通过继承Request. 请看toolbox 类中StringRequest和ImageRequest类,它们都继承子Request.
  • 实现一个抽象方法parseNetworkResponse()和deliverResponse()方法。下面有详细描述:

5.2 parseNetworkResponse

下面是parseNetworkResponse()简单实现

KOTLIN

override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
    return try {
        val json = String(
                response?.data ?: ByteArray(0),
                Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
        Response.success(
                gson.fromJson(json, clazz),
                HttpHeaderParser.parseCacheHeaders(response))
    }
    // handle errors
// ...
}

JAVA

@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的所有参数,包含了响应的payload、Http状态码以及响应的Headers。
  • 你的实现不需返回一个Response, 包含了你响应的对象和缓存的metadata或者一个错误,例如解析失败的情形。

如果你的协议不是标准的缓存字段,你可以创建一个Cache.Entry,代码示例如下:

KOTLIN

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

JAVA

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

Volley在工作线程中调用parseNetworkResponse().确保耗时的解析操作,例如:解码JPEG到Bitmap中,不会阻塞IO

5.3 deliverResponse

Volley在主线程中完成Object的回调,这个Object通过调用parseNetworkResponse()返回。大部分的请求调用这个接口例如:

KOTLIN

override fun deliverResponse(response: T) = listener.onResponse(response)

JAVA

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

5.4 例子:GsonRequest

Gson是一个常用的java Json转换工具。下面是Volley请求使用Gson的完整实现:

KOTLIN

/**
 * 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
 */
class GsonRequest<T>(
        url: String,
        private val clazz: Class<T>,
        private val headers: MutableMap<String, String>?,
        private val listener: Response.Listener<T>,
        errorListener: Response.ErrorListener
) : Request<T>(Method.GET, url, errorListener) {
    private val gson = Gson()


    override fun getHeaders(): MutableMap<String, String> = headers ?: super.getHeaders()

    override fun deliverResponse(response: T) = listener.onResponse(response)

    override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
        return try {
            val json = String(
                    response?.data ?: ByteArray(0),
                    Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
            Response.success(
                    gson.fromJson(json, clazz),
                    HttpHeaderParser.parseCacheHeaders(response))
        } catch (e: UnsupportedEncodingException) {
            Response.error(ParseError(e))
        } catch (e: JsonSyntaxException) {
            Response.error(ParseError(e))
        }
    }
}

JAVA

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));
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值