您的位置:首页 > 移动开发 > Android开发

【进阶android】Volley源码分析——Volley的工具【ImageLoader】

2015-07-29 18:58 651 查看
在上一篇文章之中,我们分析了StringRequest,并详细介绍了Request对象的生命周期及执行流程;这一章,我们将分析Volley框架中剩下的一个工具类——ImageLoader。

显然的,ImageLoader是Volley框架用以处理远程图片请求的一个工具类。此工具类封装了Volley框架对远程图片的请求、缓存等操作。

既然是Volley框架封装好的类,那么ImageLoader所提供的接口(方法)都应在UI线程之中被调用,同时从网络之中所获取的结果也将会在UI线程之中被响应;而且与Volley框架相比,ImageLoader使用了一个更有效率的方法来使得UI线程来执行对请求结果的响应。

至于Rquest对象,Volley框架也封装了一个ImageRequest类专门处理图像类别的网络请求;在ImageLoader类中,对ImageRequest类还进行了进一步的封装,即BatchedImageRequest类,该类主要增加了一个功能,就是管理所有对此次网络请求敢兴趣的兴趣点。

一、ImageLoader中的相关属性

要学习一个陌生的类,首先是看它所定义的属性,属性表如下:

属性名类型描述
mRequestQueue

RequestQueueRequestQueue对象的引用,用以添加请求到工作队列之中;
mBatchResponseDelayMs

int第一个响应到达之后,到此批响应全部到达之前,等待的毫秒数,默认为100;

即在此毫秒数之中,到达的响应都视为同一批响应。
mCache

ImageCache

图片的一级缓存;ImageCahe为一个接口;
mInFlightRequests

HashMap缓存处于飞行状态之中的请求;避免重复请求;
mBatchedResponses

HashMap同一批次的请求;
mHandler

Handler用于处理批量请求的响应;
mRunnalbeRunnable用于处理批量请求时回调的方法;
ImageLoader采用两个级别来缓存请求;一级缓存是一个ImageCache接口的实现类;二级缓存就是我们之前分析过的Volley框架之中的Cache接口。ImageCache缓存具体的图片对象,而Cache缓存则主要原始的响应内容。

mInFlightRequests是一个HashMap,主要记录处于飞行状态的Request对象,防止同一url请求重复;关于飞行状态请查看【进阶android】Volley源码分析——Volley的工具【StringRequest】一文。

除了ImageCache接口和BatchedImageRequest类之外,ImageLoader还封装了两个接口:ImageListener和ImageContainer。

ImageListener继承Response.ErrorListener接口;在此基础上它自己又定义了一个onResponse抽象方法;ImageListener接口只服务于ImageLoader类;而Response类中的两个接口却是和Request对象打交道。

ImageContainer类封装所有与图片请求响应相关的数据,主要有四种:成功加载的图片,加载成功(失败)的监听器ImageListener接口,图片请求的缓存key,图片请求的URL;上文提到BatchedImageRequest类的主要功能是管理所有对此次网络请求(Request对象)敢兴趣的兴趣点。而这里的兴趣点这个概念,就是通过ImageContainer类实现。

介绍完主要的属性和响应的内部类、内部接口时,我们便开始分析ImageLoader的工作流程。

二、ImageLoader的工作流程

ImageLoader之中一共有两种类型的工作流程:一种是处理网络请求的流程;一种是处理网络响应的流程。
我们先分析处理网络请求的流程,如下所示:



ImageLoader中通过get方法来执行网络请求流程;get方法必须在UI线程之中执行。get方法的原型如下:
public ImageContainer get(String requestUrl, ImageListener imageListener,int maxWidth, int maxHeight)
requestUrl:图片的URL;
imageListener:图片监听器;
maxWidth:图片最大宽度;
maxHeight:图片最大高度;
下面对此流程图进行分析:
1、通过ImageLoader获取图片时,首先判断一级缓存(ImageCache接口)之中是否已经缓存过需要获取的图片,如果存在,则直接以此生成一个ImageContainer对象,将其返回。一级缓存中的key是由get方法中第一个、第三个及第四个入参共同合成;这表明一级缓存可以存储同一张图片不同的像素格式。
2、如果一级缓存之中不存在请求的图片,则需要Volley框架的介入了;不过在这之前,需要创建一个ImageContainer对象,明确调用get方法的对象是一个对此次请求感兴趣的兴趣点。
3、接着根据一级缓存Key查询mInFlightRequest之中是否已有一个处于飞行状态的Request对象,如果存在则表明,Volley框架之中已经有一个相同的URL请求了,此时只需要将上一步创建的兴趣点(ImageContainer对象)添加到该Request对象的兴趣点列表之中即可;当Request对象获取到响应后,会统一处理兴趣点列表之中所有的兴趣点。ImageLoader就通过此方法,来处理重复请求的场景,即每一个重复请求的Request对象皆相同,用于处理响应的兴趣点则不同
4、如果不具有相同的URL请求,则get方法会新建一个Request对象,当然这个对象实际上是ImageRequest的一个实例。
5、将第二步创建的兴趣点(ImageContainer)与第四步新建的Request对象联系起来,即将兴趣点添加到Request对象的兴趣点列表之中,
6、创建好Request对象后,就和之前我们分析的Volley流程一样了,调用RequestQueue类的add方法,将该对象添加到相应的工作队列之中,交由工作线程处理。具体请看【进阶android】Volley源码分析——Volley的流程一文。最后返回结果。
至此,我们便将网络请求流程分析完毕;其实所谓的网络请求流程,只是ImageLoader在进行真正的Volley框架调用之前,进行的一些列判定;这些判定在不同的状态下可以直接返回结果而无须Volley框架,从而提升了相应的效率。除此之外,ImageLoader只判断一级缓存的情况,二级缓存(Cache接口)依然由Volley框架自身判定。
UI线程调用get方法将Request对象请求传递给Volley框架对应的工作线程之后,就由工作线程对其进行网络请求,UI线程继续执行消息队列的下一个消息(结合Handler机制);而当工作线程处理网络请求或者获取缓存完毕后,又会将结果封装为一个消息传递给UI线程;而UI线程则通过此消息执行响应流程的处理。
根据【进阶android】Volley源码分析——Volley的缓存这篇文章,我们可以知道,UI线程执行响应流程的入口,是调用了Request对象的deliverResponse方法。在ImageLoader类中,则是调用了ImageRequest对象的deliverResponse方法。
ImageRequest类中的deliverResponse方法代码如下:
protected void deliverResponse(Bitmap response) {
        mListener.onResponse(response);
    }
该方法调用了监听器mListener的方法;在ImageRequest类中mListener的类型为Response.Listener,并且mListener的值是通过ImageRequest的构造函数传递过来的。如此我们便可以根据上文网络请求流程的分析,知道ImageRequest对象是在ImageLoader类中的get方法进行实例的,具体代码如下:
public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        ......

        // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<?> newRequest =
            new ImageRequest(requestUrl, new Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap response) {
                    onGetImageSuccess(cacheKey, response);//成功获取图片之后,回调的方法
                }
            }, maxWidth, maxHeight,
            Config.RGB_565, new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    onGetImageError(cacheKey, error);//获取图片失败后,回调的方法
                }
            });

        ......
    }
通过代码可以看出,ImageRequest通过一个匿名类执行了Response.Listener接口;这个匿名类中的onResponse方法直接调用了ImageLoader的onGetImageSuccess方法。
分析到此,我们便可以将onGetImageSuccess方法作为ImageLoader执行网络响应流程的入口方法了。
网络响应处理的具体流程如下:



