软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> 移动开发 -> Android -> 正文阅读

[移动开发]Android


转载请注明出处: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的工作过程。
在看之前我们先看一下官方的这张图片,它代表着所有条件下的执行流程:
[img]http://img.blog.csdn.net/20150601103053119?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTczMzAyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
图片给出的加载过程分别对应三种情况:
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 主要有三个包组成 
[img]http://img.blog.csdn.net/20160403125731886?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
① cache:管理缓存  ②core:下载的核心 ③utils:一些辅助工具。
utils 不用管,剩下的两部分就是整个项目的精髓: 下载展示 和缓存。
我们这里先看一下cache:
[img]http://img.blog.csdn.net/20160403130134372?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
也是由两部分组成:磁盘和内存。

DiskCache(本地缓存)


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

MemoryCache(内存缓存)


[img]http://img.blog.csdn.net/20160403153149619?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
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 中保存。


总结


前面看上去比较不好理解,第一遍看可能会觉得很乱,这里在总结一下加载的过程:
 [img]http://img.blog.csdn.net/20160403194633748?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。

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

......显示全文...
    点击查看全文


上一篇文章      下一篇文章      查看所有文章
2016-04-04 00:13:10  
移动开发 最新文章
深入了解android中的消息机制Handler
Android
Libgdx之BitmapFont字体
AndroidApp发布到应用市场的流程
Android开发找工作之前先看看这些知识点吧
View的事件分发机制解析
简单介绍了解白鹭引擎Egret
Cocos2d
android获取本地图片(二)
动画特效七:碰撞动画
360图书馆 软件开发资料 文字转语音 购物精选 软件下载 美食菜谱 新闻资讯 电影视频 小游戏 Chinese Culture 股票 租车
生肖星座 三丰软件 视频 开发 短信 中国文化 网文精选 搜图网 美图 阅读网 多播 租车 短信 看图 日历 万年历 2017年9日历
2017-9-24 16:47:49
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --