Volley是由Google开源的、用于Android平台上的网络通信库。Volley通过优化Android的网络请求流程,形成了以Request-RequestQueue-Response为主线的网络访问链,使得Android网络访问变得简单、高效、扩展性强。(根据RTFSC原则,强烈建议Android的童鞋学习下Volley的架构设计)下面将以ImageLoader、ImageCache、ImageRequest及NetworkImageView为例,对此进行说明。

一、ImageCache-ImageLoader-ImageListener

对于图片的下载,这里采用自底向上的分析方法,即首先明确Volley加载图片是通过ImageLoader的get方法实现的,然后依次说明该方法需要的参数的构成。get方法有三种重载形式(早一些的版本没有第三种):

public ImageContainer get(String requestUrl, final ImageListener listener);public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight);public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight, ScaleType scaleType);

不过前两种都是通过三种方法实现的,这里以第三种方法为例进行说明:

    public ImageContainer get(String requestUrl, ImageListener imageListener,            int maxWidth, int maxHeight, ScaleType scaleType) {        // only fulfill requests that were initiated from the main thread.        throwIfNotOnMainThread();        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);        // Try to look up the request in the cache of remote images.        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);        if (cachedBitmap != null) {            // Return the cached bitmap.            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);            imageListener.onResponse(container, true);            return container;        }        // The bitmap did not exist in the cache, fetch it!        ImageContainer imageContainer =                new ImageContainer(null, requestUrl, cacheKey, imageListener);        // Update the caller to let them know that they should use the default bitmap.        imageListener.onResponse(imageContainer, true);        // Check to see if a request is already in-flight.        BatchedImageRequest request = mInFlightRequests.get(cacheKey);        if (request != null) {            // If it is, add this request to the list of listeners.            request.addContainer(imageContainer);            return imageContainer;        }        // The request is not already in flight. Send the new request to the network and        // track it.        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,                cacheKey);        mRequestQueue.add(newRequest);        mInFlightRequests.put(cacheKey,                new BatchedImageRequest(newRequest, imageContainer));        return imageContainer;    }

上述代码的逻辑非常清晰,可以下面的流程图来表示,不再赘述:

对于StringRequest、JsonRequest,Volley也采用了同样的处理流程。

从get方法的形参入手,这里着重说明ImageListener(其它的形参见名知义)。ImageListener是ImageLoader的内部接口,继承于ErrorListener,需要实现的方法为onResponse:

    public interface ImageListener extends ErrorListener {        /**         * Listens for non-error changes to the loading of the image request.         *         * @param response Holds all information pertaining to the request, as well         * as the bitmap (if it is loaded).         * @param isImmediate True if this was called during ImageLoader.get() variants.         * This can be used to differentiate between a cached image loading and a network         * image loading in order to, for example, run an animation to fade in network loaded         * images.         */        public void onResponse(ImageContainer response, boolean isImmediate);    }

ImageLoader还提供了静态方法getImageListener来获取ImageListener实例:

    public static ImageListener getImageListener(final ImageView view,            final int defaultImageResId, final int errorImageResId) {        return new ImageListener() {            @Override            public void onErrorResponse(VolleyError error) {                if (errorImageResId != 0) {                    view.setImageResource(errorImageResId);                }            }            @Override            public void onResponse(ImageContainer response, boolean isImmediate) {                if (response.getBitmap() != null) {                    view.setImageBitmap(response.getBitmap());                } else if (defaultImageResId != 0) {                    view.setImageResource(defaultImageResId);                }            }        };    }

从代码很容易看出,ImageListener就是Image请求返回时的回调接口,onErrorResponse和onResponse分别实现了请求失败和成功时加载对应的图片。

分析完get方法执行的流程及形参之后,我们回到ImageLoader本身。ImageLoader的构造函数如下:

/**     * Constructs a new ImageLoader.     * @param queue The RequestQueue to use for making image requests.     * @param imageCache The cache to use as an L1 cache.     */    public ImageLoader(RequestQueue queue, ImageCache imageCache) {        mRequestQueue = queue;        mCache = imageCache;    }

同样,从ImageLoader的形参入手,这里需要传入RequestQueue和ImageCache的实例对象。RequestQueue即整个Volley的核心请求队列,在使用Volley时第一个初始化的对象。其构造方法在Volley源码toolbox文件夹下的Volley工具类中:

public class Volley {    /** Default on-disk cache directory. */    private static final String DEFAULT_CACHE_DIR = "volley";    /**     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.     *     * @param context A {@link Context} to use for creating the cache dir.     * @param stack An {@link HttpStack} to use for the network, or null for default.     * @return A started {@link RequestQueue} instance.     */    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) {            if (Build.VERSION.SDK_INT >= 9) {                stack = new HurlStack();            } else {                // Prior to Gingerbread, HttpUrlConnection was unreliable.                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));            }        }        Network network = new BasicNetwork(stack);        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);        queue.start();        return queue;    }    /**     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.     *     * @param context A {@link Context} to use for creating the cache dir.     * @return A started {@link RequestQueue} instance.     */    public static RequestQueue newRequestQueue(Context context) {        return newRequestQueue(context, null);    }}

这里重点说明ImageCache,即图片的缓存。ImageCache同样是定义在ImageLoader中的接口(从这里也可以看出volley的高可扩展性):

/**     * Simple cache adapter interface. If provided to the ImageLoader, it     * will be used as an L1 cache before dispatch to Volley. Implementations     * must not block. Implementation with an LruCache is recommended.     */    public interface ImageCache {        public Bitmap getBitmap(String url);        public void putBitmap(String url, Bitmap bitmap);    }

根据注释可以看出,推荐使用LruCache。对于LruCache,前面的博文《Android开发笔记——ListView模块、缓存及性能》已做过详细介绍,其通过维护一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。当cache已满时加入新的item,在队列尾部的item会被回收。

不过,在某些应用场景下,只使用LruCache还不够。当应用退出后,LruCache清空,重新加载时,缓存的图片依然需要重新加载。这里需要使用DiskLruCache,即磁盘缓存,原理与《Android开发笔记——ListView模块、缓存及性能》中SD卡存储配合LruCache相同,但DiskLruCache实现更为合理,获得Google官方认证。

对于DiskLruCache的源码解析,推荐Android DiskLruCache完全解析,硬盘缓存的最佳方案。

因此,建议配合LruCache和DiskLruCache,以及Volley的请求缓存,形成图片三级缓存。LruCache和DiskLruCache的初始化方法分别如下:

int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);// 实例化LruCaceh对象mLruCache = new LruCache<String, Bitmap>(maxSize) {     @Override     protected int sizeOf(String key, Bitmap bitmap) {          return bitmap.getRowBytes() * bitmap.getHeight();     }};
// 实例化DiskLruCache对象 try {      // 获取DiskLruCahce对象      mDiskLruCache = DiskLruCache.open(getDiskCacheDir(                    context.getApplicationContext(), "younghao"), getAppVersion(context), 1, DISKMAXSIZE);     } catch (IOException e) {        e.printStackTrace();}

ImageCaches实现getBitmap和putBitmap方法时,可以使用LruCache和DiskLruCache实例高效处理图片。存入缓存时,先存入到LruCache中,然后判断是否存在DiskLruCache缓存,若没有存入;读取图片时,先从LruCache中取,没有时再从DiskLruCache中取(取出之后,顺便存入LruCache中,供下次访问时使用,不再访问DiskLruCache)。

/**     * 存入缓存(内存缓存,磁盘缓存)     */    @Override    public void putBitmap(String url, Bitmap bitmap) {        // 存入LruCache缓存        mLruCache.put(url, bitmap);        // 判断是否存在DiskLruCache缓存,若没有存入        String key = MD5Utils.md5(url);        try {            if (mDiskLruCache.get(key) == null) {                DiskLruCache.Editor editor = mDiskLruCache.edit(key);                if (editor != null) {                    OutputStream outputStream = editor.newOutputStream(0);                    if (bitmap.compress(CompressFormat.JPEG, 100, outputStream)) {                        editor.commit();                    } else {                        editor.abort();                    }                }                mDiskLruCache.flush();            }        } catch (IOException e) {            e.printStackTrace();        }    }
/**     * 从缓存(内存缓存,磁盘缓存)中获取Bitmap     */    @Override    public Bitmap getBitmap(String url) {        if (mLruCache.get(url) != null) {            // 从LruCache缓存中取            Log.i(TAG, "从LruCahce获取");            return mLruCache.get(url);        } else {            String key = MD5Utils.md5(url);            try {                if (mDiskLruCache.get(key) != null) {                    // 从DiskLruCahce取                    DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);                    Bitmap bitmap = null;                    if (snapshot != null) {                        bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));                        // 存入LruCache缓存                        mLruCache.put(url, bitmap);                        Log.i(TAG, "从DiskLruCahce获取");                    }                    return bitmap;                }            } catch (IOException e) {                e.printStackTrace();            }        }        return null;    }

至此,通过volley加载图片的方法已完成。调用方法如下:

// 获取ImageCache实例ImageCacheUtil imageCacheUtil = ImageCacheUtil.instance(context);// 初始化ImageLoader实例ImageLoader imageLoader = new ImageLoader(requestQueue, imageCacheUtil);// 获取ImageListener实例ImageListener listener = ImageLoader.getImageListener(imageRequestBean.getImageView(),        imageRequestBean.getDefaultImageID(), imageRequestBean.getErrorImageID());// 发送请求图片imageLoader.get(imageRequestBean.getUrl(),         
        listener,imageRequestBean.getMaxWidth(), imageRequestBean.getMaxHeight());

二、对Volley的架构的理解

如果有童鞋能读到这里,那么对Volley的网络请求处理逻辑应该已经有了一定的认识。下面将通过Volley的官方文档对Volley的架构作进一步的说明。Volley提供了对于网络请求的自动调度,能够处理高并发网络链接,拥有透明的磁盘及内存缓存,支持请求优先级、取消请求、异步网络请求等。Volley的源码地址为:https://android.googlesource.com/platform/frameworks/volley(git clone),不过考虑到网络问题,也可到github上下载:https://github.com/mcxiaoke/android-volley.git(git clone)。

2013年Volley发布会视频:Google I/O 2013 - Volley: Easy, Fast Networking for Android(YouTube,你懂)

2.1 发送一个简单的请求

先贴一张官网的图,后面做解释。

这张图展示了Volley的核心架构,主要包含了以下类:

  • Volley。前面已经提到,位于toolbox文件中,是创建请求队列的工具类;
  • Request。实现了Comparable<Request<T>>接口的抽象类,Volley中的请求都是继承于该类实现的,Request支持八种请求方法。
public abstract class Request<T> implements Comparable<Request<T>>
/**     * Supported request methods.     */    public interface Method {        int DEPRECATED_GET_OR_POST = -1;        int GET = 0;        int POST = 1;        int PUT = 2;        int DELETE = 3;        int HEAD = 4;        int OPTIONS = 5;        int TRACE = 6;        int PATCH = 7;    }
  • RequestQueue。Volley的核心,代表整个请求队列。需要重点说明的是其成员变量,包含了
    • CacheDispatcher(处理缓存请求的调度线程)
    • NetworkDispatcher[](处理网络请求的调用线程组)
    • ResponseDelivery(网络请求返回接口分发)
    • Network (执行网络请求的网络接口)
    • Cache(缓存请求的接口,PS:上一节说的是缓存图片)
    • DEFAULT_NETWORK_THREAD_POOL_SIZE(默认线程池数目,至于为什么是4?这是一个经验值,在自己实际应用中,可根据任务、网络状况以及设备等灵活设置)
    • PriorityBlockingQueue<Request<?>> (基于优先级阻塞的请求队列,包含等待的和正在执行的)、
    • Set<Request<?>> mCurrentRequests (正在处理的请求)
    • Map<String, Queue<Request<?>>> mWaitingRequests(正在等待的请求)
    • AtomicInteger mSequenceGenerator(原子的,避免并发访问)
    • RequestFinishedListener<T>接口(请求完成的回调)
/** Callback interface for completed requests. */    public static interface RequestFinishedListener<T> {        /** Called when a request has finished processing. */        public void onRequestFinished(Request<T> request);    }    /** Used for generating monotonically-increasing sequence numbers for requests. */    private AtomicInteger mSequenceGenerator = new AtomicInteger();    /**     * Staging area for requests that already have a duplicate request in flight.     *     * <ul>     *     <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache     *          key.</li>     *     <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request     *          is <em>not</em> contained in that list. Is null if no requests are staged.</li>     * </ul>     */    private final Map<String, Queue<Request<?>>> mWaitingRequests =            new HashMap<String, Queue<Request<?>>>();    /**     * The set of all requests currently being processed by this RequestQueue. A Request     * will be in this set if it is waiting in any queue or currently being processed by     * any dispatcher.     */    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();    /** The cache triage queue. */    private final PriorityBlockingQueue<Request<?>> mCacheQueue =        new PriorityBlockingQueue<Request<?>>();    /** The queue of requests that are actually going out to the network. */    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =        new PriorityBlockingQueue<Request<?>>();    /** Number of network request dispatcher threads to start. */    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;    /** Cache interface for retrieving and storing responses. */    private final Cache mCache;    /** Network interface for performing requests. */    private final Network mNetwork;    /** Response delivery mechanism. */    private final ResponseDelivery mDelivery;    /** The network dispatchers. */    private NetworkDispatcher[] mDispatchers;    /** The cache dispatcher. */    private CacheDispatcher mCacheDispatcher;    private List<RequestFinishedListener> mFinishedListeners =            new ArrayList<RequestFinishedListener>();

发送一个请求,只需将请求添加到请求队列即可。官网的一段示例代码如下:

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

取消请求同样简单,通过TAG来找到特定的request,然后取消。

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);
@Overrideprotected void onStop () {    super.onStop();    if (mRequestQueue != null) {        mRequestQueue.cancelAll(TAG);    }}

2.2 创建一个RequestQueue

在2.1节中,使用了默认的RequestQueue构造器(即通过Volley工具类),但Volley支持自定义NetWork和Cache,实现更加个性化的网络和缓存。另外一个需要注意的问题就是单例模式,为了高效的使用RequestQueue,官方建议在整个应用的生命周期内只使用一个RequestQueue实例。不过注意到很多中文的帖子使用继承Application,在Application的onCreate()方法中创建RequestQueue,官方并不鼓励这种做法,使用静态的的单例能够以更加模块化的方式实现同样的功能。核心的思想是RequestQueue应该被Application的context实例,而不是某个Activity的context。(getApplicationContext()与getContext()方法的区别)

2.3 创建一个标准的请求

继承Request的请求类型主要有:StringRequest、ImageRequest、JsonObjectRequest和JsonArrayRequest(JsonRequest的子类),第一节对ImageRequest进行了详细的说明,这里主要说明JsonObjectRequest和JsonArrayRequest,因为在实际项目中,通Json传递数据可能是目前最常见的方式。

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<JSONObject>() {    @Override    public void onResponse(JSONObject response) {        mTxtDisplay.setText("Response: " + response.toString());    }}, new Response.ErrorListener() {    @Override    public void onErrorResponse(VolleyError error) {        // TODO Auto-generated method stub    }});// Access the RequestQueue through your singleton class.MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);

官方的示例代码比较简单。不过可以关注下Request的方法,它抽象了StringRequest、ImageRequest、JsonRequest的公共特征,这种结构设计的思路值得学习。

最后推荐几个博客作为参考:

Volley 源码解析

Android库Volley的使用介绍

Android Volley完全解析(四),带你从源码的角度理解Volley(Volley系列文章)

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. Android中AsyncTask的简单用法
  5. Unity调用Android配置方法
  6. Android(安卓)Handler机制剖析
  7. Android(安卓)之Service的使用及生命周期
  8. Android(安卓)下使用 JSON 实现 HTTP 请求,外加几个示例!
  9. android.util.LruCache类

随机推荐

  1. Android(安卓)Layout XML属性
  2. Android(安卓)Handler机制2--ThreadLocal
  3. [置顶] [Android基础]Android中SharedPrefe
  4. Android(安卓)Studio安装与配置
  5. android 使用Intent传递数据之返回结果
  6. 通过xml布局文件实现按钮改变焦点设置背
  7. Android(安卓)viewpager 实现画廊效果 左
  8. android:backgroundDimEnabled的作用
  9. 解决Eclipse New菜单没有Android(安卓)Pr
  10. Android实现九宫格 主界面应用列表效果