软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> 移动开发 -> Android -> 正文阅读
移动开发 最新文章
深入了解android中的消息机制Handler
Android
Libgdx之BitmapFont字体
AndroidApp发布到应用市场的流程
Android开发找工作之前先看看这些知识点吧
View的事件分发机制解析
简单介绍了解白鹭引擎Egret
Cocos2d
android获取本地图片(二)
动画特效七:碰撞动画

[移动开发]Android

  2016-04-04 00:13:23

转载请注明出处:http://blog.csdn.net/u011733020

前言:


在Android开发中,对于图片的加载可以说是个老生常谈的问题了,图片加载是一个比较坑的地方,处理不好,会有各种奇怪的问题,比如 加载导致界面卡顿,程序crash。
因此 如何高效的加载大量图片,以及如何加载大分辨率的图片到内存,是我们想要开发一款优质app时不得不去面对与解决的问题。
通常开发中,我们只有两种选择:① 使用开源框架  ②自己去实现处理图片的加载与缓存。
通常一开始让我们自己去写,我们会无从下手,因此先去分析一下开源的思路,对我们的成长很有必要。
目前使用频率较高的图片缓存框架有  Universal-Image-Loader、android-Volley、Picasso、Fresco和Glide五大Android开源组件。
首先排除android-Volley 孰优孰劣,后面再去验证,剩下的四种对于 图片加载缓存的思想,从大方向上应该是类似的
而Android-Universal-Image-Loader 作为一款比较经典的框架,从早期到现在一直都比较常见,这里就拿Android-Universal-Image-Loader 来看一下它对图片处理的思想,以帮助我们理解,以便于我们也能写出类似的框架。

正文:


一 工作流程


前面介绍了如何在我们的项目中使用Android-Universal-Image-Loader,本文看一下UIL的工作过程。
在看之前我们先看一下官方的这张图片,它代表着所有条件下的执行流程:

图片给出的加载过程分别对应三种情况:
1.当内存中有该 bitmap 时,直接显示。
2.当本地有该图片时,加载进内存,然后显示。
3. 内存本地都没有时,请求网络,下载到本地,接下来加载进内存,然后显示。
过程分析:
最终展示图片还是调用的 ImageLoader 这个类中的 display() 方法,那么我们就把注意力集中到ImageLoader 这个类上,看下display() 内部怎么实现的。
	public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
			ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
		// 首先检查初始化配置,configuration == null 抛出异常
		checkConfiguration();
		if (imageAware == null) {
			throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
		}
		if (listener == null) {
			listener = defaultListener;
		}
		if (options == null) {
			options = configuration.defaultDisplayImageOptions;
		}
		// 当  目标uri "" 时这种情况的处理
		if (TextUtils.isEmpty(uri)) {
			engine.cancelDisplayTaskFor(imageAware);
			listener.onLoadingStarted(uri, imageAware.getWrappedView());
			if (options.shouldShowImageForEmptyUri()) {
				imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
			} else {
				imageAware.setImageDrawable(null);
			}
			listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
			return;
		}
		// 根据  配置的大小与图片实际大小得出 图片尺寸
		ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
		String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
		engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

		listener.onLoadingStarted(uri, imageAware.getWrappedView());
		// 首先从内存中取,看是否加载过,有缓存直接用
		Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
		if (bmp != null && !bmp.isRecycled()) {
			L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

			if (options.shouldPostProcess()) {
				ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
						options, listener, progressListener, engine.getLockForUri(uri));
				ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
						defineHandler(options));
				if (options.isSyncLoading()) {
					displayTask.run();
				} else {
					engine.submit(displayTask);
				}
			} else {
				options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
				listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
			}
		} else {
			// 没有缓存
			if (options.shouldShowImageOnLoading()) {
				imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
			} else if (options.isResetViewBeforeLoading()) {
				imageAware.setImageDrawable(null);
			}
			
			ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
					options, listener, progressListener, engine.getLockForUri(uri));
			LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
					defineHandler(options));
			if (options.isSyncLoading()) {
				displayTask.run();
			} else {
				engine.submit(displayTask);
			}
		}
	}

