android-volley 图片缓存分析与比较Volley , Universal-Image-Loader 和 picasso
2015-06-05 21:40
375 查看
关于android图片缓存开源框架,被程序猿们津津乐道的应该是,Volley , Universal-Image-Loader 和 picasso。关于他们大家问的最多的问题是,到底Volley和UIL那个好?其实我也不知道哪个好,但是我可以帮大家分析分析,大家来评判一下:
首先说说他们之间关于缓存部分(Cache),用过Volley的同行应该都知道,它只是提供了接口,ImageCache 和 Cache 让大家自己去实现,接口已经定义好了,至于大家想用内存缓存,还是磁盘缓存,看各自应用场景了。而相比于UIL提供了内存缓存,还同时实现了各种内存缓存的策略,另外还实现了磁盘缓存以及磁盘缓存的各种策略。其实这部分代码并不复杂,我就算实现在这个框架里面了,也不会和UIL有多大差异吧。
接下来就按照Volley加载图片ImageRequest,ImageLoader,NetworkImageView三种方式入手,分析Volley加载图片的控制流程和UIL的Core部分有哪些差别。
Volley加载图片的第一种方式:ImageRequest
先简单分析一下这两个类。
ImageRequest:就是一个网络请求数据类,包含图片下载地址,需要下载图片的宽高,格式,已近下载成功的回调等等。
RequestQueue:这个是个核心类,下面是各个成员变量的作用。
Cache mCache; 缓存自己实现.
ResponseDelivery mDelivery; 分发请求结果。
mWaitingRequests 等待队列 重复请求处理
mCurrentRequests 当前所有请求队列,同意处理,比如取消。
mCacheQueue 需要获取图片的请求请求队列
mNetworkQueue 需要从网络获取图片的请求队列
NetworkDispatcher 网络请求处理线程,从mNetworkQueue队列中读取从网络获取图片请求。
CacheDispatcher 缓存处理线程, 从mCacheQueue 队列中获取数据, 从自定义缓存mCache中获取对应请求的数据,如果数据存在则调用mDelivery分发请求结果,如没有加入到mNetworkQueue。
然后为我们分析add函数做一下简单的铺垫。在RequestQueue构造函数中
我们会为缓存mCache, 网络请求mNetwork, mDispatchers图片处理线程数组,处理结果分发mDelivery。几个对象初始化。以及定义处理加载网络请求线程个数threadPoolSize
再看RequestQueue.start函数。
首先停止当前的线程,然后创建CacheDispatcher 线程并把mCacheQueue, mNetworkQueue, mCache, mDelivery传进入,再启动threadPoolSize个NetworkDispatcher线程启动了同样也把mNetworkQueue, mNetwork, mCache, mDelivery传进去。一切算是准备就绪了。先打住,我们回忆一下UIL中是怎样处理的?UIL中是我们自己配置的线程池taskExecutorForCachedImages和taskExecutor 也就是说,UIL中是用线程池的方式,和Volley就是开五个常驻线程。
铺垫准备就绪了,看看mQueue.add(imageRequest);函数。
这个函数比较简单主要工作如下:
这里把RequestQueue保存到request里面,同时把request加入到mCurrentRequests中。然后判断这个请求是否需要缓存,如果不需要直接加入到mNetworkQueue让NetworkDispatcher去处理,然后返回。如果需要缓存获得缓存请求key,判断当前mWaitingRequests等待队列里面是否已经存在这个请求,如果存在就保存到mWaitingRequests对应的key的请求队列中。如果不存在就往mWaitingRequests中加入KEY并对应null值,表示没有相同key的请求。最后把request加入到mCacheQueue中。这样add函数就完成了。mWaitingRequests的作用相信大家看出来了,就是保存重复请求的。
CacheDispatcher线程分析:
前面说过mCacheQueue队列中的数据是由CacheDispatcher线程来读取并处理的。现在看看CacheDispatcher的run函数。
这个函数比较长分段阅读:
这里是从mCacheQueue取出请求然后判断是否此请求已经被取消,如果取消则继续去下一条。
上面主要检查是否该请求已经缓存,或者是否过期,如果都不成立就直接把请求加入到mNetworkQueue中去取下一条。
如果在缓存中且没有过期,我们构造一个请求结果,同时判断该请求是否需要刷新,如果不需要直接mDelivery.postResponse(request, response);分发请求结果,如果需要则,在分发请求结果的同时把这个请求加入到mNetworkQueue中重新再请求一次,拿到新的图片再刷新。
接下来看看mDelivery.postResponse做了啥?
往主线程消息队列里面发送了一个
调用mRequest.finish,而在mRequest.finish中又调用了mRequestQueue.finish(this);我们还记得在Add函数的第一行就把mRequestQueue保存到了request中。看看mRequestQueue.finish
把当前request从mCurrentRequests移除,也就是说mCurrentRequests保存了当前所有未完成的请求。继续往下看,request.shouldCache()判断当前请求是否需要缓存,如果需要,这从waitingRequests找出所有正在等待中的同样key请求。全部加入到缓存mCacheQueue队列中。交给CacheDispatcher处理。到这里我想我们应该打住了,想想UIL是怎么处理同样的Uri请求的。在我的Android-Universal-Image-Loader简单分析-Core部分文章中有说道,每个uri创建一个 ReentrantLock对象。同时保存在ImageLoaderEngine 的uriLocks 中,之所以每一个uri对应一个,是为了防止多个imageView同时都从网络加载同一张图片的情况。它可以让其中一个task从网络下载图片,让后让后来的task等待直到第一个task下载完成
然后别的task就可以从内存中区获取该图片的bigmap.也就是说会阻塞线程池工作线程。而Volley不会。
这几行代码就是调用request分发处理结果,然后调用Response.Listener或者Response.ErrorListener做具体的请求。
这几行代码就是执行前面传进来的Runnable对象run函数比如说前面的如果这个请求需要刷新就把当前请求加入到mNetworkQueue中。
NetworkDispatcher线程分析:
在CacheDispatcher线程中,如果该请求没有缓存,或者该请求过期,或者该请求需要刷新会把该请求加入到mNetworkQueue中,而mNetworkQueue是由NetworkDispatcher来读取数据并处理的。下面看看NetworkDispatcher.run函数。
这个函数主要先是从mNetworkQueue取出需要发起网络请求的request,然后判断是否已经被用户取消,如果取消就结束去取下一个请求。如果未取消,这mNetwork执行这个网络请求,拿到请求后调用request.parseNetworkResponse解析这个请求,这里主要是根据之前我们对图片一些宽高,格式或者imageview大小或解析出合适的bitmap。解析完成之后判断是否需要缓存,如果需要就把他加入到mCache缓存中。然后调用mDelivery.postResponse完成对此次请求的分发工作。前面在CacheDispatcher中已经详细分析过了。
Volley加载图片的第二种方式:ImageLoader
在ImageLoader构造函数中,有我们熟悉的mQueue另外的就是ImageCache 这个是图片缓存,需要我们自己实现自己的缓存方式。ImageListener 为imageView不同状态设置不同图片加载完成时做收尾工作。现在看看imageLoader.get函数
上面的代码主要是检测是否在主线程中,然后判断当前请求是否已经在缓存中,如果在这创建一个ImageContainer 然后调用imageListener.onResponse把结果返回,为ImageView设置上对应的bitmap数据。
上面代码主要是新建一个ImageContainer对象,同时调用imageListener.onResponse为ImageView显示加载中的图片。
上面代码主要是判断当前请求是否在mInFlightRequests存在,如果存在这加入到对应key的BatchedImageRequest批处理请求中。其实mInFlightRequests的作用还是为了处理重复的uri请求。有前面知道ImageRequest两个listener 当期请求完成有mDelivery分发请求,最后由listener处理最后的显示,而在ImageLoader中在Listener的onResponse和onErrorResponse中会调用onGetImageSuccess和onGetImageError函数在这两个函数中会
从mInFlightRequests取出相同key的请求,然后调用batchResponse函数直接把结果通过listen设置上对应的图片。
mRequestQueue.add(newRequest);
这个函数我们前面已经详细分析过了。其实ImageLoader只是对mRequestQueue封装。方便用户更简单的使用。在这个方面Volley相比UIL去做需要的ImageLoaderConfiguration和DisplayImageOptions要更简洁,当然,我们也可以使用UIL默认的配置。
Volley加载图片的第三种方式:NetworkImageView
其实NetworkImageView就是继承ImageView然后封装了ImageLoader。也就是说它内部还是调用了第二种加载方式中ImageLoader.get,在ImageListener的onResponse函数中直接调用Imageview 的接口根据设置加载结果。看似简单的这个NetworkImageView背后其实隐藏着让人深思的问题。什么问题呢?来看看networkImageView.setImageUrl函数。
这个函数重点在于loadImageIfNecessary(false);对这个函数做完简化处理。
这个就是如果当前下载Url为空,mImageContainer不为空,表示什么?这个Imageview之前有一个下载图片的请求,现在需要用新的URL而这个值有为空,那我们就取消掉之前的,然后把现在的赋值为空。
如果运行到这里表示新的URL不为空,那么我们判断一下新的URL是否和之前的一样,一样就返回了,不一样,这取消掉之前的下载请求。
最后把新的newContainer 保存起来。
}
到这里,函数已经分析完了,用过UIL的都知道,它完成了,在ListView滑动中ImageView 重用时,取消之前Task的执行,从而避免下载不需要的图片,另外在ListView快速滑动过程中,我们可以监听并设置ImageLoaderEngine.pause 让其它还未执行下载的task阻塞。其实我开始说的值得深思的问题。
回头再看看如果Imageview当前新的URL与之前要加载的图片URL不一样,我们会调用mImageContainer.cancelRequest看他是如何取消的呢?
这个函数最重要的一个调用就是BatchedImageRequest. removeContainerAndCancelIfNecessary()而在这个函数中,有调用了ImageRequest.cancel(),前面分析过ImageRequest是我们NetworkDispatcher和CacheDispatcher一个执行的单位,这两个线程会从队列mNetworkQueue和mCacheQueue取出这个请求(ImageRequest)先判断是否已经取消,如果没有取消,那么就开始执行下载,如果取消了,就返回取下一个请求。原来是这样,那快速滑动怎么解决呢?其实,还是mImageContainer.cancelReques这个函数,试想一下如果listView快速滑动,那么Imageview必然会很快重用,很快被重用带来的是之前的请求很快就会被取消。那么NetworkDispatcher线程如果正在忙,等他忙完了,从mNetworkQueue取出来的ImageRequest可能就已经被取消了。如果不忙,就让它在快速滑动的时候请求一张图片也未尝不可嘛!
到此,我对volley和UIL简单的比较分析做完了。我尽量做到看完每一个类控制部分的数据和使用部分的数据,不放过我能发现的可以比较的细节,但是我知道还有许多我没有发现的东西。如果遗漏的地方请大神们多多指点!
首先说说他们之间关于缓存部分(Cache),用过Volley的同行应该都知道,它只是提供了接口,ImageCache 和 Cache 让大家自己去实现,接口已经定义好了,至于大家想用内存缓存,还是磁盘缓存,看各自应用场景了。而相比于UIL提供了内存缓存,还同时实现了各种内存缓存的策略,另外还实现了磁盘缓存以及磁盘缓存的各种策略。其实这部分代码并不复杂,我就算实现在这个框架里面了,也不会和UIL有多大差异吧。
接下来就按照Volley加载图片ImageRequest,ImageLoader,NetworkImageView三种方式入手,分析Volley加载图片的控制流程和UIL的Core部分有哪些差别。
Volley加载图片的第一种方式:ImageRequest
RequestQueuemQueue=Volley.newRequestQueue(context); ImageRequestimageRequest = new ImageRequest( "图片下载地址", new Response.Listener<Bitmap>(){ @Override public void onResponse(Bitmapresponse) { imageView.setImageBitmap(response); } }, 0, 0, Config.RGB_565, newResponse.ErrorListener() { @Override public void onErrorResponse(VolleyErrorerror) { imageView.setImageResource(R.drawable.default_image); } }); mQueue.add(imageRequest);
先简单分析一下这两个类。
ImageRequest:就是一个网络请求数据类,包含图片下载地址,需要下载图片的宽高,格式,已近下载成功的回调等等。
RequestQueue:这个是个核心类,下面是各个成员变量的作用。
Cache mCache; 缓存自己实现.
ResponseDelivery mDelivery; 分发请求结果。
mWaitingRequests 等待队列 重复请求处理
mCurrentRequests 当前所有请求队列,同意处理,比如取消。
mCacheQueue 需要获取图片的请求请求队列
mNetworkQueue 需要从网络获取图片的请求队列
NetworkDispatcher 网络请求处理线程,从mNetworkQueue队列中读取从网络获取图片请求。
CacheDispatcher 缓存处理线程, 从mCacheQueue 队列中获取数据, 从自定义缓存mCache中获取对应请求的数据,如果数据存在则调用mDelivery分发请求结果,如没有加入到mNetworkQueue。
然后为我们分析add函数做一下简单的铺垫。在RequestQueue构造函数中
public RequestQueue(Cache cache, Networknetwork, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
我们会为缓存mCache, 网络请求mNetwork, mDispatchers图片处理线程数组,处理结果分发mDelivery。几个对象初始化。以及定义处理加载网络请求线程个数threadPoolSize
再看RequestQueue.start函数。
public void start() { stop(); // Make sure anycurrently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue,mCache, mDelivery); mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the poolsize. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = newNetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
首先停止当前的线程,然后创建CacheDispatcher 线程并把mCacheQueue, mNetworkQueue, mCache, mDelivery传进入,再启动threadPoolSize个NetworkDispatcher线程启动了同样也把mNetworkQueue, mNetwork, mCache, mDelivery传进去。一切算是准备就绪了。先打住,我们回忆一下UIL中是怎样处理的?UIL中是我们自己配置的线程池taskExecutorForCachedImages和taskExecutor 也就是说,UIL中是用线程池的方式,和Volley就是开五个常驻线程。
铺垫准备就绪了,看看mQueue.add(imageRequest);函数。
public<T> Request<T> add(Request<T> request) { request.setRequestQueue(this); synchronized (mCurrentRequests) { mCurrentRequests.add(request); } request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); if(!request.shouldCache()) { mNetworkQueue.add(request); return request; } synchronized (mWaitingRequests) { String cacheKey =request.getCacheKey(); if (mWaitingRequests.containsKey(cacheKey)) { Queue<Request<?>> stagedRequests =mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = newLinkedList<Request<?>>(); } stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); } else { mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; } }
这个函数比较简单主要工作如下:
这里把RequestQueue保存到request里面,同时把request加入到mCurrentRequests中。然后判断这个请求是否需要缓存,如果不需要直接加入到mNetworkQueue让NetworkDispatcher去处理,然后返回。如果需要缓存获得缓存请求key,判断当前mWaitingRequests等待队列里面是否已经存在这个请求,如果存在就保存到mWaitingRequests对应的key的请求队列中。如果不存在就往mWaitingRequests中加入KEY并对应null值,表示没有相同key的请求。最后把request加入到mCacheQueue中。这样add函数就完成了。mWaitingRequests的作用相信大家看出来了,就是保存重复请求的。
CacheDispatcher线程分析:
前面说过mCacheQueue队列中的数据是由CacheDispatcher线程来读取并处理的。现在看看CacheDispatcher的run函数。
这个函数比较长分段阅读:
final Request<?> request = mCacheQueue.take(); request.addMarker("cache-queue-take"); if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; }
这里是从mCacheQueue取出请求然后判断是否此请求已经被取消,如果取消则继续去下一条。
Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); mNetworkQueue.put(request); continue; } if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; }
上面主要检查是否该请求已经缓存,或者是否过期,如果都不成立就直接把请求加入到mNetworkQueue中去取下一条。
request.addMarker("cache-hit"); Response<?> response =request.parseNetworkResponse( newNetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { mDelivery.postResponse(request, response); } else { request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); response.intermediate =true; mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(request); } catch(InterruptedException e) { } } }); } } catch (InterruptedException e) { if (mQuit) { return; } continue; }
如果在缓存中且没有过期,我们构造一个请求结果,同时判断该请求是否需要刷新,如果不需要直接mDelivery.postResponse(request, response);分发请求结果,如果需要则,在分发请求结果的同时把这个请求加入到mNetworkQueue中重新再请求一次,拿到新的图片再刷新。
接下来看看mDelivery.postResponse做了啥?
往主线程消息队列里面发送了一个
ResponseDeliveryRunnablerunnable对象。 public void run() { if (mRequest.isCanceled()) { mRequest.finish("canceled-at-delivery"); return; }
调用mRequest.finish,而在mRequest.finish中又调用了mRequestQueue.finish(this);我们还记得在Add函数的第一行就把mRequestQueue保存到了request中。看看mRequestQueue.finish
void finish(Request<?> request) { synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } if (request.shouldCache()) { synchronized (mWaitingRequests) { String cacheKey =request.getCacheKey(); Queue<Reque f9ff st<?>>waitingRequests = mWaitingRequests.remove(cacheKey); if (waitingRequests != null) { mCacheQueue.addAll(waitingRequests); } } }
把当前request从mCurrentRequests移除,也就是说mCurrentRequests保存了当前所有未完成的请求。继续往下看,request.shouldCache()判断当前请求是否需要缓存,如果需要,这从waitingRequests找出所有正在等待中的同样key请求。全部加入到缓存mCacheQueue队列中。交给CacheDispatcher处理。到这里我想我们应该打住了,想想UIL是怎么处理同样的Uri请求的。在我的Android-Universal-Image-Loader简单分析-Core部分文章中有说道,每个uri创建一个 ReentrantLock对象。同时保存在ImageLoaderEngine 的uriLocks 中,之所以每一个uri对应一个,是为了防止多个imageView同时都从网络加载同一张图片的情况。它可以让其中一个task从网络下载图片,让后让后来的task等待直到第一个task下载完成
然后别的task就可以从内存中区获取该图片的bigmap.也就是说会阻塞线程池工作线程。而Volley不会。
if (mResponse.isSuccess()) { mRequest.deliverResponse(mResponse.result); } else { mRequest.deliverError(mResponse.error); }
这几行代码就是调用request分发处理结果,然后调用Response.Listener或者Response.ErrorListener做具体的请求。
if (mRunnable != null) { mRunnable.run(); } }
这几行代码就是执行前面传进来的Runnable对象run函数比如说前面的如果这个请求需要刷新就把当前请求加入到mNetworkQueue中。
NetworkDispatcher线程分析:
在CacheDispatcher线程中,如果该请求没有缓存,或者该请求过期,或者该请求需要刷新会把该请求加入到mNetworkQueue中,而mNetworkQueue是由NetworkDispatcher来读取数据并处理的。下面看看NetworkDispatcher.run函数。
public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { long startTimeMs =SystemClock.elapsedRealtime(); Request<?> request; request = mQueue.take(); try { request.addMarker("network-queue-take"); if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); NetworkResponse networkResponse= mNetwork.performRequest(request); request.addMarker("network-http-complete"); if (networkResponse.notModified&& request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } Response<?> response =request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); if (request.shouldCache()&& response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } request.markDelivered(); mDelivery.postResponse(request,response); } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() -startTimeMs); parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog.e(e, "Unhandledexception %s", e.toString()); VolleyError volleyError = newVolleyError(e); volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime()- startTimeMs); mDelivery.postError(request,volleyError); } } }
这个函数主要先是从mNetworkQueue取出需要发起网络请求的request,然后判断是否已经被用户取消,如果取消就结束去取下一个请求。如果未取消,这mNetwork执行这个网络请求,拿到请求后调用request.parseNetworkResponse解析这个请求,这里主要是根据之前我们对图片一些宽高,格式或者imageview大小或解析出合适的bitmap。解析完成之后判断是否需要缓存,如果需要就把他加入到mCache缓存中。然后调用mDelivery.postResponse完成对此次请求的分发工作。前面在CacheDispatcher中已经详细分析过了。
Volley加载图片的第二种方式:ImageLoader
ImageLoaderimageLoader = new ImageLoader(mQueue, new ImageCache() { @Override public void putBitmap(String url, Bitmapbitmap) { } @Override public Bitmap getBitmap(String url) { return null; } }); ImageListenerlistener = ImageLoader.getImageListener(imageView, R.drawable.default_image,R.drawable.failed_image); imageLoader.get("图片加载地址",listener, 200, 200);
在ImageLoader构造函数中,有我们熟悉的mQueue另外的就是ImageCache 这个是图片缓存,需要我们自己实现自己的缓存方式。ImageListener 为imageView不同状态设置不同图片加载完成时做收尾工作。现在看看imageLoader.get函数
public ImageContainer get(StringrequestUrl, ImageListener imageListener, int maxWidth, int maxHeight) { throwIfNotOnMainThread(); final String cacheKey =getCacheKey(requestUrl, maxWidth, maxHeight); Bitmap cachedBitmap =mCache.getBitmap(cacheKey); if (cachedBitmap != null) { ImageContainer container = newImageContainer(cachedBitmap, requestUrl, null, null); imageListener.onResponse(container,true); return container; }
上面的代码主要是检测是否在主线程中,然后判断当前请求是否已经在缓存中,如果在这创建一个ImageContainer 然后调用imageListener.onResponse把结果返回,为ImageView设置上对应的bitmap数据。
ImageContainer imageContainer = new ImageContainer(null,requestUrl, cacheKey, imageListener); imageListener.onResponse(imageContainer, true);
上面代码主要是新建一个ImageContainer对象,同时调用imageListener.onResponse为ImageView显示加载中的图片。
BatchedImageRequest request =mInFlightRequests.get(cacheKey); if (request != null) { request.addContainer(imageContainer); return imageContainer; } Request<Bitmap> newRequest =makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
上面代码主要是判断当前请求是否在mInFlightRequests存在,如果存在这加入到对应key的BatchedImageRequest批处理请求中。其实mInFlightRequests的作用还是为了处理重复的uri请求。有前面知道ImageRequest两个listener 当期请求完成有mDelivery分发请求,最后由listener处理最后的显示,而在ImageLoader中在Listener的onResponse和onErrorResponse中会调用onGetImageSuccess和onGetImageError函数在这两个函数中会
BatchedImageRequest request =mInFlightRequests.remove(cacheKey); batchResponse(cacheKey, request);
从mInFlightRequests取出相同key的请求,然后调用batchResponse函数直接把结果通过listen设置上对应的图片。
mRequestQueue.add(newRequest);
这个函数我们前面已经详细分析过了。其实ImageLoader只是对mRequestQueue封装。方便用户更简单的使用。在这个方面Volley相比UIL去做需要的ImageLoaderConfiguration和DisplayImageOptions要更简洁,当然,我们也可以使用UIL默认的配置。
mInFlightRequests.put(cacheKey, newBatchedImageRequest(newRequest, imageContainer)); return imageContainer; }
Volley加载图片的第三种方式:NetworkImageView
networkImageView= (NetworkImageView) findViewById(R.id.network_image_view); networkImageView.setDefaultImageResId(R.drawable.default_image); networkImageView.setErrorImageResId(R.drawable.failed_image); networkImageView.setImageUrl("图片下载地址",imageLoader);
其实NetworkImageView就是继承ImageView然后封装了ImageLoader。也就是说它内部还是调用了第二种加载方式中ImageLoader.get,在ImageListener的onResponse函数中直接调用Imageview 的接口根据设置加载结果。看似简单的这个NetworkImageView背后其实隐藏着让人深思的问题。什么问题呢?来看看networkImageView.setImageUrl函数。
public void setImageUrl(String url,ImageLoader imageLoader) { mUrl = url; mImageLoader = imageLoader; loadImageIfNecessary(false); }
这个函数重点在于loadImageIfNecessary(false);对这个函数做完简化处理。
void loadImageIfNecessary(final booleanisInLayoutPass) { // if the URL to be loaded in this viewis empty, cancel any old requests and clear the // currently loaded image. if (TextUtils.isEmpty(mUrl)) { if (mImageContainer != null) { mImageContainer.cancelRequest(); mImageContainer = null; } setDefaultImageOrNull(); return; }
这个就是如果当前下载Url为空,mImageContainer不为空,表示什么?这个Imageview之前有一个下载图片的请求,现在需要用新的URL而这个值有为空,那我们就取消掉之前的,然后把现在的赋值为空。
// if there was an old request in thisview, check if it needs to be canceled. if (mImageContainer != null &&mImageContainer.getRequestUrl() != null) { if(mImageContainer.getRequestUrl().equals(mUrl)) { // if the request is from thesame URL, return. return; } else { // if there is a pre-existingrequest, cancel it if it's fetching a different URL. mImageContainer.cancelRequest(); setDefaultImageOrNull(); } }
如果运行到这里表示新的URL不为空,那么我们判断一下新的URL是否和之前的一样,一样就返回了,不一样,这取消掉之前的下载请求。
// The pre-existing content of thisview didn't match the current URL. Load the new image // from the network. ImageContainer newContainer =mImageLoader.get(mUrl,...); 然后的然后,就调用mImageLoader.get,到这里大家就都知道在干吗了。。。。 // update the ImageContainer to be thenew bitmap container. mImageContainer = newContainer;
最后把新的newContainer 保存起来。
}
到这里,函数已经分析完了,用过UIL的都知道,它完成了,在ListView滑动中ImageView 重用时,取消之前Task的执行,从而避免下载不需要的图片,另外在ListView快速滑动过程中,我们可以监听并设置ImageLoaderEngine.pause 让其它还未执行下载的task阻塞。其实我开始说的值得深思的问题。
回头再看看如果Imageview当前新的URL与之前要加载的图片URL不一样,我们会调用mImageContainer.cancelRequest看他是如何取消的呢?
这个函数最重要的一个调用就是BatchedImageRequest. removeContainerAndCancelIfNecessary()而在这个函数中,有调用了ImageRequest.cancel(),前面分析过ImageRequest是我们NetworkDispatcher和CacheDispatcher一个执行的单位,这两个线程会从队列mNetworkQueue和mCacheQueue取出这个请求(ImageRequest)先判断是否已经取消,如果没有取消,那么就开始执行下载,如果取消了,就返回取下一个请求。原来是这样,那快速滑动怎么解决呢?其实,还是mImageContainer.cancelReques这个函数,试想一下如果listView快速滑动,那么Imageview必然会很快重用,很快被重用带来的是之前的请求很快就会被取消。那么NetworkDispatcher线程如果正在忙,等他忙完了,从mNetworkQueue取出来的ImageRequest可能就已经被取消了。如果不忙,就让它在快速滑动的时候请求一张图片也未尝不可嘛!
到此,我对volley和UIL简单的比较分析做完了。我尽量做到看完每一个类控制部分的数据和使用部分的数据,不放过我能发现的可以比较的细节,但是我知道还有许多我没有发现的东西。如果遗漏的地方请大神们多多指点!
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories