去年Google在IO大会上推出了vollery框架,适用于通信量不大但是通信频繁的情况,对普通的网络访问,json解析以及图片的加载都提供了很好的支持。
在真正的网络访问之前,首先需要做一些准备工作
//创建一个请求调度队列
RequestQueue rq = Volley.newRequestQueue(this);
RequestQueue的创建
/**
* 创建工作池并start
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
/*
* 在Android 2.3及以上版本,使用的是HttpURLConnection
* 在Android 2.2及以下版本,使用的是HttpClient
* reason:
* 2.2之前HttpClient的bug较少,而HttpURLConnection的缺陷较多
* 比如对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了
* 2.2之后API简单,体积较小,因而非常适用于Android项目。
* 压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。
* 对于新的应用程序应该更加偏向于使用HttpURLConnection
*/
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
if(null == network)
network = new BasicNetwork(stack);
if(null == queue)
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
普通的网络访问
//创建一个请求调度队列
RequestQueue rq = Volley.newRequestQueue(this);
StringRequest request = new StringRequest(Method.GET, url, new Response.Listener<String>() {
/**
* 访问成功返回数据
* @param response
*/
@Override
public void onResponse(String response) {
Log.i("response", response);
}
}, new Response.ErrorListener() {
/**
* 网络访问失败处理及原因
*/
@Override
public void onErrorResponse(VolleyError error) {
if(error instanceof ServerError){//服务器错误
Log.i("error", error.toString());
}else if(error instanceof NoConnectionError){//无连接
Log.i("error", error.toString());
}else if(error instanceof TimeoutError){//超时
Log.i("error", error.toString());
}
}
});
rq.add(request);
Json数据的访问
RequestQueue rq = Volley.newRequestQueue(this);
JsonObjectRequest request = new JsonObjectRequest(Method.GET, url, null,
new Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i("json", response.toString());
}}, null);
rq.add(request);
压轴:图片的访问
1.首先创建一个图片的缓存类,使用的是lruCache
package com.example.vollerytest;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;
public class BitmapCache implements ImageCache{
@SuppressWarnings("unused")
private static BitmapCache bitmapCache = null;
private LruCache<String, Bitmap> cache = null;
private void cacheInit(){
int maxSize = (int) (Runtime.getRuntime().maxMemory() / 1024)/8;
Log.i("memorySize", "use memory size is " + maxSize);
cache = new LruCache<String, Bitmap>(maxSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
private BitmapCache(){
cacheInit();
}
public static BitmapCache getInstance(){
if(null == bitmapCache){
bitmapCache = new BitmapCache();
}
return bitmapCache;
}
@SuppressLint("NewApi")
@Override
public Bitmap getBitmap(String url) {
if(null != cache)
return cache.get(url);
else
return null;
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
if(!TextUtils.isEmpty(url) && null != bitmap && null != cache)
cache.put(url, bitmap);
else
throw new IllegalStateException();
}
}
2.创建一个图片加载和图片缓存的处理类
//图片的加载和缓存处理类
ImageLoader mImageLoader = new ImageLoader(rq, BitmapCache.getInstance());
3.图片获取
//创建ImageView对象的监听,用于在图片成功获取或者图片获取失败时显示相应图片
ImageListener listener = ImageLoader.getImageListener(ivArray[i], R.drawable.empty, R.drawable.empty);
//将图片的URL设置给ImageView
mImageLoader.get(urls[i], listener);
小结
vollery对于普通的网络访问或json用起来还是比较舒服的,但是对于图片的请求,该框架只用了内存缓存,并未使用Disk的二级缓存,而且该框架没有对图片是否显示进行判断,比如说一个listview有200项,每一项都有一张图片,这时肯定需要下滑才能看见下面的图片,但是不管用户是否下滑listview,vollery都会将这200张图片全部下载完成,这样造成了一定程度上的运行时间,及流量的浪费。
相对于vollery,google所推出的另一套专门用于图片加载的框架,bitmapfun则有效的处理了这个问题,只有用户看到的图片才会去下载,也就是做了一个图片的懒加载,并且bitmapfun在缓存上做了硬盘和内存的双缓存。但是bitmapfun有一个非常头疼的问题。详情请看下面的链接
vollery中的disk缓存
package com.example.vollerytest;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
public class DiskBitmapCache {
public static final String TAG = "DiskBitmapCache";
public static final String PATH = "/imageCache";
public static final String CACHE_PATH = Environment.getExternalStorageDirectory() + PATH + File.separator;
public static Bitmap getBitmapFromDiskCache(String url){
if(TextUtils.isEmpty(url)){
throw new NullPointerException();
}
String url_hashCode = hashKeyForDisk(url);
File file = new File(Environment.getExternalStorageDirectory() + PATH + File.separator + url_hashCode);
BitmapFactory.Options options = new BitmapFactory.Options();
/*
* BitmapFactory.decodeFile并不会真的返回一个Bitmap给你,它仅仅会把它的宽,高取回来给你,
* 这样就不会占用太多的内存,也就不会那么频繁的发生OOM了
* 但是这时候该方法返回的bitmap为null,在大图片加载的时候考虑
*/
/* options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);//获取图片的宽高,计算以避免OOM
*/
if(null != file && file.exists())
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
else
return null;
}
/**
*
*/
public static boolean saveBitmap2file(Bitmap bmp,String url){
// 判断SDcard状态
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
{
// 错误提示
return false;
}
// 检查SDcard空间
File SDCardRoot = Environment.getExternalStorageDirectory();
if (SDCardRoot.getFreeSpace() < 10000)
{
// 弹出对话框提示用户空间不够
Log.e("Utils", "存储空间不够");
return false;
}
File bitmapFile = new File(CACHE_PATH);
bitmapFile.mkdirs();// 创建文件夹
CompressFormat format= Bitmap.CompressFormat.JPEG;
int quality = 100;
OutputStream stream = null;
try {
stream = new FileOutputStream(CACHE_PATH + hashKeyForDisk(url));
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "FileNotFoundException");
return false;
}
return bmp.compress(format, quality, stream);
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable for using as a
* disk filename.
* 将一个URL通过一个哈希算法转化成一个哈希值用来作为文件名
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
在ImageLoader的get方法中,当从内存中没有发现图片时,先去硬盘中查找,查找的key是图片URL生成的hash值,如果找到则转换成bitmap返回,如果为找到再去下载。
//TODO:硬盘获取图片
Bitmap diskBitmap = DiskBitmapCache.getBitmapFromDiskCache(requestUrl);
if(diskBitmap != null){
ImageContainer container = new ImageContainer(diskBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}