可以看到这个方法还不长,大体流程也向前面图中描述的:
首先判断传入的目标url 是" ",如果空,是否配置了默认的图片,接下来重点在url 是合法的情况下,去加载bitmap,首先从内存中去取,看能否取到(如果前面加载到内存,并且缓存过,没有被移除,则可以取到),如果取到则直接展示就可以了。
如果没有在内存中取到,接下来执行LoadAndDisplayImageTask 这个任务,主要还是看run()方法的执行过程:
@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 {
			checkTaskNotActual();
			bmp = configuration.memoryCache.get(memoryCacheKey);
			if (bmp == null || bmp.isRecycled()) { 
				// cache 中没有,下载
				bmp = tryLoadBitmap();
				if (bmp == null) return; // listener callback already was fired

				checkTaskNotActual();
				checkTaskInterrupted();

				if (options.shouldPreProcess()) {
					L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
					bmp = options.getPreProcessor().process(bmp);
					if (bmp == null) {
						L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
					}
				}
				// 加入到内存的缓存
				if (bmp != null && options.isCacheInMemory()) {
					L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
				 //LruMemoryCache
					configuration.memoryCache.put(memoryCacheKey, bmp);
				}
			} else {
				loadedFrom = LoadedFrom.MEMORY_CACHE;
				L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
			}

			if (bmp != null && options.shouldPostProcess()) {
				L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
				bmp = options.getPostProcessor().process(bmp);
				if (bmp == null) {
					L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
				}
			}
			checkTaskNotActual();
			checkTaskInterrupted();
		} catch (TaskCancelledException e) {
			fireCancelEvent();
			return;
		} finally {
			loadFromUriLock.unlock();
		}

		DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
		runTask(displayBitmapTask, syncLoading, handler, engine);
	}

这里一开始进行了一些基本的判断,比如是否当前暂停加载,延时加载等情况。
接下来,因为设置到了下载读写等过程,所以加了 锁,保证线程安全,下载过程是在 上面的// cache 中没有,这个注释下面的tryLoadBitmap() 方法中进行的,这个方法中做了什么,我们一会在看,现在继续往下走,下载后拿到了bitmap,接着进行判断是否 把bitmap加入到内存中的缓存中。 最后在 DisplayBitmapTask 的run 方法中setImageBitmap设置为背景。
这就是大体工作流程,也是前面说的的 三种情况
1. 内存中有,直接显示。
2. 内存中没有 本地有,加载进内存并显示。
3 本地没有,网络下载,本地保存,加载进内存,显示。
接下来再看前面说的下载方法tryLoadBitmap(), 由于比较长,这里只看关键的 try代码块中的操作:
		// 尝试 本地文件中是否有缓存
			File imageFile = configuration.diskCache.get(uri);
			if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
				L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
				loadedFrom = LoadedFrom.DISC_CACHE;

				checkTaskNotActual();
				bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
			}
			// 本地也没有
			if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
				L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
				loadedFrom = LoadedFrom.NETWORK;

				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);

 就是前面的情况,先看本地文件,如果有,加载进内存,并显示。
 如果没有则 下载,首先判断是否允许保存到本地,如果允许则下载到本地,接下来通过bitmap = decodeImage(imageUriForDecoding);拿到目标bitmap ,并返回 用于显示。

二 缓存策略分析


通过上图,我们可以总结出 UIL采用的是 内存(memory cache)+本地(disk cache) 的两级缓存策略。
采用缓存的好处有以下几点:
1. 减少每次请求网络下载消耗的的流量。
2.复用直接从内存/本地中获取,提高了加载速度。
那么我们接下来看一下UIL 是采取哪些方式去缓存内存和本地文件的。
通过查看UIL的lib库我们可以看出,整个lib 主要有三个包组成 

① cache:管理缓存  ②core:下载的核心 ③utils:一些辅助工具。
utils 不用管,剩下的两部分就是整个项目的精髓: 下载展示 和缓存。
我们这里先看一下cache:

也是由两部分组成:磁盘和内存。

DiskCache(本地缓存)


disc 有两种cache类型:

第一类是是基于DiskLruCache的LruDiskCache, 第二类是基于BaseDiskCache的LimitedAgeDiskCache 和UnlimitedDiskCache 。
这两种的工作原理稍微复杂,在这里先不做介绍,有时间单独再专门开篇文章介绍。
这两种的相同点是都是将请求到的图片 inputStream写到本地文件中。不同点在鱼管理方式不同,
LruDiskCache是根据 size > maxSize || fileCount > maxFileCount || 或者存的数据超过2000条而自动去删除。
LimitedAgeDiskCache 是根据存入时间与当前时间差,是否大于过期时间 去判断是从新下载 还是重复利用。
UnlimitedDiskCache:这个就是不限制cache大小,只要disk 上有空间 就可以保存到本地。
以上三个都实现了DiskCache 这个接口,具体工作过程是 save get remove clear 等几个方法,类似于数据库的 curd 操作。

MemoryCache(内存缓存)



memory的缓存 的实现类比较多,都是实现了 MemoryCache 这个接口
public interface MemoryCache {
	/**
	 * Puts value into cache by key
	 *  根据Key将Value添加进缓存中
	 * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into
	 * cache
	 */
	boolean put(String key, Bitmap value);

	/** Returns value by key. If there is no value for key then null will be returned. */
	根据Key 取Value
	Bitmap get(String key);

	/** Removes item by key */
	根据Key移除对应的Value
	Bitmap remove(String key);

	/** Returns all keys of cache */
	返回所有的缓存Keys
	Collection<String> keys();

	/** Remove all items from cache */
	情况缓存的map
	void clear();
}

比较多,不一一说,拿比较常用的LruLimitedMemoryCache 说一下吧
	@Override
	public boolean put(String key, Bitmap value) {
		boolean putSuccessfully = false;
		// Try to add value to hard cache
		// 当前要存入的 size
		int valueSize = getSize(value);
		// 约定的最大size
		int sizeLimit = getSizeLimit();
		//当前存在的size 大小
		int curCacheSize = cacheSize.get();
		//如果当前没有满,存入
		if (valueSize < sizeLimit) {
			// 判断 存入后如果 超出了约定的 maxsize  则删除掉最早的那一条
			while (curCacheSize + valueSize > sizeLimit) {
				Bitmap removedValue = removeNext();
				if (hardCache.remove(removedValue)) {
					curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
				}
			}
			hardCache.add(value);
			cacheSize.addAndGet(valueSize);

			putSuccessfully = true;
		}
		// 如果过大,则不存入到上面的集合,则将value 先new WeakReference<Bitmap>(value)中,然后在加入Map<k v> 中
		// Add value to soft cache
		super.put(key, value);
		return putSuccessfully;
	}

注释的很详细, 首先判断大小可以加入List<Bitmap> hardCache 这样一个集合中, 如果可以则加入在判断 当前集合是否超出 设置的默认最大值,如果该图片不能加入到这个集合中,那么首先将value 添加到WeakReference<Bitmap>(value)中,然后将WeakReference 作为value 添加到另一个Map 中保存。


总结


前面看上去比较不好理解,第一遍看可能会觉得很乱,这里在总结一下加载的过程:
 

谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。

欢迎爱学习的小伙伴加群一起进步:230274309 。

上一篇文章      下一篇文章      查看所有文章
2016-04-04 00:13:10  
360图书馆 论文大全 母婴/育儿 软件开发资料 网页快照 文字转语音 购物精选 软件 美食菜谱 新闻中心 电影下载 小游戏 Chinese Culture
生肖星座解梦 三沣玩客 拍拍 视频 开发 Android开发 站长 古典小说 网文精选 搜图网 天下美图 中国文化英文 多播视频 装修知识库
2017-2-23 12:48:07
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --