0%

Android开发:Google网络框架Volley的使用

Volley是Google在Google I/O 2013上发布的一个网络框架,主要功能:web接口请求,网络图片异步下载,支持缓存。volley只是定义了缓存以及Request的接口,具体实现可以自己定义,例如lru磁盘缓存,内存缓存,下载图片的ImageRequest. Volley的源代码里包含了一些实现,都在com.android.volley.toolbox包里,包括磁盘缓存、json请求,图片请求。还定义了一个继承自ImageView的NetworkImageView,可以异步载入网络图片。 项目地址: https://android.googlesource.com/platform/frameworks/volley/ 可能需要翻墙。 下面写个小例子,是请求百度图片api的,给各位参考下. 图方便,我把volley的源代码拷到自己项目里了. 百度图片接口地址为:http://image.baidu.com/channel/listjson?pn=42&rn=42&tag1=%E6%98%8E%E6%98%9F&tag2=%E6%98%9F%E9%97%BB%E6%98%9F%E4%BA%8B&ftags=&sorttype=0&ie=utf8&oe=utf-8&fr=channel&app=img.browse.channel.star 各位可以先看一下结构,针对接口的返回,定义一下Model,ImageListResponse.java:

import java.util.ArrayList;
public class ImageListResponse {
    ArrayList data;
    int totalNum;
    public ArrayList getData() {
        return data;
    }

    public void setData(ArrayList data) {
        this.data = data;
    }

    public class BaiduImage{
        String id,abs,desc,tag,date,image_url;

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getAbs() {
            return abs;
        }

