Android Universal Image Loader 算是Android中最流行的图片加载库了,作者Sergey的确牛逼,能将整个Android图片加载的点点滴滴考虑的如此全面。网上研究这个开源库的人很多比如这个:http://codekk.com/open-source-project-analysis, 我就不做全面介绍了,感兴趣的朋友可以进入这个网站查看关于UIL库的全面介绍,这个网站上还有很多其他著名Android开源项目的分析,有兴趣的朋友可以慢慢研究哦。我今天就从UIL库的代码出发结合实际经验,发掘出一些关于Android图片加载需要注意的事情。

转载请注明出处:http://blog.csdn.net/qinjunni2014/article/details/45298989

1、如何解决同时请求重复url时的资源浪费

很多朋友肯定遇到过一些问题,比如在同时加载相同的图片时,效率会比你想象的低,尤其是在这个url还是一个错误的url的时候,很有可能你的整个app图片加载层就死掉了,这个因为你的加载引擎同时发起了多个任务,这些任务都指向了一个错误的url,在获取错误的url图片时,任务就会效率非常低。本人觉得这是图片加载最基本的问题,对于重复的url,我们应该直接忽略掉后面的请求。我们来看看UIL库是如何处理的。

class ImageLoaderEngine {//对某个url执行load任务时,先要获得url对应的ReentrantLock锁private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();}

ImageloaderEngine是整个UIL处理任务的引擎,不熟悉的朋友可以上面提到的链接。可以看到engine中有一个map,key是image的url,value是一个ReentrantLock类型的锁,接下来的事情就很明了了,大家肯定会想到,如果要处理某个url,先要获得这个锁对不对?

//LoadAndDisplayImageTask.java
@Override
public void run() {if (waitIfPaused()) return;if (delayIfNeed()) return;ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);if (loadFromUriLock.isLocked()) {L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);}loadFromUriLock.lock();Bitmap bmp;try {//...获取bitmap         } finally {loadFromUriLock.unlock();}
}

LoadAndDisplayImageTask是UIL中执行获取bitmap的任务类型,继承自Runnable对象,可以看到在执行真正的获取bitmap操作前,我们首先试图锁住loadFromUriLock锁,这个锁就是从engine中获取的。对象同的url只有一个对应的lock,如果已经有任务在执行,lock()操作就会失败,线程就会处于休眠状态。

相关讨论:http://angeldevil.me/2015/03/19/a-bug-in-universal-image-loader/

2、如何解决类似ListView情况下view可以被重用的情况

这个可能是图片加载中最常被考虑的问题了,因为ListView广泛被使用,类似的还有GridView,RecyclerView。因为view可以被重用,而图片加载往往又是异步的,因此如果不加特殊考虑,会出现图片加载不对或者图片闪烁 的情况,通常比较简单地做法就是对ImageView执行settag,把url设为imageview的tag,这样url能保持最新,在图片加载任务返回时,先检查tag是否匹配,如果相等就把bitmap画到Imageview中去。原理比较简单,不过我们还是先看看UIL是怎么做的,先上代码:

private boolean isViewReused() {String currentCacheKey = engine.getLoadingUriForView(imageAware);// Check whether memory cache key (image URI) for current ImageAware is actual.// If ImageAware is reused for another task then current task should be cancelled.boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey);if (imageAwareWasReused) {L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);return true;}return false;
}

这个函数也位于LoadAndDisplayImageTask中,用于检测view时候已经被重用,这个函数首先会去从engine中去获取ImageAware的对应的uri(ImageAware可以暂且理解为imageview的包装,加上一些属性操作,读者如果不懂可以先阅读之前那个连接,或者直接读源码),其实这个和settag类似,但是这中方法更安全,如何安全?

//ImageLoaderEngine.java
private final Map<Integer, String> cacheKeysForImageAwares = Collections.synchronizedMap(new HashMap<Integer, String>());

以上是engine中对cacheKeysForImageAwares的定义,利用synchronizedMap,保证对cacheKey的并发安全。
有了这个isViewReused函数,我们就可以根据view时候已经被重用做出措施了。

其实手上有源码的童鞋,可以注意到LoadAndDisplayImageTask的run方法中,经常会去调用checkTaskNotActual函数,

private void checkTaskNotActual() throws TaskCancelledException {checkViewCollected();//检测view时候已经被回收checkViewReused();//检测view时候已经被重用
}

看名字就能理解了,这个函数会去检测view的有效性,如果不可用,就会抛出TaskCancelledException。run函数会捕捉这个异常做取消操作。

3、如何处理ListView滚动加载

第三个问题还是跟ListView相关,讨论在ListView进行滚动的时候,我们需要做一些处理,使得图片加载暂停,不然如果用户不停的做fling操作的话,我们需要不停的去加载一些可能最终不会呈现给用户的图片,这样会使得效率变得更低,同时也会更耗电。当然UIL也考虑到了这些。

public class PauseOnScrollListener implements OnScrollListener {private ImageLoader imageLoader;private final boolean pauseOnScroll;private final boolean pauseOnFling;private final OnScrollListener externalListener;@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {switch (scrollState) {case OnScrollListener.SCROLL_STATE_IDLE:imageLoader.resume();break;case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:if (pauseOnScroll) {imageLoader.pause();}break;case OnScrollListener.SCROLL_STATE_FLING:if (pauseOnFling) {imageLoader.pause();}break;}if (externalListener != null) {externalListener.onScrollStateChanged(view, scrollState);}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {if (externalListener != null) {externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);}}
}

UIL有这个PauseOnScrollListener,这个类继承自AbsListView.OnScrollListener,顾名思义,就是在scroll的时候暂停图片的加载。因此我们只需要在ListView或者GridView上set这个类的对象,在OnScrollListener的onScrollStateChanged函数中,我们可以根据ListView的状态去决定是否要暂停ImageLoader。另外这个类的构造函数可以再传一个OnScrollListener对象,这样就可以做额外的scroll监听操作。

4、如何处理同一张图片不同尺寸的内存缓存

有经验的朋友一定遇到过这个问题,因为我们的内存缓存肯定是downScale过的bitmap,而对于同一个image对不同大小的imageView我们会downscale成不同的尺寸,是否需要在memorycache中存储多张不同尺寸的缓存呢?对于一张图片,如果只存储大尺寸的bitmap缓存,那会很占内存,memorycache总量一定的情况下,可缓存的图片较少,而只存储小尺寸的图片的话,对于大尺寸的imageview,进行放大处理后会显示模糊。那么UIL是怎么做的呢?

其实UIL默认是会对一张图片存储不同的尺寸的缓存的,因为首先它的memorycache的key中不仅包含uri而且还包括目标尺寸大小,比如 “http://example.com/a.jpg_500x400“, 那么对于不同的目标大小,会有不同的cachekey,如果不能hitcache,就会从硬盘或者网络解码出这个尺寸的bitmap,这样的话,在memorycache中对同一张图片就有可能存在多种不同尺寸的缓存。当然这对缓存的空间会照成比较大得浪费,不过UIL可以关掉这种机制,在ImageLoaderConfiguration(用来配置UIL的全局配置)中,有一个函数叫做denyCacheImageMultipleSizesInMemory, 从名字就可以看出,如果设置了这个配置,memorycache中就不会存储一张图的多尺寸缓存。

public class FuzzyKeyMemoryCache implements MemoryCache {private final MemoryCache cache;private final Comparator<String> keyComparator;public FuzzyKeyMemoryCache(MemoryCache cache, Comparator<String> keyComparator) {this.cache = cache;this.keyComparator = keyComparator;}@Overridepublic boolean put(String key, Bitmap value) {// Search equal key and remove this entrysynchronized (cache) {String keyToRemove = null;for (String cacheKey : cache.keys()) {if (keyComparator.compare(key, cacheKey) == 0) {keyToRemove = cacheKey;break;}}if (keyToRemove != null) {cache.remove(keyToRemove);}}return cache.put(key, value);}@Overridepublic Bitmap get(String key) {return cache.get(key);}
}

MemoryCache是一个interface它规定了get,put,remove等接口函数,而FuzzyKeyMemoryCache是一个装饰类,它内部的keyComparator是一个模糊匹配比较,只需要图片的url相同就认为相等。值得注意的是,只有在put操作时做了模糊匹配,在get时并没有,这意味着,对于同一张图片,如果目标大小和缓存中得大小不一致,UIL依然会去从网络或者disk解码图片。在加入新的尺寸时,老的尺寸会被替换掉。个人觉得还是如果只是个别图片有不同尺寸的显示尺寸的话,可以开启这个配置。

5、如何解决url重定向问题

相信大部分人在自己写图片加载时,并没有考虑到这个小问题。如果图片的url并没有直接给出,而是给出了一个可重定向的url,那么http请求会返回3xx的状态码,表示url重定向,并在头部中包含Location,它的值就是重定向的目标地址。来看UIL时如何处理的:

int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {conn = createConnection(conn.getHeaderField("Location"), extra);redirectCount++;
}

它用一个循环来处理,如果返回3xx,就拿到新的地址,重新发起连接,不过最大不能超过MAX_REDIRECT_COUNT次。

6、如何处理硬件加速bitmap尺寸限制

可能对Tablet上Android开发有经验的朋友都会遇到过,图片加载成功了但是却没法显示出来的问题,而且最诡异的是没有error,令你没法调试,但是仔细查看log时,会发现有这么一条warning: Bitmap too large to be uploaded into a texture,主要原因是在做硬件加速渲染时,openGL渲染的texture对bitmap的大小是有一定限制的,很多是2048x2048,还有一些是4096x4096,取决于你的手机硬件水平还有openGL的版本。在阅读UIL源码时,发现这个问题作者也是有解决的,请参考一下代码:

public final class ImageSizeUtils {private static final int DEFAULT_MAX_BITMAP_DIMENSION = 2048;private static ImageSize maxBitmapSize;static {int[] maxTextureSize = new int[1];GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);int maxBitmapDimension = Math.max(maxTextureSize[0], DEFAULT_MAX_BITMAP_DIMENSION);maxBitmapSize = new ImageSize(maxBitmapDimension, maxBitmapDimension);}/*** Computes minimal sample size for downscaling image so result image size won't exceed max acceptable OpenGL* texture size.<br />* We can't create Bitmap in memory with size exceed max texture size (usually this is 2048x2048) so this method* calculate minimal sample size which should be applied to image to fit into these limits.** @param srcSize Original image size* @return Minimal sample size*/public static int computeMinImageSampleSize(ImageSize srcSize) {final int srcWidth = srcSize.getWidth();final int srcHeight = srcSize.getHeight();final int targetWidth = maxBitmapSize.getWidth();final int targetHeight = maxBitmapSize.getHeight();final int widthScale = (int) Math.ceil((float) srcWidth / targetWidth);final int heightScale = (int) Math.ceil((float) srcHeight / targetHeight);return Math.max(widthScale, heightScale); // max}
}

位于static代码块中代码就是为了获取当前设备支持的最大texture size。不过我在实际开发中注意到这段代码只有在主线程中去调用才能获取到正确地值,否则为0,不知道是否正确,请各位读者谨慎使用,不过Sergey也做了保险起见,规定最小至少能支持2048。在获取了这个值之后,我们就能通过这个maxBitmapSize来计算我们在解码bitmap时需要采取的sampleSize,保证我们解码出来的bitmap不会超过这个限制。不过默认情况下,UIL并没有开启这种计算sampleSize的模式。UIL中一个枚举类型ImageScaleType,不同的scaleType规定了计算sampleSize的不同方式。

public enum ImageScaleType {/** Image won't be scaled */NONE,/*** Image will be scaled down only if image size is greater than maximum acceptable texture size}.* Usually it's 2048x2048.*/NONE_SAFE,/*** Image will be reduces 2-fold until next reduce step make image smaller target size.<br />*/IN_SAMPLE_POWER_OF_2,/*** Image will be subsampled in an integer number of times (1, 2, 3, ...). Use it if memory economy is quite*/IN_SAMPLE_INT,/*** Image will scaled-down exactly to target size (scaled width or height or both will be equal to target size;*/EXACTLY,/*** Image will scaled exactly to target size (scaled width or height or both will be equal to target size; depends on* {@linkplain android.widget.ImageView.ScaleType ImageView's scale type}). Use it if memory economy is critically* important.<br />* <b>Note:</b> If original image size is smaller than target size then original image <b>will be stretched*/EXACTLY_STRETCHED
}

UIL默认会采用IN_SAMPLE_POWER_OF_2,这种计算sampleSize的方式,不过读者可以通过更改DisplayImageOptions来手动改成NON_SAFE,这样UIL解码出来的bitmap大小就不会超过texture的大小上限。

7、在慢网络中下载图片

这个问题说来惭愧,本人并没有在实际开发中碰到过,说的是在网络比较慢时下载图片会出现6060这个issue: https://code.google.com/p/android/issues/detail?id=6066,总的来说基本原因是因为输入流的skip操作不完全所致,issue中也给出了解决方法,Sergey就是采取了这种做法,用户可以调用ImageLoader的handleSlowNetwork方法开启慢网络模式,在这种模式下,下载的操作由SlowNetworkImageDownloader完成

private static class SlowNetworkImageDownloader implements ImageDownloader {private final ImageDownloader wrappedDownloader;public SlowNetworkImageDownloader(ImageDownloader wrappedDownloader) {this.wrappedDownloader = wrappedDownloader;}@Overridepublic InputStream getStream(String imageUri, Object extra) throws IOException {InputStream imageStream = wrappedDownloader.getStream(imageUri, extra);switch (Scheme.ofUri(imageUri)) {case HTTP:case HTTPS:return new FlushedInputStream(imageStream);default:return imageStream;}}
}

可以看到这个类也是个装饰类,它在普通的ImageDownloader外面包装了一层,它返回的输入流也是在ImageDownloader的输入流的基础上包装。

public class FlushedInputStream extends FilterInputStream {public FlushedInputStream(InputStream inputStream) {super(inputStream);}@Overridepublic long skip(long n) throws IOException {long totalBytesSkipped = 0L;while (totalBytesSkipped < n) {long bytesSkipped = in.skip(n - totalBytesSkipped);if (bytesSkipped == 0L) {int by_te = read();if (by_te < 0) {break; // we reached EOF} else {bytesSkipped = 1; // we read one byte}}totalBytesSkipped += bytesSkipped;}return totalBytesSkipped;}
}

这个类重写了skip操作,不断尝试skip直到遇到EOF或者skip完指定字符数。有大神对这个issue十分了解的,别忘了评论哦。

好了,先写到这里,基本上UIL为我们考虑到了图片所有应该考虑的事情,这里没有提到的比如Memorycache, DiskCache,BitmapDisplayer等,UIL都进行了完美的实现,有兴趣的童鞋可以直接阅读源码,难度不大,我在这里只是列举了一些典型的问题,让大家在写自己的图片加载库时有一个很好的参考。基本上UIL已经是一个很成熟的库了,再次感谢作者Sergey!!再会。

从UIL库谈Android图片加载中需要注意的事情相关推荐

  1. 【解决方案】Android图片加载中drawable等图片资源报错,提示找不到该资源

    情景描述: 在调用drawable文件中的图片资源时,偶尔会遇到图片资源报错的问题,明明在preview中都能显示,一旦编译就报错,报错提示如下: error: Error: No resource  ...

  2. fackbook的Fresco (FaceBook推出的Android图片加载库-Fresco)

    [Android开发经验]FaceBook推出的Android图片加载库-Fresco 欢迎关注ndroid-tech-frontier开源项目,定期翻译国外Android优质的技术.开源库.软件架构 ...

  3. android图片资源加载失败,Android图片加载问题分析

    下图是一个客户端图片加载模块常见的处理流程. imagepipeline.png 本文以UniversalImageLoader为例分析了这一流程,然后分析了Fresco的优势和问题,最终推荐大家使用 ...

  4. Android 图片加载框架Coil使用总结

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/122040645 本文出自[赵彦军的博客] 文章目录 简介 简单使用 高斯模糊 圆角 ...

  5. Android图片加载框架最全解析(八),带你全面了解Glide 4的用法

    本文转载自郭神的Glide分析系列:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二 ...

  6. Android图片加载框架 Glide 4 的用法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  7. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  8. Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/78357251 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  9. Android图片加载框架

    这篇文章主要和大家一起动手编写Android图片加载框架,从内部原理到具体实现来详细介绍如何开发一个简洁而实用的Android图片加载缓存框架,感兴趣的小伙伴们可以参考一下 开发一个简洁而实用的And ...

最新文章

  1. python成绩统计_python学习-统计学生成绩-统计学生成绩
  2. 你也许只使用到了 VS Code 20% 的功能
  3. centos7-同步时间
  4. wxPython笔记
  5. 分布式系统关注点:无状态
  6. nosql介绍、memrcached介绍、安装memcached、查看memcachedq状态
  7. java高并发(三)并发编程的基础
  8. 【简便解法】1083 是否存在相等的差 (20分)_14行代码AC
  9. 一步步带你实现简版 ButterKnife
  10. Apple任意代码执行漏洞
  11. smarty模板引擎(一)基础知识
  12. HWDB数据集gnt格式转为png格式
  13. stm32f4有重映射么_STM32 端口复用重映射(USART Remap)
  14. linux的input命令,通过xinput命令在Manjaro中启用Tap-to-click功能的方法
  15. Address localhost:8080 is already in use
  16. Python模块查询
  17. PhpStorm 正则 小写变大写
  18. MySQL索引原理理解
  19. 22考研基础阶段计划;超七成考研人已开始复习!
  20. 分享上海seo统计的seo基础知识

热门文章

  1. 美团上交开源PromptDet:无需标注,开放世界的目标检测器
  2. Android 修改系统屏幕亮度
  3. 研究生查分方式-查分时间大汇总-文都管联院
  4. 38掌握分布式存储系统 GlusterFS 的基本用法,包括卷管理、数据复制
  5. SQL Server数据库-表
  6. 简单概括 文明进化的各个阶段 (39)
  7. 古筝d调变降e调怎么办_古筝转调方法_古筝怎么转调
  8. Linux烤机脚本测试io,sipp测试脚本用于媒体测试
  9. vb.net图书管理系统
  10. 802.11 NDP Sounding