由图可以看出,网络响应处理的总体流程并不复杂;其需要注意的是第三步:批量处理请求的响应,具体执行这一步的是ImageLoader的batchResponse方法。
ImageLoader对响应的批量处理,是采用Handler的方式,用一段颇为精彩的代码实现了。下面我们就来看看这一段代码:
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);//将本次Request请求添加到批量请求集合之中
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
//如果我们还没有一个用来处理批量请求的回调方法,创建一个。
//注意此回调方法将用来处理响应请求中每一个请求。
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {//处理一个请求所有的兴趣点
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);//回调兴趣点的方法!
}......
}
}
mBatchedResponses.clear();//同批次的请求皆被处理,清空
mRunnable = null;//开始一场新的批量处理任务
}

};
// Post the runnable.
//创建消息后mBatchResponseDelayMs毫秒后,再执行消息
//mBatchResponseDelay时间内,添加到mBatchedResponses之中的所有Request对象,视为同批次的请求。
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
batchResponse通过向UI现场发送一个定时消息,来实现批量处理一批请求;这种方式与BufferedInputStream有点类似,将物理源(例如文件)中的内容读入一个缓冲区,当缓冲区满了,或者调用了flush方法,则将缓冲区直接返回给调用者。在batchResponse方法之中,mBatchResponseDelay就类似于一个缓冲区,当时间未到时(BufferedInputStream是空间未满时),将接收到的请求添加到缓冲区,时间一到就一次性处理缓冲区中所有的Request对象。
在Runnable接口的run方法中,UI线程处理了一批批次请求中所有Request对象中每一个兴趣点;因而for方法采用了两个for循环进行处理。
至此,我们便分析完ImageLoader的网络响应流程;同时也分析完整个ImageLoader。当然还有些细节并未分析,例如ImageLoader中取消请求的操作等等。

三 总结

至此,关于Volley框架的五篇分析文章就至此结束。
此刻回过头来,Volley框架对具体的HTTP请求(例如网络5层)并未有相应的修改或者优化,毕竟Volley框架的底层也是采用HTTPUrlConnection或则HTTPClient等java自身、第三方开源框架的API来进行HTTP通信。Volley框架更多的是对请求之前或者请求之后的优化以及通过多线程并发的方式进行调度。而这两方面也是在Volley框架的缓存与线程两方面得以体现。并且在这两者基础上,提供了良好的可扩展性的支持。回忆一下Volley框架之中的接口:Cache接口、Request抽象类、NetWork接口、ResponseDeliver接口以及RetryPolicy接口这些高度抽象的设计都为个性化需求的实现提供了良好的支持。
然而Volley框架也不是完美的,它在处理大文件下载方面,的确有着不可忽略的缺点。之前面试,遇到这样一个场景,一个ListView中,每个item是一个进度条,每个进度条来显示不同文件(每个文件至少100M)下载的进度,这种情况下还适合使用Volley吗?答案是显然不能的!至于为什么,大家可以结合之前的分析来思考一二。
在处理图片方面,Volley默认提供的工具类中提供了二级缓存,同时Volley框架之中处理重复图片url请求,与处理重复字符串url请求的方式也大不一样!前者采用了兴趣点(ImageContainer)的方式,来将相同的url请求与一个Request对象映射起来;而后者则是确保相同的url请求中有且仅有一个Request对象处于飞行状态之中,当这个对象结束之后,才会从剩下的相同URL请求中挑选一个Request对象,将其变为飞行状态,余下的则继续等待。两者的效率谁高谁低一目了然。
另外,Volley框架之中的一些数据结构也颇为有趣,例如LinkHashMap、PriorityBlockQueue等Java集合里我们并不太常用的集合,在Volley之中都有多处使用,这些大家都可以深入分析;Google里的大神们为何不用HashMap,ArrayList?
至此,Volley框架,我们就全部分析结束了。

当然由于本人自身水平所限,文章肯定有一些不对的地方,希望大家指出!

愿大家一起进步,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: