Universal-ImageLoader这是一个强大的图片下载处理的开源框架,由于他的功能强大且使用方便在应用开发中很受欢迎,早AdapterView<ListAdapter>中很常见被使用去异步加载图片,平时自己写的有时候会出现处理不严谨存在内存溢出的危险建议使用这个开源框架,主要来看看里面的源码详解。
1. ImageLoaderConfiguration 和 ImageLoaderConfiguration.Builder
这个ImageLoaderConfiguration.java文件用的是创建者设计模式 除了记下几行代码外其余的值通过Builder来赋值
// 图片下载类的实现
networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader); slowNetworkDownloader = new SlowNetworkImageDownloader(downloader); //定义在SD卡缓存目录 File reserveCacheDir = StorageUtils.getCacheDirectory(builder.context, false); reserveDiscCache = DefaultConfigurationFactory.createReserveDiscCache(reserveCacheDir);所以主要看Builder中的代码实现
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
<span style="white-space:pre"> </span>.threadPoolSize(3)// 开启的线程池个数 .tasksProcessingOrder(QueueProcessingType.LIFO)// 队列中采用的是先进先出
<span style="white-space:pre"> </span>.threadPriority(Thread.NORM_PRIORITY)// 线程优先级 .memoryCache(new LruMemoryCache(20 * 1024 * 1024))// 使用LrumemoryCache内存缓存方式
<span style="white-space:pre"> </span>.memoryCacheSize(20 * 1024 * 1024)<span style="font-family: Arial, Helvetica, sans-serif;">// 内存缓存空间大小</span> .discCacheSize(256 * 1024 * 1024)// SD卡缓存空间 .discCacheFileCount(10000)// SD卡最多缓存的图片张数
<span style="white-space:pre"> </span>.memoryCacheExtraOptions(480, 800).build();// 内存缓存最大的分辨率
ImageLoader.getInstance().init(config);我们这么简单定义之后就等于图片下载配置完成了。
2. 缓存方式浅出
采取经典的缓存方式 MemoryCache--->DiskCache--->NetWork: 先从MemoryCache里找Bitmap没有开启线程读取图片,这是判断是否在DiskCache下存在否则就是读取来自NetWork的图片,我们看下源码的写法
<span style="white-space:pre"> </span>bmp = configuration.memoryCache.get(memoryCacheKey);// 先从memoryCache中读取Bitmap if (bmp == null) { bmp = tryLoadBitmap();// 开启线程进行下载 if (bmp == null) return; // listener callback already was fired if (bmp != null && options.isCacheInMemory()) { log(LOG_CACHE_IMAGE_IN_MEMORY);
<span style="white-space:pre"> </span>// 下载成功存入内存中 configuration.memoryCache.put(memoryCacheKey, bmp); }在LoadAndDisplayImageTask的tryCacheImageOnDisc方法把下载好的图片存入到SD卡下,这样下次读取的时候就不需要再从netWork中去下载。
按上面的ImageLoaderConfiguration 配置我们的DisCache使用的缓存类文件是TotalSizeLimitedDiscCache.java实现LimitedDiscCache.java起到的作用是:当要加入到SD的时候DisCache的大小已经超出了设置的最大空间容量sizeLimit时候会把最先插入的File文件删除以便腾出空间来存放新来的File。
<span style="white-space:pre"> </span>Long oldestUsage = null;// 用于记住最先加入的文件key File mostLongUsedFile = null;<span style="font-family: Arial, Helvetica, sans-serif;">// 用于记住最先加入的文件</span> Set<Entry<File, Long>> entries = lastUsageDates.entrySet(); synchronized (lastUsageDates) { for (Entry<File, Long> entry : entries) { if (mostLongUsedFile == null) { mostLongUsedFile = entry.getKey(); oldestUsage = entry.getValue(); } else { Long lastValueUsage = entry.getValue(); if (lastValueUsage < oldestUsage) { oldestUsage = lastValueUsage; mostLongUsedFile = entry.getKey(); } } } } <span style="white-space:pre"> </span>// 从SD空间删除 int fileSize = 0; if (mostLongUsedFile != null) { if (mostLongUsedFile.exists()) { fileSize = getSize(mostLongUsedFile); if (mostLongUsedFile.delete()) { lastUsageDates.remove(mostLongUsedFile); } } else { lastUsageDates.remove(mostLongUsedFile); } }而我们使用的MemoryCache的是LruMemoryCache.java同样会为新来的Bitmap腾出空间如果空间不足的情况下,实现的方式都是略同的。在这个开源框架中的缓存可以按照时间,图片张数,空间大小来决定哪个key对应的value将被remove在空间不足的情况下。
3.下载过程的实现
<span style="white-space:pre"> </span>// 获取ImageView 的Width Height的值 ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
<span style="white-space:pre"> </span>// 经过处理后的key为xxxxxxxxxxxxxxxxxxxxxxxxxxx.png_480*320会已ImageView的大小做结尾 String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize); engine.prepareDisplayTaskFor(imageAware, memoryCacheKey); <span style="white-space:pre"> </span>// 准备开始 listener.onLoadingStarted(uri, imageAware.getWrappedView()); // 从内存里读取出来 Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); if (bmp != null && !bmp.isRecycled()) { if (configuration.writeLogs) L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey); <span style="white-space:pre"> </span>// 可以为图片下载显示当前的实时进度 if (options.shouldPostProcess()) { ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, engine.getLockForUri(uri)); ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo, options.getHandler()); if (options.isSyncLoading()) { displayTask.run(); } else { engine.submit(displayTask); } } else {// 如果在内存中存在就直接显示并完成 options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE); listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp); imageAware.getWrappedView().setTag(uri); } } else { if (options.shouldShowImageOnLoading()) { imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources)); } else if (options.isResetViewBeforeLoading()) { imageAware.setImageDrawable(null); } <span style="white-space:pre"> </span>// 如果不在内存中就会封装下载实现 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, engine.getLockForUri(uri)); LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, options.getHandler()); if (options.isSyncLoading()) { displayTask.run(); } else { engine.submit(displayTask); } }最后下载好之后需要进行压缩处理
<span style="white-space:pre"> </span>int srcWidth = srcSize.getWidth(); int srcHeight = srcSize.getHeight(); int targetWidth = targetSize.getWidth(); int targetHeight = targetSize.getHeight(); <span style="white-space:pre"> </span>// 获取压缩比例 float widthScale = (float) srcWidth / targetWidth; float heightScale = (float) srcHeight / targetHeight; int destWidth; int destHeight; if ((viewScaleType == ViewScaleType.FIT_INSIDE && widthScale >= heightScale) || (viewScaleType == ViewScaleType.CROP && widthScale < heightScale)) { destWidth = targetWidth; destHeight = (int) (srcHeight / widthScale); } else { destWidth = (int) (srcWidth / heightScale); destHeight = targetHeight; } float scale = 1; if ((!stretch && destWidth < srcWidth && destHeight < srcHeight) || (stretch && destWidth != srcWidth && destHeight != srcHeight)) { scale = (float) destWidth / srcWidth; }// 这是我再Adapter中的调用方法 就一句话很方便就显示出来