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);
@Override
protected 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系列文章)

Android开发笔记——以Volley图片加载、缓存、请求及展示为例理解Volley架构设计的更多相关文章

  1. Android开发三种第三方图片加载的框架

    最近在项目中用到了大量图片加载,第三方优秀框架还不错,下面介绍三款榜首的框架用法和问题,做一个记录. 现在项目使用的是Android Studio开发的,现在也没有多少人使用Eclipse了吧. 一. ...

  2. Android 开发 框架系列 Android-Universal-Image-Loader 图片加载使用demo

    Android-Universal-Image-Loader github地址:https://github.com/nostra13/Android-Universal-Image-Loader 加 ...

  3. Volley 图片加载相关源码解析

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47721631: 本文出自:[张鸿洋的博客] 一 概述 最近在完善图片加载方面的 ...

  4. Android开发之多媒体编程之加载大分辨率图片

    Android中图片占用内存的大小=图片的总像数*每个像数占用的大小. Android保存图片像素信息使用ARGB,意思是每个像素占用4个字节. 以分辨率为2400*3200的图片来说,加载到Andr ...

  5. Android Fresco (Facebook开源的图片加载管理库)

    Fresco是Facebook开源的一个图片加载和管理库. 这里是Fresco的GitHub网址. 同类型的开源库市面有非常多,比如Picasso, Universal Image Loader, G ...

  6. Volley图片加载并加入缓存处理(转自http://blog.csdn.net/viewhandkownhealth/article/details/50957024)

    直接上代码  两种方式 ImageView 和NetworkImageView 如有问题或者好的建议.意见 欢迎大家加入技术群(群号: 387648673 ) 先自定义全局Application 获取 ...

  7. 【第三篇】Volley图片加载之NetworkImageView代码分析

    在Volley的使用之加载图片讲过使用NetWorkImageView进行图片加载的例子,本文着重讲解NetWorkImageView内部是如何实现的,以及Volley这个控件有什么特性.   1,通 ...

  8. ★android开发--ListView+Json+异步网络图片加载+滚动翻页的例子(图片能缓存,图片不错乱)

    例子中用于解析Json的Gson请自己Google下载 主Activity: package COM.Example.Main; import java.util.HashMap; import ja ...

  9. Android学习笔记_36_ListView数据异步加载与AsyncTask

    一.界面布局文件: 1.加入sdcard写入和网络权限: <!-- 访问internet权限 --> <uses-permission android:name="andr ...

随机推荐

  1. python执行linux和window的命令

    linux: python执行shell脚本常用的方法 import os val=os.system("shell语句")  >>> val=os.system ...

  2. windows ionic bash: command not found

    安装好了node.js和npm后,执行npm install -g cordova ionic后,成功安装,但是执行ionic命令后,返回 command not found. 配置好了环境变量后,仍 ...

  3. Template Method(模板方法)模式

    1.概述 在面向对象开发过程中,通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序.但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关.例子1: ...

  4. JS基础——事件操作总结

    通用事件绑定   function bindEvent(elem,type,fn) { elem.addEventListener(type,fn); } let a =document.getEle ...

  5. react 使用antd 按需加载

    使用 react-app-rewired 1. 安装react-app-rewired: 由于新的 react-app-rewired@2.x 版本的关系,你还需要安装 customize-cra. ...

  6. Flume(5)-Ganglia监控

    一. 安装Ganglia 1. 安装httpd服务与php sudo yum -y install httpd php 2. 安装其他依赖 sudo yum -y install rrdtool pe ...

  7. HIve数据存储

    表 Table 内部表 Partition 分区表 External Table 外部表 Bucket Table 桶表 内部表 分区表 parttion对应于数据库中的Partition列的密集索引 ...

  8. 树莓派 ubuntu16.04 安装SSH 配置SSH 开机自启SSH

    入手个树莓派3B 装了 ubuntu 16.04 需要用到SSH 记录下 0.先获得树莓派IP 树莓派 使用网线连接路由器和树莓派 在路由器设置页面(一般是192.168.1.1具体看路由器的型号和设 ...

  9. Leecode刷题之旅-C语言/python-342 4的幂

    这里不列举普通的方法了. 发现一个好帖: 学习一下: https://blog.csdn.net/butterfly5211314/article/details/86099993 --------- ...

  10. php安装redis拓展

    1. 查看是否安装redis库 查看是否安装redis库了.可以通过下面2种方式查看. phpinfo()是否能输出redis的加载信息 在命令行执行`php -m` 输出gd 2. 安装redis库 ...