解析Android之Volley框架(1)

本文翻译自android官方文档,结合自己测试,整理如下。

使用Volley进行网络通信

Volley是Android官方提出的HTTP网络通信框架,它能够使得Android程序网络传输更容易而且更快。

Volley有以下优点:

  • 自动调度网络请求。
  • 多并发网络连接。
  • 标准的HTTP网络请求缓存。
  • 支持请求优先级。
  • 取消请求API。我们可以取消单次请求,也可以取消请求块或一个范围。
  • 容易定制。
  • 很好地排序,能够使得网络异步数据正确地填充UI。
  • 调试与追踪工具。

Volley不适合大数据量传输(包括上传和下载)或者数据流操作,这是因为Volley在内存中处理所有的请求。对于大数据量传输可以考虑使用其它工具,例如DownloadManager。

在使用Volley前要导入jar包,具体办法自行解决,哈哈,,,,。

发送一个简单的请求

我们通过创建一个RequestQueue和传递给Volley一个Request对象使用Volley。RequestQueue管理工作线程,这些线程用于网络操作/读写缓存/解析请求等。Requests处理原始请求,Volley把解析结果传递给主线程。

本节中将介绍如何使用Volley.newRequestQueue()发送请求,添加请求,以及取消请求。

添加INTERNET许可

为了使用Volley,我们必须在manifest文件中添加许可android.permission.INTERNET,没有该请求的话我们无法使用网络。

使用newRequestQueue()

可以通过Volley.newRequestQueue()获取一个RequestQueue对象,然后添加一个Request到该请求队列中,这里我们使用StringRequest。具体步骤如下:

  1. 获取请求队列RequestQueue对象;
  2. 创建StringRequest对象;
  3. 将请求对象添加到请求队列中。

代码如下:


public void sendRequestWithVolley(View view){
        // 1. 创建请求队列
        RequestQueue queue = Volley.newRequestQueue(this);
        // 2. 创建请求对象
        // 2.1 创建url
        String url = "http://blog.csdn.net/wangyongge85";
        // 2.2 创建请求对象,这里使用StringRequest
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.d(TAG,response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d(TAG,error.getMessage());
                    }
                });
        // 3. 将请求对象加入到请求队列中
        queue.add(stringRequest);
    }

上述代码中,我们创建了一个StringRequest对象,StringRequest的构造器接收四个参数,分别为:

  • HTTP请求方法;
  • 目标服务器的URL;
  • 服务器响应成功回调方法;
  • 服务器响应失败回调方法。

由于Response.Listener和Response.ErrorListener都是函数式接口(只有一个抽象方法),因此可以使用Lambda表达式取代。例如:Response.Listener<String>可以替换为:(String response)->{Log.d(TAG,s);}

通过上述三步我们就完成了HTTP请求。

这里我们使用了Request.Method.GET方法,若要向服务器发送数据,则使用Request.Method.POST。但是StringRequest中并没有直接提供设置传递数据参数的构造器,我们只能通过重写父类的getParams()方法来传递提交的数据,如下:


 StringRequest stringRequest2 = new StringRequest(Request.Method.POST, url,
               (String s)->{Log.d(TAG,s);},
                (VolleyError volleyError)-> {Log.d(TAG,volleyError.getMessage());
        })
        {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> map = super.getParams();
                map.put("param","value");
                return map;
            }
        };

Volley总是在main线程中处理响应。将接收的数据在主线程中填充UI是很方便的,可以直接使用响应结果更新UI。

发送请求

为了发送一次请求,我们可以简单的通过RequestQueue的add()方法,例如上面代码中。一旦我们添加过请求,该请求就会移动到请求队列中,并处理,然后返回原始响应数据并传输到Response.Listener或Response.ErrorListener处理。

当我们调用add()方法时,Volley将运行一个高速缓存处理线程和一个网络调度线程池。若请求在缓存中的话,缓存响应就将在缓存线程中解析,并将解析响应传递给main线程;若请求不在缓存中的话,该请求将加入到网络请求队列中。第一个可用的网络线程从队列中取请求,执行HTTP事务,在子线程中解析响应,将响应写入缓存中,然后将解析的响应返回给主线程中。

注意到耗时的操作如阻塞I/O和解析解码等都要在子线程中执行。我们可以在任何线程中添加请求,但是处理响应结果总是在main线程中。

下图描述了请求全过程:
这里写图片描述

取消一次请求

为了取消一次请求,可以调用Request对象的cancel()方法。一旦被取消,Volley能够保证我们的响应请求绝对不会被调用。这意味着在实际中我们能够在activity的onStop()中取消所有的延迟请求,我们不需要通过检查是否getActivity()==null,是否onSaveInstanceState()已经被调用,或者其它内容回收响应处理者。

为了能够在合适的时间取消请求,我们可以在每次请求时设置一个标签,通过该标签我们能够取消相应的请求。例如下面的代码:


public static final String TAG = "MyTag";
StringRequest stringRequest;
RequestQueue mRequestQueue;

// 设置请求标签
stringRequest.setTag(TAG);

// 将请求添加到请求队列中
mRequestQueue.add(stringRequest);

然后我们可以在Activity中的onStop()方法取消所有标签为TAG的请求:


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

设置RequestQueue请求队列

上一节讲解了如何使用Volley.newRequestQueue()设置RequestQueue对象,并充分利用Volley默认的请求方法。这一节中我们将通过下面的方法创建RequestQueue队列,方便我们实现自定义的请求方法。下面我们将使用单例模式创建一个RequestQueue对象,这种方式使得该RequestQueue对象的生存周期和我们程序的生存周期一样。

设置网络和缓存

RequestQueue需要完成下列两件事:执行传输请求的网络Network和处理缓存的空间Cache。在Volley中的toolbox包中有默认的实现:DiskBasedCache和BasicNetwork。DiskBasedCache提供一个文件缓存,并且带有内存索引;BasicNetwork提供基于HttpURLConnection或者AndroidHttpClient的网络传输请求,是Volley默认的网络请求实现。

- 在低于API 9 (Gingerbread)中使用的是AndroidHttpClient,在API 9之前,HttpURLConnection是不可靠的。
- 在高于API 9之后(包括API 9),使用HttpURLConnection。

为了应用程序能够在Android所有的版本中使用Volley,我们可以检查android设备的版本,选择合适的HTTP客户端来创建BasicNetwork对象,例如:


HttpStack stack;
if (stack == null) {
    if (Build.VERSION.SDK_INT >= 9) {
        stack = new HurlStack();
    } else {
        stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
    }
}
Network network = new BasicNetwork(stack);

在上面的例子中,通过HttpStack对象创建了一个BasicNetwork对象。而对于HttpStack对象来说在API 9之前是AndroidHttpClient(实现了HttpStack接口),在API 9之后是HurlStack对象(实现了HttpStack接口,并且内部使用HttpURLConnection实现网络请求)。

下面的代码描述了通过DiskBasedCache对象和BasicNetwork对象创建RequestQueue对象:


RequestQueue mRequestQueue;

// 实例化缓存
Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap

// 使用HttpURLConnection作为Http客户端连接方式
Network network = new BasicNetwork(new HurlStack());

// 使用cache和network实例化消息队列
mRequestQueue = new RequestQueue(cache, network);

// 启动消息队列
mRequestQueue.start();

String url ="http://blog.csdn.net/wangyongge85";

// 实例化请求对象
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
    }
});

// 将消息添加到消息队列中
mRequestQueue.add(stringRequest);
...

若我们仅仅只请求一次,不想使用线程池,我们可以在任何需要它的地方创建RequestQueue类。但是更常用的做法是创建一个全局RequestQueue单例对象。下面详细描述这种方法。

使用单例模式

若我们的程序一直使用网络的话,最高效的做法是创建一个全局的RequestQueue单例对象。实现单例的方法有很多种,本节中推荐使用一个单例封装RequestQueue对象和其他Volley方法。不建议在Application子类中的onCreate()中创建RequestQueue对象。

在实例化RequestQueue时,必须使用Application作为参数,而不能使用Activity作为参数,这种方式保证RequestQueue在整个程序运行期间都存在,而不是当activity重建时RequestQueue也重建(例如用户旋转手机屏幕)。并且能够防止内存泄漏。

如下单例模式封装了RequestQueue和ImageLoader:


private static MySingleton mInstance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;

private MySingleton(Context context) {
    mCtx = context;
    // 实例化消息队列
    mRequestQueue = getRequestQueue();
    // 实例化ImageLoader,这个在下面部分有详细讲解
    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()能够防止内存泄漏
        mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
    }
    return mRequestQueue;
}

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

public ImageLoader getImageLoader() {
    return mImageLoader;
}

下面代码给出了如何使用该单例模式:


// 获取请求队列
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();
...

// 将请求对象发送到请求队列中
MySingleton.getInstance(this).addToRequestQueue(stringRequest);

制定标准请求

本节中将介绍如何使用Volley默认实现的各种请求:

  • StringRequest
    须指定URL,接收未处理的字符串响应。
  • ImageRequest
    须指定URL,接收图片响应。
  • JsonObjectRequestJsonArrayRequest
    都是JsonRequest的子类。指定URL,各自接收Json对象或Json数组响应。

若我们想接收以上数据响应的话,可以不用我们自定义请求,直接使用以上类就可以。

本章节将描述如何使用这些标准的请求方式。

请求图片响应

Volley提供了下列的类来请求图片。这些类之间存在着相互关系,提供不同级别的支持处理图像:

  • ImageRequest从给定的URL中获得图片,并且通过解析的bitmap回调。它也能够提供一些方便的方法,例如指定一个大小来重新设置图片等。主要优点在于Volley线程处理机制能够保证耗时的图片操作(解码,重新绘画)自动在子线程中执行。
  • ImageLoader
    帮助类,能够加载和缓存从远程URL获取的图片。ImageLoader是一个管理大量的ImageRequest的类。
  • MetworkImageView
    建立在ImageLoader上,能够高效地在合适的地方显示通过网络获取的图片,因此可以替换ImageView。若该控件不再可用时,它也能够取消延迟请求。

使用ImageRequest

下面是一个简单的使用ImageRequest的例子。它能够检索指定URL中的图片,然后显示在我们的程序中。注意下面使用到的RequestQueue是通过单例模式创建的:


ImageView mImageView;
String url = "http://i.imgur.com/7spzG.png";
mImageView = (ImageView) findViewById(R.id.myImage);
...

// 检索URL指定位置的图片,然后显示在UI上。
ImageRequest request = new ImageRequest(url,
    new Response.Listener() {
        @Override
        public void onResponse(Bitmap bitmap) {
            mImageView.setImageBitmap(bitmap);
        }
    }, 0, 0, null,
    new Response.ErrorListener() {
        public void onErrorResponse(VolleyError error) {
            mImageView.setImageResource(R.drawable.image_load_error);
        }
    });
// 通过单例获取请求队列,然后将请求加入到请求队列中
MySingleton.getInstance(this).addToRequestQueue(request);

ImageRequest的构造器接收六个参数,分别为:

  1. 图片的URL;
  2. 请求成功的回调;
  3. 指定允许图片最大的宽度,大于指定值则会压缩,0的话表示按原图大小显示;
  4. 指定允许图片最大的高度,大于指定值则会压缩,0的话表示按原图大小显示;
  5. 指定图片的颜色属性;
  6. 请求失败的回调。

使用ImageLoader和NetworkImageView

我们可以使用ImageLoader和NetworkImageView来高效地显示多个图片,例如在ListView显示。在我们的布局文件中使用NetworkImageView(和ImageView差不多)。

使用ImageLoader

使用ImageLoader将图片显示在ImageView中,例如:


ImageView mImageView;
// 加载指定图片
private static final String IMAGE_URL =
    "http://i.imgur.com/7spzG.png";
...
mImageView = (ImageView) findViewById(R.id.regularImageView);

// 实例化ImageLoader,这个在下面部分有详细讲解
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);
        }
    });
mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
         R.drawable.def_image, R.drawable.err_image));

ImageLoader构造器参数为:

  • 请求队列对象;
  • ImageCache对象,用于缓存图片。

创建ImageLoader对象之后,调用其get()方法加载图片,get()方法接收两个参数:

  • 图片的URL地址;
  • ImageListener对象,用于监听图片加载情况,可以通过ImageLoader.getImageListener()获得,该方法接收三个参数:

    • 显示图片的ImageView;
    • 加载图片时显示的图片;
    • 加载图片失败后显示的图片。

使用NetworkImageView

若要使用NetworkImageView的话,首先在布局文件中设置NetworkImageView,代码如下:


<com.android.volley.toolbox.NetworkImageView
        android:id="@+id/networkImageView"
        android:layout_width="150dp"
        android:layout_height="170dp"
        android:layout_centerHorizontal="true" />

然后使用ImageLoader将图片显示,代码如下:


ImageLoader mImageLoader;
NetworkImageView mNetworkImageView;
private static final String IMAGE_URL = "http://i.imgur.com/7spzG.png";
...

// 获取NetworkImageView
mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);
// 通过单例模式获取ImageLoader
mImageLoader = MySingleton.getInstance(this).getImageLoader();
// 默认显示图片
networkImageView.setDefaultImageResId(R.drawable.default_image);
// 加载失败后显示的图片
networkImageView.setErrorImageResId(R.drawable.failed_image);
// 设置将要加载的图片,并且指定ImageLoader处理请求
mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);

使用单例模式能够允许bitmap缓存超出activity的生命周期。若我们在activity中创建了一个ImageLoader,当用户每次旋转设备时,activity都需要重新创建,ImageLoader也会被重新创建。使用单例的话就会避免这种反复重建。

LRU缓存例子

Volley通过DiskBasedCache类提供了一个标准的缓存实现。该类直接将文件缓存在指定的存储器目录上。但是为了使用ImageLoader,我们应该提供一个自定义的内存LRU bitmap缓存,该缓存实现了ImageLoader.ImageCache接口。同样该缓存也可以设置成单例模式。

简单代码如下:


import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache<String, Bitmap>
        implements ImageCache {

    public LruBitmapCache(int maxSize) {
        super(maxSize);
    }

    public LruBitmapCache(Context ctx) {
        this(getCacheSize(ctx));
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

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

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

    // Returns a cache size equal to approximately three screens worth of images.
    public static int getCacheSize(Context ctx) {
        final DisplayMetrics displayMetrics = ctx.getResources().
                getDisplayMetrics();
        final int screenWidth = displayMetrics.widthPixels;
        final int screenHeight = displayMetrics.heightPixels;
        // 4 bytes per pixel
        final int screenBytes = screenWidth * screenHeight * 4;

        return screenBytes * 3;
    }
}

下面是通过cache实例化ImageLoader的例子:


RequestQueue mRequestQueue;
ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache(
            LruBitmapCache.getCacheSize()));

请求JSON

Volley提供了下列方法处理JSON请求:

  • JsonArrayRequest
    从给定的URL中检索JSONArray响应体。
  • JsonObjectRequest
    从给定的URL中检索JSON对象响应体。允许添加额外的JSONObject作为请求体的一部分。

这两个类都是基于基类JsonRequest的实现。我们可以根据下面相同的基本模式使用他们:


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() {
            @Override
            public void onResponse(JSONObject response) {
                mTxtDisplay.setText("Response: " + response.toString());
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
            }
        });

MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);

其中第三个参数为请求体,这里为null。

实现自定义请求

本节中将描述如何实现自定义的请求。

编写自定义请求

大部分请求都可以直接使用Volley提供的API,如:字符串,图片或者JSON。

如我们想实现自定义的请求如:GSON和XML。可以通过下列方法:

  1. 继承Request<T>类,其中T是解析响应的类型。
  2. 实现抽象方法parseNetworkResponse()和deliverResponse()`。

下面详细讲解具体实现方法。

实现parseNetworkResponse()

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));
    }
    // handle errors
...
}

注意

  • parseNetworkResponse()接收一个NetworkResponse参数,包括响应加载字节数组,HTTP,响应头。
  • 必须返回Response,该对象包含了我们定义的响应对象和数据缓存或者错误。

若我们的协议不包括标准的缓存语义,我们应该建立Cache.Entry,但是大多数请求都可以使用下面的方法:


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

Volley在子线程中调用parseNetworkPesponse()。这样能够确保耗时操作(解析JPEG成Bitmap等操作)不会阻塞UI线程。

deliverResponse()

Volley能够在主线程中处理parseNetworkResponse()返回的结果。大多数采用下述方法:


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

parseNetworkResponse()方法中,先将服务器响应的数据解析,然后在deliverResponse()方法中进行回调。

下面以Gson请求为例。

自定义Gson请求

下面我们实现自定义Gson请求,代码如下:


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;

    /**
     * GET请求方式,并且返回JSON解析对象
     */
    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
    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));
        }
    }
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers != null ? headers : super.getHeaders();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值