        public void setAbs(String abs) {
            this.abs = abs;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public String getTag() {
            return tag;
        }

        public void setTag(String tag) {
            this.tag = tag;
        }

        public String getDate() {
            return date;
        }

        public void setDate(String date) {
            this.date = date;
        }

        public String getImage_url() {
            return image_url;
        }

        public void setImage_url(String image_url) {
            this.image_url = image_url;
        }
    }
}

定义一个Request,继承自JsonRequest(就是为了用它实现的Listener,因为Request接口是没有实现deliverResponse方法的),ListRequest.java:

import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;
import com.android.volley.toolbox.JsonRequest;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import java.io.UnsupportedEncodingException;


public class ListRequest extends JsonRequest {
    public ListRequest(Response.Listener listener,Response.ErrorListener errorListener) {
        super(Method.GET, "http://image.baidu.com/channel/listjson?pn=42&rn=42&tag1=%E6%98%8E%E6%98%9F&tag2=%E6%98%9F%E9%97%BB%E6%98%9F%E4%BA%8B&ftags=&sorttype=0&ie=utf8&oe=utf-8&fr=channel&app=img.browse.channel.star",null,listener, errorListener);
        //用来取消请求的
        setTag(listener);
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        //配合Gson,转换成我们定义的ImageListResponse
        try {
            String json = new String(
                    response.data, HttpHeaderParser.parseCharset(response.headers));
            Gson gson = new Gson();
            return Response.success(
                    gson.fromJson(json, ImageListResponse.class), HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
}

Activity里使用:

import android.app.Activity;
import android.os.Bundle;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.Volley;

import java.util.ArrayList;

public class MainActivity extends Activity implements Response.Listener,Response.ErrorListener{

    RequestQueue requestQueue;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //初始化
        requestQueue = Volley.newRequestQueue(this);
        //添加请求
        requestQueue.add(new ListRequest(this,this));

    }

    @Override
    public void onResponse(ImageListResponse response) {
       ArrayList images = response.data;
        for (ImageListResponse.BaiduImage image:images) {
            String imageUrl=image.getImage_url();
            if(imageUrl!=null)
                System.out.println(imageUrl);
        }
    }

    @Override
    public void onErrorResponse(VolleyError error) {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消请求,参数是tag
        requestQueue.cancelAll(this);
        requestQueue.stop();
    }
}

Volley判断是否需要刷新缓存是使用服务端设置的,会考虑服务端返回header里的Cache-Control的Expires。但是有时候接口并不返回这些东西,这种情况下,volley设置的缓存ttl就是0,也就是相当于没有缓存,每次都会从网络请求,参考com.android.volley.toolbox.HttpHeaderParser. 这个时候,如果我们需要强制缓存,可以继承HttpHeaderParser,重载parseCacheHeaders方法.

package com.android.volley.helper;

import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.toolbox.HttpHeaderParser;

/**
 * 自定义的HeaderParser,跟默认的比,可以强制缓存,忽略服务器的设置
 */
public class CustomHttpHeaderParser extends HttpHeaderParser {
    /**
     * Extracts a {@link com.android.volley.Cache.Entry} from a {@link com.android.volley.NetworkResponse}.
     *
     * @param response The network response to parse headers from
     * @param cacheTime 缓存时间,如果设置了这个值,不管服务器返回是否可以缓存,都会缓存,一天为1000*60*60*24
     * @return a cache entry for the given response, or null if the response is not cacheable.
     */
    public static Cache.Entry parseCacheHeaders(NetworkResponse response,long cacheTime) {
        Cache.Entry entry=parseCacheHeaders(response);
        long now = System.currentTimeMillis();
        long softExpire=now+cacheTime;
        entry.softTtl = softExpire;
        entry.ttl = entry.softTtl;
        return entry;
    }
}

然后在Request的parseNetworkResponse方法里用CustomHttpHeaderParser.parseCacheHeaders(NetworkResponse response,long cacheTime)替代HttpHeaderParser.parseCacheHeaders(NetworkResponse response). 关于NetWorkImageView,调用setImageUrl(String url, ImageLoader imageLoader)方法设置图片,setDefaultImageResId(int defaultImage)设置在图片没下载完时显示的默认图片,setErrorImageResId(int errorImage)设置在图片下载失败时显示的图片.NetWorkImageView会自动根据自身的宽高读取图片,降低OOM的概率。 当NetworkImageView在ListView中使用时,ImageLoader会处理View复用的问题,不会重复给一个复用的ImageView设置图片.可以查看ImageContainer,ImageRequest了解相关实现. ImageLoader需要一个ImageCache,用于处理图片缓存,配合开源项目DiskLruCache,我写了个ImageCache的实现,采用二级缓存,一级在内存中,一级在磁盘上.不过,磁盘读取文件还是在ui线程中,文件大了可能会有卡顿,以后再优化吧.

package com.android.volley.helper;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.LruCache;
import com.android.volley.toolbox.ImageLoader;
import com.jakewharton.disklrucache.DiskLruCache;
import utils.MD5Utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 二级Lru图片缓存,
 */
public class LruImageCache implements ImageLoader.ImageCache {
    LruCache lruCache;
    DiskLruCache diskLruCache;
    final int RAM_CACHE_SIZE = 5 * 1024 * 1024;
    String DISK_CACHE_DIR = "image";
    final long DISK_MAX_SIZE = 20 * 1024 * 1024;

    public LruImageCache() {
        this.lruCache = new LruCache(RAM_CACHE_SIZE) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

        File cacheDir = new File(Environment.getExternalStorageDirectory(), DISK_CACHE_DIR);
        if(!cacheDir.exists())
        {
            cacheDir.mkdir();
        }
        try {
            diskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_MAX_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Bitmap getBitmap(String url) {
        String key=generateKey(url);
        Bitmap bmp = lruCache.get(key);
        if (bmp == null) {
            bmp = getBitmapFromDiskLruCache(key);
            //从磁盘读出后,放入内存
            if(bmp!=null)
            {
                lruCache.put(key,bmp);
            }
        }
        return bmp;
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        String key=generateKey(url);
        lruCache.put(url, bitmap);
        putBitmapToDiskLruCache(key,bitmap);
    }

    private void putBitmapToDiskLruCache(String key, Bitmap bitmap) {
        try {
            DiskLruCache.Editor editor = diskLruCache.edit(key);
            if(editor!=null)
            {
                OutputStream outputStream = editor.newOutputStream(0);
                bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
                editor.commit();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Bitmap getBitmapFromDiskLruCache(String key) {
        try {
            DiskLruCache.Snapshot snapshot=diskLruCache.get(key);
            if(snapshot!=null)
            {
                InputStream inputStream = snapshot.getInputStream(0);
                if (inputStream != null) {
                    Bitmap bmp = BitmapFactory.decodeStream(inputStream);
                    inputStream.close();
                    return bmp;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 因为DiskLruCache对key有限制,只能是[a-z0-9_-]{1,64},所以用md5生成key
     * @param url
     * @return
     */
    private String generateKey(String url)
    {
        return MD5Utils.getMD532(url);
    }
}

MD5Utils是生成md5的类.