Android Universal Image Loader 源码分析(三)

来源:互联网 发布:游戏编程教程视频 编辑:程序博客网 时间:2024/05/23 01:25

4.2 核心类功能介绍

4.2.1 ImageLoader.java

       图片加载器,对外的主要 API,采取了单例模式,用于图片的加载和显示。

主要函数:

(1). getInstance()

       得到ImageLoader的单例。通过双层是否为 null 判断提高性能。

(2). init(ImageLoaderConfiguration configuration)

        初始化配置参数,参数configurationImageLoader的配置信息,包括图片最大尺寸、任务线程池、磁盘缓存、下载器、解码器等等。
实现中会初始化ImageLoaderEngine engine属性,该属性为任务分发器。

(3). displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

加载并显示图片或加载并执行回调接口。ImageLoader 加载图片主要分为三类接口:

  • displayImage(…) 表示异步加载并显示图片到对应的ImageAware上。
  • loadImage(…) 表示异步加载图片并执行回调接口。
  • loadImageSync(…) 表示同步加载图片。

以上三类接口最终都会调用到这个函数进行图片加载。函数参数解释如下:
uri: 图片的 uri。uri 支持多种来源的图片,包括 http、https、file、content、assets、drawable 及自定义,具体介绍可见ImageDownloader
imageAware: 一个接口,表示需要加载图片的对象,可包装 View。
options: 图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在内存缓存等。
listener: 图片加载各种时刻的回调接口,包括开始加载、加载失败、加载成功、取消加载四个时刻的回调函数。
progressListener: 图片加载进度的回调接口。

函数流程图如下:

4.2.2 ImageLoaderConfiguration.java

ImageLoader的配置信息,包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

主要属性:

(1). Resources resources

程序本地资源访问器,用于加载DisplayImageOptions中设置的一些 App 中图片资源。

(2). int maxImageWidthForMemoryCache

内存缓存的图片最大宽度。

(3). int maxImageHeightForMemoryCache

内存缓存的图片最大高度。

(4). int maxImageWidthForDiskCache

磁盘缓存的图片最大宽度。

(5). int maxImageHeightForDiskCache

磁盘缓存的图片最大高度。

(6). BitmapProcessor processorForDiskCache

图片处理器,用于处理从磁盘缓存中读取到的图片。

(7). Executor taskExecutor

ImageLoaderEngine中用于执行从源获取图片任务的 Executor。

(18). Executor taskExecutorForCachedImages

ImageLoaderEngine中用于执行从缓存获取图片任务的 Executor。

(19). boolean customExecutor

用户是否自定义了上面的 taskExecutor。

(20). boolean customExecutorForCachedImages

用户是否自定义了上面的 taskExecutorForCachedImages。

(21). int threadPoolSize

上面两个默认线程池的核心池大小,即最大并发数。

(22). int threadPriority

上面两个默认线程池的线程优先级。

(23). QueueProcessingType tasksProcessingType

上面两个默认线程池的线程队列类型。目前只有 FIFO, LIFO 两种可供选择。

(24). MemoryCache memoryCache

图片内存缓存。

(25). DiskCache diskCache

图片磁盘缓存,一般放在 SD 卡。

(26). ImageDownloader downloader

图片下载器。

(27). ImageDecoder decoder

图片解码器,内部可使用我们常用的BitmapFactory.decode(…)将图片资源解码成Bitmap对象。

(28). DisplayImageOptions defaultDisplayImageOptions

图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在内存缓存等。

(29). ImageDownloader networkDeniedDownloader

不允许访问网络的图片下载器。

(30). ImageDownloader slowNetworkDownloader

慢网络情况下的图片下载器。

4.2.3 ImageLoaderConfiguration.Builder.java 静态内部类

Builder 模式,用于构造参数繁多的ImageLoaderConfiguration
其属性与ImageLoaderConfiguration类似,函数多是属性设置函数。

主要函数及含义:

(1). build()

按照配置,生成 ImageLoaderConfiguration。代码如下:

public ImageLoaderConfiguration build() {    initEmptyFieldsWithDefaultValues();    return new ImageLoaderConfiguration(this);}
(2). initEmptyFieldsWithDefaultValues()

初始化值为null的属性。若用户没有配置相关项,UIL 会通过调用DefaultConfigurationFactory中的函数返回一个默认值当配置。
taskExecutorForCachedImagestaskExecutorImageLoaderEnginetaskDistributor的默认值如下:

parameterstaskDistributortaskExecutorForCachedImages/taskExecutorcorePoolSize03maximumPoolSizeInteger.MAX_VALUE3keepAliveTime600unitSECONDSMILLISECONDSworkQueueSynchronousQueueLIFOLinkedBlockingDeque / LinkedBlockingQueuepriority53

diskCacheFileNameGenerator默认值为HashCodeFileNameGenerator
memoryCache默认值为LruMemoryCache。如果内存缓存不允许缓存一张图片的多个尺寸,则用FuzzyKeyMemoryCache做封装,同一个图片新的尺寸会覆盖缓存中该图片老的尺寸。
diskCache默认值与diskCacheSizediskCacheFileCount值有关,如果他们有一个大于 0,则默认为LruDiskCache,否则使用无大小限制的UnlimitedDiskCache
downloader默认值为BaseImageDownloader
decoder默认值为BaseImageDecoder
详细及其他属性默认值请到DefaultConfigurationFactory中查看。

(3). denyCacheImageMultipleSizesInMemory()

设置内存缓存不允许缓存一张图片的多个尺寸,默认允许。
后面会讲到 View 的 getWidth() 在初始化前后的不同值与这个设置的关系。

(4). diskCacheSize(int maxCacheSize)

设置磁盘缓存的最大字节数,如果大于 0 或者下面的maxFileCount大于 0,默认的DiskCache会用LruDiskCache,否则使用无大小限制的UnlimitedDiskCache

(5). diskCacheFileCount(int maxFileCount)

设置磁盘缓存文件夹下最大文件数,如果大于 0 或者上面的maxCacheSize大于 0,默认的DiskCache会用LruDiskCache,否则使用无大小限制的UnlimitedDiskCache

4.2.4 ImageLoaderConfiguration.NetworkDeniedImageDownloader.java 静态内部类

不允许访问网络的图片下载器,实现了ImageDownloader接口。
实现也比较简单,包装一个ImageDownloader对象,通过在 getStream(…) 函数中禁止 Http 和 Https Scheme 禁止网络访问,如下:

@Overridepublic InputStream getStream(String imageUri, Object extra) throws IOException {    switch (Scheme.ofUri(imageUri)) {        case HTTP:        case HTTPS:            throw new IllegalStateException();        default:            return wrappedDownloader.getStream(imageUri, extra);    }}
4.2.5 ImageLoaderConfiguration.SlowNetworkImageDownloader.java 静态内部类

慢网络情况下的图片下载器,实现了ImageDownloader接口。
通过包装一个ImageDownloader对象实现,在 getStream(…) 函数中当 Scheme 为 Http 和 Https 时,用FlushedInputStream代替InputStream处理慢网络情况,具体见后面FlushedInputStream的介绍。

4.2.6 ImageLoaderEngine.java

LoadAndDisplayImageTaskProcessAndDisplayImageTask任务分发器,负责分发任务给具体的线程池。

主要属性:

(1). ImageLoaderConfiguration configuration

ImageLoader的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

(2). Executor taskExecutor

用于执行从源获取图片任务的 Executor,为configuration中的 taskExecutor,如果为null,则会调用DefaultConfigurationFactory.createExecutor(…)根据配置返回一个默认的线程池。

(3). Executor taskExecutorForCachedImages

用于执行从缓存获取图片任务的 Executor,为configuration中的 taskExecutorForCachedImages,如果为null,则会调用DefaultConfigurationFactory.createExecutor(…)根据配置返回一个默认的线程池。

(4). Executor taskDistributor

任务分发线程池,任务指LoadAndDisplayImageTaskProcessAndDisplayImageTask,因为只需要分发给上面的两个 Executor 去执行任务,不存在较耗时或阻塞操作,所以用无并发数(Int 最大值)限制的线程池即可。

(5). Map cacheKeysForImageAwares

ImageAware与内存缓存 key 对应的 map,key 为ImageAware的 id,value 为内存缓存的 key。

(6). Map uriLocks

图片正在加载的重入锁 map,key 为图片的 uri,value 为标识其正在加载的重入锁。

(7). AtomicBoolean paused

是否被暂停。如果为true,则所有新的加载或显示任务都会等待直到取消暂停(为false)。

(8). AtomicBoolean networkDenied

是否不允许访问网络,如果为true,通过ImageLoadingListener.onLoadingFailed(…)获取图片,则所有不在缓存中需要网络访问的请求都会失败,返回失败原因为网络访问被禁止

(9). AtomicBoolean slowNetwork

是否是慢网络情况,如果为true,则自动调用SlowNetworkImageDownloader下载图片。

(10). Object pauseLock

暂停的等待锁,可在engine被暂停后调用这个锁等待。

主要函数:

(1). void submit(final LoadAndDisplayImageTask task)

添加一个LoadAndDisplayImageTask。直接用taskDistributor执行一个 Runnable,在 Runnable 内部根据图片是否被磁盘缓存过确定使用taskExecutorForCachedImages还是taskExecutor执行该 task。

(2). void submit(ProcessAndDisplayImageTask task)

添加一个ProcessAndDisplayImageTask。直接用taskExecutorForCachedImages执行该 task。

(3). void pause()

暂停图片加载任务。所有新的加载或显示任务都会等待直到取消暂停(为false)。

(4). void resume()

继续图片加载任务。

(5). stop()

暂停所有加载和显示图片任务并清除这里的内部属性值。

(6). fireCallback(Runnable r)

taskDistributor立即执行某个任务。

(7). getLockForUri(String uri)

得到某个 uri 的重入锁,如果不存在则新建。

(8). createTaskExecutor()

调用DefaultConfigurationFactory.createExecutor(…)创建一个线程池。

(9). getLoadingUriForView(ImageAware imageAware)

得到某个imageAware正在加载的图片 uri。

(10). prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey)

准备开始一个Task。向cacheKeysForImageAwares中插入ImageAware的 id 和图片在内存缓存中的 key。

(11). void cancelDisplayTaskFor(ImageAware imageAware)

取消一个显示任务。从cacheKeysForImageAwares中删除ImageAware对应元素。

(12). denyNetworkDownloads(boolean denyNetworkDownloads)

设置是否不允许网络访问。

(13). handleSlowNetwork(boolean handleSlowNetwork)

设置是否慢网络情况。

4.2.7 DefaultConfigurationFactory.java

ImageLoaderConfigurationImageLoaderEngine提供一些默认配置。

主要函数:

(1). createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType)

创建线程池。
threadPoolSize表示核心池大小(最大并发数)。
threadPriority表示线程优先级。
tasksProcessingType表示线程队列类型,目前只有 FIFO, LIFO 两种可供选择。
内部实现会调用createThreadFactory(…)返回一个支持线程优先级设置,并且以固定规则命名新建的线程的线程工厂类DefaultConfigurationFactory.DefaultThreadFactory

(2). createTaskDistributor()

ImageLoaderEngine中的任务分发器taskDistributor提供线程池,该线程池为 normal 优先级的无并发大小限制的线程池。

(3). createFileNameGenerator()

返回一个HashCodeFileNameGenerator对象,即以 uri HashCode 为文件名的文件名生成器。

(4). createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, long diskCacheSize, int diskCacheFileCount)

创建一个 Disk Cache。如果 diskCacheSize 或者 diskCacheFileCount 大于 0,返回一个LruDiskCache,否则返回无大小限制的UnlimitedDiskCache

(5). createMemoryCache(Context context, int memoryCacheSize)

创建一个 Memory Cache。返回一个LruMemoryCache,若 memoryCacheSize 为 0,则设置该内存缓存的最大字节数为 App 最大可用内存的 1/8。
这里 App 的最大可用内存也支持系统在 Honeycomb 之后(ApiLevel >= 11) application 中android:largeHeap="true"的设置。

(6). createImageDownloader(Context context)

创建图片下载器,返回一个BaseImageDownloader

(7). createImageDecoder(boolean loggingEnabled)

创建图片解码器,返回一个BaseImageDecoder

(8). createBitmapDisplayer()

创建图片显示器,返回一个SimpleBitmapDisplayer

4.2.8 DefaultConfigurationFactory.DefaultThreadFactory

默认的线程工厂类,为
DefaultConfigurationFactory.createExecutor(…)

DefaultConfigurationFactory.createTaskDistributor(…)
提供线程工厂。支持线程优先级设置,并且以固定规则命名新建的线程。

PS:重命名线程是个很好的习惯,它的一大作用就是方便问题排查,比如性能优化,用 TraceView 查看线程,根据名字很容易分辨各个线程。

4.2.9 ImageAware.java

需要显示图片的对象的接口,可包装 View 表示某个需要显示图片的 View。

主要函数:

(1). View getWrappedView()

得到被包装的 View,图片在该 View 上显示。

(2). getWidth() 与 getHeight()

得到宽度高度,在计算图片缩放比例时会用到。

(3). getId()

得到唯一标识 id。ImageLoaderEngine中用这个 id 标识正在加载图片的ImageAware和图片内存缓存 key 的对应关系,图片请求前会将内存缓存 key 与新的内存缓存 key 进行比较,如果不相等,则之前的图片请求会被取消。这样当ImageAware被复用时就不会因异步加载(前面任务未取消)而造成错乱了。

4.2.10 ViewAware.java

封装 Android View 来显示图片的抽象类,实现了ImageAware接口,利用Reference来 Warp View 防止内存泄露。

主要函数:

(1). ViewAware(View view, boolean checkActualViewSize)

构造函数。
view表示需要显示图片的对象。
checkActualViewSize表示通过getWidth()getHeight()获取图片宽高时返回真实的宽和高,还是LayoutParams的宽高,true 表示返回真实宽和高。
如果为true会导致一个问题,View在还没有初始化完成时加载图片,这时它的真实宽高为 0,会取它LayoutParams的宽高,而图片缓存的 key 与这个宽高有关,所以当View初始化完成再次需要加载该图片时,getWidth()getHeight()返回的宽高都已经变化,缓存 key 不一样,从而导致缓存命中失败会再次从网络下载一次图片。可通过ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory()设置不允许内存缓存缓存一张图片的多个尺寸。

(2). setImageDrawable(Drawable drawable)

如果当前操作在主线程并且 View 没有被回收,则调用抽象函数setImageDrawableInto(Drawable drawable, View view)去向View设置图片。

(3). setImageBitmap(Bitmap bitmap)

如果当前操作在主线程并且 View 没有被回收,则调用抽象函数setImageBitmapInto(Bitmap bitmap, View view)去向View设置图片。

4.2.11 ImageViewAware.java

封装 Android ImageView 来显示图片的ImageAware,继承了ViewAware,利用Reference来 Warp View 防止内存泄露。
如果getWidth()函数小于等于 0,会利用反射获取mMaxWidth的值作为宽。
如果getHeight()函数小于等于 0,会利用反射获取mMaxHeight的值作为高。

4.2.12 NonViewAware.java

仅包含处理图片相关信息却没有需要显示图片的 View 的ImageAware,实现了ImageAware接口。常用于加载图片后调用回调接口而不是显示的情况。

4.2.13 DisplayImageOptions.java

图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在 memory 缓存等。

主要属性及含义:

(1). int imageResOnLoading

图片正在加载中的占位图片的 resource id,优先级比下面的imageOnLoading高,当存在时,imageOnLoading不起作用。

(2). int imageResForEmptyUri

空 uri 时的占位图片的 resource id,优先级比下面的imageForEmptyUri高,当存在时,imageForEmptyUri不起作用。

(3). int imageResOnFail

加载失败时的占位图片的 resource id,优先级比下面的imageOnFail高,当存在时,imageOnFail不起作用。

