毕业工作这么久了,一直被公司的代码束缚着,每人负责自己的模块,每天不是修改新需求就是改bug,没事的时候也看看其他模块的代码,不过往往看着看着就不想往下看了,反正想不是自己负责的模块,出了什么bug也和自己没关系,哎,这种想法真是大错特错了,别人铺好的路现成给你学习,都没把握住,也许就应了一句话:没有压力就没有动力。有时候就应该给自己点压力,这样才会让自己成长,让自己学到更多的东西。就拿我自己来说,之前我没事的时候看其他模块的代码时,一般比较喜欢看到有界面的代码模块,对这块比较感兴趣,每次只要碰到网络通信之类的操作就立马跳过,感觉太高深了,完全看不懂,也不是完全看不懂,压根就是一点没去看。现在项目代码打算优化,让调查一下Volley,我去,完全懒得看网络通信的人去调查这个,立马着急起来了,赶紧查找资料啊。
什么是Volley
在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在Google I/O 2013上,Volley发布了。Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。
这是Volley名称的由来: a burst or emission of many things or a large amount at once
1.Volley引入的背景
在以前,我们可能面临如下很多麻烦的问题。
比如以前从网上下载图片的步骤可能是这样的流程:
- 在ListAdapter#getView()里开始图像的读取。
- 通过AsyncTask等机制使用HttpURLConnection从服务器去的图片资源
- 在AsyncTask#onPostExecute()里设置相应ImageView的属性。
再比如,屏幕旋转的时候,有时候会导致再次从网络取得数据。为了避免这种不必要的网络访问,我们可能需要自己写很多针对各种情况的处理,比如cache什么的。
再有,比如ListView的时候,我们滚动过快,可能导致有些网络请求返回的时候,早已经滚过了当时的位置,根本没必要显示在list里了,虽然我们可以通过ViewHolder来保持url等来实现防止两次取得,但是那些已经没有必须要的数据,还是会浪费系统的各种资源。
2. Volley提供的功能简单来说,它提供了如下的便利功能:
- JSON,图像等的异步下载;
- 网络请求的排序(scheduling)
- 网络请求的优先级处理
- 缓存
- 多级别取消请求
- 和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)
Android访问网络,使用HttpURLConnection还是HttpClient?
大多数的Android应用程序都会使用HTTP协议来发送和接收网络数据,而Android中主要提供了两种方式来进行HTTP操作,HttpURLConnection和HttpClient。这两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、以及连接池等功能。
HttpClientDefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具体的实现类,它们都拥有众多的API,而且实现比较稳定,bug数量也很少。
但同时也由于HttpClient的API数量过多,使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以目前Android团队在提升和优化HttpClient方面的工作态度并不积极。
HttpURLConnectionHttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。虽然HttpURLConnection的API提供的比较简单,但是同时这也使得我们可以更加容易地去使用和扩展它。
不过在Android 2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能
哪一种才是最好的?
在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。
而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在以后的工作当中我们也会将更多的时间放在优化HttpURLConnection上面。
加载网络图片,Volley提供了三种方式:ImageRequest、ImageLoader和NetworkImageView。
下面简单介绍一下三种方式的用法:
ImageRequest的用法:
基本上分为三步:1. 创建一个RequestQueue对象。
RequestQueue mQueue = Volley.newRequestQueue(context);
2. 创建一个
ImageRequest对象。
ImageRequest request=new ImageRequest(StringUtil.preUrl(imageUrl), new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}, 0, 0, Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError arg0) {
imageView.setImageResource(R.drawable.ic_empty);
}
});
ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址,此处做了一些处理,就是在给的url如果不是以http开头的,就添加上http
。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。
3. 将ImageRequest对象添加到RequestQueue里面。
mQueue.add(imageRequest);
ImageLoader的用法:
分为以下四步:
2. 创建一个ImageLoader对象。
ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
@Override
public Bitmap getBitmap(String url) {
return null;
}
});
ImageLoader的构造函数接收两个参数,第一个参数就是RequestQueue对象,第二个参数是一个ImageCache对象。
3. 获取一个ImageListener对象。
ImageListener listener=ImageLoader.getImageListener(imageView, R.drawable.car, R.drawable.ic_empty);
通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对象,getImageListener()方法接收三个参数,第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片。
4. 调用ImageLoader的get()方法加载网络上的图片。
imageLoader.get(StringUtil.preUrl(imageUrl),listener);
get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:
imageLoader.get(StringUtil.preUrl(imageUrl),listener,100,100);
到这里已经差不多掌握了ImageLoader的用法,但是刚才介绍的ImageLoader的优点却还没有使用到。为什么呢?因为这里创建的ImageCache对象是一个空的实现,完全没能起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想要写一个性能非常好的ImageCache,最好就要借助Android提供的LruCache功能了。
public class LruImageCache implements ImageCache{
private LruCache<String, Bitmap> lruCache;
public LruImageCache() {
int maxSize = 10 * 1024 * 1024;
lruCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return lruCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
lruCache.put(url, bitmap);
}
}
这里我们将缓存图片的大小设置为10M。接着修改创建ImageLoader实例的代码,第二个参数传入LruImageCache
的实例,如下所示
ImageLoader imageLoader = new ImageLoader(mQueue, new LruImageCache());
NetworkImageView的用法:
NetworkImageView是一个自定义控制,它是继承自ImageView的,具备ImageView控件的所有功能,并且在原生的基础之上加入了加载网络图片的功能。估计我们更趋向去控件的使用了,同时NetworkImageView控件的用法要比前两种方式更加简单,大致可以分为以下五步:1. 创建一个RequestQueue对象。
2. 创建一个ImageLoader对象。
3. 在布局文件中添加一个NetworkImageView控件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/iv_car"
android:layout_width="match_parent"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
</LinearLayout>
4. 在代码中获取该控件的实例。
networkImageView = (NetworkImageView) findViewById(R.id.network_iv_car);
5. 设置要加载的图片地址。
networkImageView.setDefaultImageResId(R.drawable.ic_empty);
networkImageView.setErrorImageResId(R.drawable.ic_empty);
networkImageView.setImageUrl(StringUtil.preUrl(imageUrl), imageLoader);
调用它的setDefaultImageResId()方法、setErrorImageResId()方法和setImageUrl()方法来分别设置加载中显示的图片,加载失败时显示的图片,以及目标图片的URL地址。其中,setImageUrl()方法接收两个参数,第一个参数用于指定图片的URL地址,第二个参数则是前面创建好的ImageLoader对象。
稍后整理一下会把demo源码奉上。
OK,三种方式均已简单介绍完毕,刚看不行,还得需要自己动手去操作一遍,哪怕按着模板手动敲一遍也是有效果的。下面就来总结一下三种方式各自的优点吧。
ImageRequest可以加载网络图片,可以对图片进行缓存。但ImageLoader也可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。
NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心,NetworkImageView会始终呈现给我们一张大小刚刚好的网络图片,不会多占用任何一点内存,这也是NetworkImageView最简单好用的一点。