(4). Drawable imageOnLoading

加载中的占位图片的 drawabled 对象,默认为 null。

(5). Drawable imageForEmptyUri

空 uri 时的占位图片的 drawabled 对象,默认为 null。

(6). Drawable imageOnFail

加载失败时的占位图片的 drawabled 对象,默认为 null。

(7). boolean resetViewBeforeLoading

在加载前是否重置 view,通过 Builder 构建的对象默认为 false。

(8). boolean cacheInMemory

是否缓存在内存中,通过 Builder 构建的对象默认为 false。

(9). boolean cacheOnDisk

是否缓存在磁盘中,通过 Builder 构建的对象默认为 false。

(10). ImageScaleType imageScaleType

图片的缩放类型,通过 Builder 构建的对象默认为IN_SAMPLE_POWER_OF_2

(11). Options decodingOptions;

为 BitmapFactory.Options,用于BitmapFactory.decodeStream(imageStream, null, decodingOptions)得到图片尺寸等信息。

(12). int delayBeforeLoading

设置在开始加载前的延迟时间,单位为毫秒,通过 Builder 构建的对象默认为 0。

(13). boolean considerExifParams

是否考虑图片的 EXIF 信息,通过 Builder 构建的对象默认为 false。

(14). Object extraForDownloader

下载器需要的辅助信息。下载时传入ImageDownloader.getStream(String, Object)的对象,方便用户自己扩展,默认为 null。

(15). BitmapProcessor preProcessor

缓存在内存之前的处理程序,默认为 null。

(16). BitmapProcessor postProcessor

缓存在内存之后的处理程序,默认为 null。

(17). BitmapDisplayer displayer

图片的显示方式,通过 Builder 构建的对象默认为SimpleBitmapDisplayer

(18). Handler handler

handler 对象,默认为 null。

(19). boolean isSyncLoading

是否同步加载,通过 Builder 构建的对象默认为 false。

4.2.14 DisplayImageOptions.Builder.java 静态内部类

Builder 模式,用于构造参数繁多的DisplayImageOptions
其属性与DisplayImageOptions类似,函数多是属性设置函数。

4.2.15 ImageLoadingListener.java

图片加载各种时刻的回调接口,可在图片加载的某些点做监听。
包括开始加载(onLoadingStarted)、加载失败(onLoadingFailed)、加载成功(onLoadingComplete)、取消加载(onLoadingCancelled)四个回调函数。

4.2.16 SimpleImageLoadingListener.java

实现ImageLoadingListener接口,不过各个函数都是空实现,表示不在 Image 加载过程中做任何回调监听。
ImageLoader.displayImage(…)函数中当入参listener为空时的默认值。

4.2.17 ImageLoadingProgressListener.java

Image 加载进度的回调接口。其中抽象函数

void onProgressUpdate(String imageUri, View view, int current, int total)

会在获取图片存储到文件系统时被回调。其中total表示图片总大小,为网络请求结果Response Headercontent-length字段,如果不存在则为 -1。

4.2.18 DisplayBitmapTask.java

显示图片的Task,实现了Runnable接口,必须在主线程调用。

主要函数:

(1) run()

首先判断imageAware是否被 GC 回收,如果是直接调用取消加载回调接口ImageLoadingListener.onLoadingCancelled(…)
否则判断imageAware是否被复用,如果是直接调用取消加载回调接口ImageLoadingListener.onLoadingCancelled(…)
否则调用displayer显示图片,并将imageAware从正在加载的 map 中移除。调用加载成功回调接口ImageLoadingListener.onLoadingComplete(…)

对于 ListView 或是 GridView 这类会缓存 Item 的 View 来说,单个 Item 中如果含有 ImageView,在滑动过程中可能因为异步加载及 View 复用导致图片错乱,这里对imageAware是否被复用的判断就能很好的解决这个问题。原因类似:Android ListView 滑动过程中图片显示重复错位闪烁问题原因及解决方案。

4.2.19 ProcessAndDisplayImageTask.java

处理并显示图片的Task,实现了Runnable接口。

主要函数:

(1) run()

主要通过 imageLoadingInfo 得到BitmapProcessor处理图片,并用处理后的图片和配置新建一个DisplayBitmapTaskImageAware中显示图片。

4.2.20 LoadAndDisplayImageTask.java

加载并显示图片的Task,实现了Runnable接口,用于从网络、文件系统或内存获取图片并解析,然后调用DisplayBitmapTaskImageAware中显示图片。

主要函数:

(1) run()

获取图片并显示,核心代码如下:

bmp = configuration.memoryCache.get(memoryCacheKey);if (bmp == null || bmp.isRecycled()) {    bmp = tryLoadBitmap();    ...    ...    ...    if (bmp != null && options.isCacheInMemory()) {        L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);        configuration.memoryCache.put(memoryCacheKey, bmp);    }}……DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);runTask(displayBitmapTask, syncLoading, handler, engine);

从上面代码段中可以看到先是从内存缓存中去读取 bitmap 对象,若 bitmap 对象不存在,则调用 tryLoadBitmap() 函数获取 bitmap 对象,获取成功后若在 DisplayImageOptions.Builder 中设置了 cacheInMemory(true), 同时将 bitmap 对象缓存到内存中。
最后新建DisplayBitmapTask显示图片。

函数流程图如下:

  1. 判断图片的内存缓存是否存在,若存在直接执行步骤 8;
  2. 判断图片的磁盘缓存是否存在,若存在直接执行步骤 5;
  3. 从网络上下载图片;
  4. 将图片缓存在磁盘上;
  5. 将图片 decode 成 bitmap 对象;
  6. 根据DisplayImageOptions配置对图片进行预处理(Pre-process Bitmap);
  7. 将 bitmap 对象缓存到内存中;
  8. 根据DisplayImageOptions配置对图片进行后处理(Post-process Bitmap);
  9. 执行DisplayBitmapTask将图片显示在相应的控件上。
    流程图可以参见3. 流程图
(2) tryLoadBitmap()

从磁盘缓存或网络获取图片,核心代码如下:

File imageFile = configuration.diskCache.get(uri);if (imageFile != null && imageFile.exists()) {    ...    bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));}if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {    ...    String imageUriForDecoding = uri;    if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {        imageFile = configuration.diskCache.get(uri);        if (imageFile != null) {            imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());        }    }    checkTaskNotActual();    bitmap = decodeImage(imageUriForDecoding);    ...}

首先根据 uri 看看磁盘中是不是已经缓存了这个文件,如果已经缓存,调用 decodeImage 函数,将图片文件 decode 成 bitmap 对象; 如果 bitmap 不合法或缓存文件不存在,判断是否需要缓存在磁盘,需要则调用tryCacheImageOnDisk()函数去下载并缓存图片到本地磁盘,再通过decodeImage(imageUri)函数将图片文件 decode 成 bitmap 对象,否则直接通过decodeImage(imageUriForDecoding)下载图片并解析。

(3) tryCacheImageOnDisk()

下载图片并存储在磁盘内,根据磁盘缓存图片最长宽高的配置处理图片。

    loaded = downloadImage();

主要就是这一句话,调用下载器下载并保存图片。
如果你在ImageLoaderConfiguration中还配置了maxImageWidthForDiskCache或者maxImageHeightForDiskCache,还会调用resizeAndSaveImage()函数,调整图片尺寸,并保存新的图片文件。

(4) downloadImage()

下载图片并存储在磁盘内。调用getDownloader()得到ImageDownloader去下载图片。

(4) resizeAndSaveImage(int maxWidth, int maxHeight)

从磁盘缓存中得到图片,重新设置大小及进行一些处理后保存。

(5) getDownloader()

根据ImageLoaderEngine配置得到下载器。
如果不允许访问网络,则使用不允许访问网络的图片下载器NetworkDeniedImageDownloader;如果是慢网络情况,则使用慢网络情况下的图片下载器SlowNetworkImageDownloader;否则直接使用ImageLoaderConfiguration中的downloader

0 0
原创粉丝点击