整理自帖子:http://www.eoeandroid.com/thread-331669-1-1.htmlhtml
/** * 获取压缩后的图片 * @param res * @param resId * @param reqWidth 所需图片压缩尺寸最小宽度 * @param reqHeight 所需图片压缩尺寸最小高度 * @return */ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 首先不加载图片,仅获取图片尺寸 final BitmapFactory.Options options = new BitmapFactory.Options(); // 当inJustDecodeBounds设为true时,不会加载图片仅获取图片尺寸信息 options.inJustDecodeBounds = true; // 此时仅会将图片信息会保存至options对象内,decode方法不会返回bitmap对象 BitmapFactory.decodeResource(res, resId, options); // 计算压缩比例,如inSampleSize=4时,图片会压缩成原图的1/4 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 当inJustDecodeBounds设为false时,BitmapFactory.decode...就会返回图片对象了 options. inJustDecodeBounds = false; // 利用计算的比例值获取压缩后的图片对象 return BitmapFactory.decodeResource(res, resId, options); }
/** * 计算压缩比例值 * @param options 解析图片的配置信息 * @param reqWidth 所需图片压缩尺寸最小宽度 * @param reqHeight 所需图片压缩尺寸最小高度 * @return */ public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 保存图片原宽高值 final int height = options. outHeight; final int width = options. outWidth; // 初始化压缩比例为1 int inSampleSize = 1; // 当图片宽高值任何一个大于所需压缩图片宽高值时,进入循环计算系统 if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // 压缩比例值每次循环两倍增长, // 直到原图宽高值的一半除以压缩值后都~大于所需宽高值为止 while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
通常少许图片是不多出现OOM异常的,除非单张图片过大。那么就能够用第一节里面的方法了。
一般应用场景是listview列表加载多张图片,为了提升效率通常要缓存一部分图片,这样方便再次查看时能快速显示,不用从新下载图片。可是手机内存是颇有限的,当缓存的图片愈来愈多,即便单张图片不是很大,不过数量太多时仍然会出现OOM的状况了。前端
代码实现
软应用缓存池类型做为二级缓存:
java
<span style="font-family:Microsoft YaHei;">HashMap<String, SoftReference<Bitmap>> mSecondLevelCache = new HashMap<String, SoftReference<Bitmap>>();</span>
强引用做为一级缓存,为了实现删除最不经常使用对象,能够用LinkedHashMap<String,Bitmap>类。LinkedHashMap对象能够复写一个removeEldestEntry,这个方法就是用来处理删除最不经常使用对象逻辑的,按照以前的设计就能够这么写:android
<span style="font-family:Microsoft YaHei;"> final int MAX_CAPACITY = 10; // 一级缓存阈值 // 第一个参数是初始化大小 // 第二个参数0.75是加载因子为经验值 // 第三个参数true则表示按照最近访问量的高低排序,false则表示按照插入顺序排序 HashMap<String, Bitmap> mFirstLevelCache = new LinkedHashMap<String, Bitmap>( MAX_CAPACITY / 2, 0.75f, true) { // eldest 最老的对象,即移除的最不经常使用图片对象 // 返回值 true即移除该对象,false则是不移除 protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) { if (size() > MAX_CAPACITY) {// 当缓存池总大小超过阈值的时候,将老的值从一级缓存搬到二级缓存 mSecondLevelCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue())); return true; } return false; } };</span>
每次图片显示时即便用时,若是存在与缓存中,则先将对象从缓存中删除,而后从新添加到一级缓存中的最前端。
会有三种状况
1.若是图片是从一级缓存中取出来的,则至关于把对象移到了一级缓存池的最前端(至关于最近使用的一张图片)
2.若是图片是从二级缓存中取出来的,则会存到一级缓存池最前端并检测,若是超过阀值,则将最不经常使用的一个对象移动到二级缓存中
3.若是缓存中没有,那就网上下载图片,下载好之后保存至一级缓存中,一样再进行检测是否要移除一个对象至二级缓存中
-----------------------------------------------------------------------
git
摘取段对软引用的介绍
github
Avoid Soft References for Caching In practice, soft references areinefficient for caching. The runtime doesn't have enough information on whichreferences to clear and which to keep. Most fatally, it doesn't knowwhat to do when given the choice between clearing a soft reference and growingthe heap. The lack of information on the value to your application of each referencelimits the usefulness of soft references. References that are cleared too earlycause unnecessary work; those that are cleared too late waste memory. Most applications should use an android.util.LruCache instead of soft references.LruCache has an effective eviction policy and lets the user tune how muchmemory is allotted.
咱们要避免用软引用去处理缓存 。 在实践中,软引用在缓存的处理上是没有效率的。运行时没有足够的信息用于判断哪些引用要清理回收还有哪些要保存。最致命的,当同时面对清理软引用和增长堆内存两种选择时它不知道作什么。对于你应用的每个引用都缺少有价值的信息,这一点限制了软引用让它的可用性十分有限。过早清理回收的引用致使了无必要的工做; 而那些过晚清理掉的引用又浪费了内存。 大多数应用程序应该使用一个android.util.LruCache代替软引用。LruCache有一个有效的回收机制,让用户可以调整有多少内存分配。
@Override protected int sizeOf(String key, Bitmapvalue) { return value.getRowBytes() * value.getHeight(); }
主要介绍这三个框架,都挺有名的,其余的框架估计也差很少了
Android-Universal-Image-Loader
https://github.com/nostra13/Android-Universal-Image-Loader
ImageLoader
https://github.com/novoda/ImageLoader
Volley(综合框架,包含图片部分)
https://github.com/mcxiaoke/android-volley
扯淡时间,能够跳过这段。这些开源框架的源码仍是挺复杂的,本人技术有限,有部分分析不对的地方或者不足的地方但愿你们一块儿讨论,因为有大量的源代码分析,因此形成内容较多且比较杂乱,重点部分我会用红字或者加粗字体标出来,若是没有耐心所有看完的话能够挑部分重点看,能够跳过的部分我也会有说明,你们能够选择性阅读。其实框架的实现和咱们教程(一)(二)节的也差很少,只不过在此基础上进行了一些优化,核心部分是几乎没有区别的。
因为篇幅有限,暂时只介绍第一个框架(其余其实也都差很少),另外两个在后续教程中详细介绍
---------------------------------------------------------------------
首先介绍universal-image-loader(如下简称UIL),是github社区上star最多的一个项目,能够理解为点赞最多滴,应该是最有名的一个。国内不少知名软件都用它包括淘宝京东聚划算等等。框架其实都差很少,这里详细介绍下使用者最多的一个,以后再分析其余框架时就简单说明一些特性了,重复类似部分再也不赘述了。
使用比较简单,这个框架的github主页上也有快速使用的步骤。基本上就是在Application类里的onCreate方法(整个程序开始时运行一次)中进行一下简单的基本配置。能够根据须要自行进行设定,懒得设定的话框架也提供了一个默认的配置,调用一个方法便可
基本上是配置一些相似于:缓存类型啊,缓存上限值啊,加载图片的线程池数量啊等等。
此外在页面内显示的时候还要设置一个显示配置。这个配置不一样于基本配置,一个项目里能够根据须要建立多个配置对象使用,
这个配置就比较具体了,能够设置是否使用disk缓存(存到sd卡里通常),加载图片失败时显示的图片,默认图片,图片的色彩样式等
配置好之后,就是简单的使用了,建立一个图片加载对象,而后一行代码搞定显示图片功能。参数通常是入你须要显示的图片url和imageview对象。大部分框架其实都这一个尿性,配置稍微麻烦点,可是使用时通常只须要一行,显示方法通常会提供多个重载方法,支持不一样须要。
因为不是框架使用教程,因此下面结合以前两章的内容着重分析下框架对于单张图片的压缩处理,和多图缓存池的处理
---------------------------------------------------------------------
面试
protected Options prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo) { ImageScaleType scaleType = decodingInfo.getImageScaleType(); int scale; if (scaleType == ImageScaleType.NONE) { scale = ImageSizeUtils.computeMinImageSampleSize(imageSize); } else { ImageSize targetSize = decodingInfo.getTargetSize(); boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2; scale = ImageSizeUtils.computeImageSampleSize(imageSize, targetSize, decodingInfo.getViewScaleType(), powerOf2); } if (scale > 1 && loggingEnabled) { L.d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey()); } Options decodingOptions = decodingInfo.getDecodingOptions(); decodingOptions.inSampleSize = scale; return decodingOptions; }
简单扫一眼,ImageSize,ImageDecodingInfo神马的明显是自定义的一个类,不要管,咱们先挑重点部分看算法
Options decodingOptions =decodingInfo.getDecodingOptions(); decodingOptions.inSampleSize= scale;
方法最后两行能够看出来ImageDecodingInfo类里面保存了一个option对象,经过一个方法对其中的inSampleSize进行了设置。
ImageScaleType.NONE 什么意思,扫了眼注释,是图片无压缩。那咱们看else里面的须要压缩的computeImageSampleSize方法。方法是具体如何处理的呢?咱们再继续跟踪computeImageSampleSize方法。设计模式
(业界良心小技巧:按着ctrl不松左键点击方法或者变量或者类,就能够自动跳转到对应的地方了)
方法的代码以下缓存
/** * Computes sample size for downscaling image size (<b> srcSize</b> ) to * view size (<b>targetSize</b> ). This sample size is used during * {@linkplain BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options) * decoding image} to bitmap.<br /> * <br /> * <b>Examples: </b><br /> * <p/> * * <pre> * srcSize(100x100), targetSize(10x10), powerOf2Scale = true -> sampleSize = 8 * srcSize(100x100), targetSize(10x10), powerOf2Scale = false -> sampleSize = 10 * * srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> sampleSize = 5 * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> sampleSize = 2 * </pre> * <p/> * <br /> * The sample size is the number of pixels in either dimension that * correspond to a single pixel in the decoded bitmap. For example, * inSampleSize == 4 returns an image that is 1/4 the width/height of the * original, and 1/16 the number of pixels. Any value <= 1 is treated the * same as 1. * * @param srcSize * Original (image) size * @param targetSize * Target (view) size * @param viewScaleType * {@linkplain ViewScaleType Scale type} for placing image in * view * @param powerOf2Scale * <i>true </i> - if sample size be a power of 2 (1, 2, 4, 8, * ...) * @return Computed sample size */ public static int computeImageSampleSize(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType, boolean powerOf2Scale) { int srcWidth = srcSize.getWidth(); int srcHeight = srcSize.getHeight(); int targetWidth = targetSize.getWidth(); int targetHeight = targetSize.getHeight(); int scale = 1; int widthScale = srcWidth / targetWidth; int heightScale = srcHeight / targetHeight; switch (viewScaleType) { case FIT_INSIDE: if (powerOf2Scale) { while (srcWidth / 2 >= targetWidth || srcHeight / 2 >= targetHeight) { // || srcWidth /= 2; srcHeight /= 2; scale *= 2; } } else { scale = Math.max(widthScale, heightScale); // max } break; case CROP: if (powerOf2Scale) { while (srcWidth / 2 >= targetWidth && srcHeight / 2 >= targetHeight) { // && srcWidth /= 2; srcHeight /= 2; scale *= 2; } } else { scale = Math.min(widthScale, heightScale); // min } break; } if (scale < 1) { scale = 1; } return scale; }
我连注释一块儿复制过来了,里面对参数有比较详细的介绍,还有对应的例子~很良心的注释啊。
能够看到核心计算方法,其实和咱们教程(一)里面是同样的(区别在于这里有两个状况的处理,一个是||一个是&&,后面会介绍缘由),对源图的宽高和目标图片的宽高进行一些判断,知足时一直循环下去进行处理,直到取得一个合适的比例值为止,最终保证
使压缩后显示的图片像素密度是大于等于设定的像素密度的那个最低值。
(这里换了个更合适的说法,由于框架对图片不一样scaleType形成的不一样显示效果进行的区别处理,使得压缩比例值计算的更加精确,其实不用这个优化处理已经可以知足图片缩放显示需求了,这里是为了作到更好,固然也会更麻烦了,因此做为咱们学习者来讲要自行取舍要研究到哪一个深度更合适,后面有单独一部分进行介绍,能够选择性看)
框架进行了优化,添加了一个powerOf2Scale的参数和viewScaleType的参数区别不一样状况以进行响应处理,方法注释里面都有介绍参数的做用,我这里也简单说明下。
powerOf2Scale: 是否为2的幂数,也就是2.4.8.16.....
true时即咱们以前教程里面举得例子,也是官方推荐的,最好缩放比例是2的幂数(2的N次方),须要经过循环每次*2的计算压缩比例
false即不须要是2的幂数,能够是1.2.3.4...任何一个数,直接用图片宽高除以所需图片宽高便可得到压缩比例(注意要是整数)
因此,true的时候和咱们教程(一)中的算法一致,而false的时候不作限定,那就能够简单的直接进行除法操做了
viewScaleType: 就是android里控件imageView的scaleType属性,这里集合成了两种,对应关系见框架的源码,以下
public static ViewScaleType fromImageView(ImageView imageView) { switch (imageView.getScaleType()) { case FIT_CENTER: case FIT_XY: case FIT_START: case FIT_END: case CENTER_INSIDE: return FIT_INSIDE; case MATRIX: case CENTER: case CENTER_CROP: default: return CROP; } }
再次重复
第一章的计算方法掌握了就能够了,已经可以知足开发需求,下面这段是对于不一样viewScaleType的处理分析,能够跳过,有兴趣的话就接着看看
---------------------------------------------------------------------
本段可略过~
这部份内容仍是很绕的,我也是边研究边写,实例demo也一边跑一边debug看研究,基本上算是大概了解了。这一段反复修改了屡次,耗费了大量精力,脑子有点迷乱了都,因此可能有不太准确的地方但愿你们提醒下,我再作修改。
根据他的两种不一样算法(||和&&),咱们举个具体例子说明下。
好比咱们限定图片要200 200,原图是1000 600,控件则是100 100的正方形(单位都是像素)。
先看默认的FIT_INSIDE效果
第一次循环,1000/2 >= 200 || 600/2 >= 200 都知足, 源宽高变成一半 500 400,缩放值翻倍,变成2~
第二次循环,500/2 >= 200 || 300/2 >= 200 知足其中一个,源宽高变成一半 250 200,缩放值翻倍,变成4~
第三次循环,250/2 >= 200 || 150/2 >= 200 都不知足,结束循环,最后缩放值为 4
以缩放比例4计算,得到的缩放图片实例的宽高即为250 150
CROP效果时的处理
第一次循环,1000/2 >= 200 && 600/2 >= 200 都知足,源宽高变成一半 500 400,缩放值翻倍,变成2~
第二次循环,500/2 >= 200 && 300/2 >= 200 只知足其中一个,没有知足&&的条件,结束循环,最后缩放值是2~
以缩放比例2计算,得到的缩放图片实例的宽高即为500 300
这样看的话,250 150的结果没有知足宽高都大于等于限定值200 200的原则啊~
思考下压缩图片的原则,首先尽量的缩小,由于越小越节约内存,可是不能过小,由于压缩过大的图不够清楚,效果很差~跟看骑兵电影似得,因此要有个最低值~
好比个人imageview控件的宽高是100 100大小的一个正方形,那我但愿将显示的图片压缩成至少为200 200像素的大小,这样我控件的单位大小上均可以显示至少4个像素的图片点~
因此限定大小,其实是为了限定控件上图片的像素密度,也就是控件单位大小上图片像素点的数量~
对于控件正方形,图片几乎也是正方形的状况,那就简单了
好比一个500 500的图片和一个200 200的图片都放在100 100的imageview上~外观看上去的大小都是同样的,可是单位大小上的图片像素点数量是不一样的,500*500=250000,分布在100*100的控件上,那控件单位大小上就有25个图片像素点~
200 200的图片,图片像素密度则是4~ 确定是单位大小上图片像素点越多就越清楚了
为何要有区分处理呢,由于对于图片长宽比例和控件长宽比例相差很大时,不一样的scaleType形成的显示缩放效果区别是很大的~
在长宽比相同或者相近时,好比都是正方形,那么两种显示效果毫无区别,可是若是控件是正方形,图片是3:2甚至3:1的矩形,那差异就明显了, 咱们看下比例值差异较大时的
FIT_INSIDE的显示效果和CROP_INSIDE
两种显示效果简单的理解为
FIT_INSIDE(左图) 完整的展现图片,但imageView控件可能不占满
CROP(右图) 填充整个imageview,图片可能会被裁去一部分
对于上面的例子1000 600的图, 而图片限定大小是200200
若是两种效果都是用一种压缩规则算法,好比官方提供的那种规则(也是咱们教程一里面的),则会把图片压缩成了500 300~
分析下压缩后图片在两种scaleType下的缩放状况
FIT_INSIDE模式时
显示时候的缩放(不是图片像素的压缩)是根据较长的那个边缩放的(蓝色框框的上下两条长边),
500缩放成了100,缩放比例为5,那像素密度就是25了
CROP模式时
显示时候的缩放是根据较短的那个段边缩放的(蓝色框框的左右俩短边)
上例中是短边是300,缩放成了100,比例为3,那单位大小上图片像素点就是9了
预期大小是200 200,显示在100 100的控件上其实就至关于但愿单位大小上的图片像素数量4以上
那个25貌似是有点多了呢~
那咱们按照UIL框架里针对FIT_INSIDE的算法从新计算下,压缩后图片像素为250 150
缩放类型按照长边缩放,比例为2.5~单位大小上图片像素点是12.5~明显知足条件又更加合适~
那CROP缩放类型也按照UIL框架FIT_INSIDE对应的算法处理呢?
算出来是250 150,显示在CROP上,则是按照短边缩放,比例是1.5~最后像素密度是2.25,不到4,明显是不知足的了~
因此图片限定宽高大小,是为了保证图片的清晰度,实质上是要保证单位大小上有足够的像素点,即对像素密度有个最低值要求
根据长边缩放的FIT_INSIDE效果,咱们只须要保证长的边即其中一个大于等于限定数值便可
而根据短边缩放的CROP效果,要保证短的边大于等于限定数值即只有宽高都知足大于等于限定的数时才能够
因为大部分状况下,尤为是实际应用场景中,什么头像啊,logo啊,图片啊大部分其实都是控件和图片长宽比类似的,偶尔有几个奇葩图片也浪费不来多少内存的,因此通常没有作区分处理~UIL框架这种区别计算比例更加的准确,固然,同时也更加难了
---------------------------------------------------------------------
图片色彩样式
缩放比例的控制介绍完毕,基本上和咱们教程(一)中介绍的方法差很少(没羞没臊啊), 只不过进行了一些优化
而对于图片色彩样式的控制,则能够在框架提供的显示配置对象中设置
DisplayImageOptions.bitmapConfig(Bitmap.Config.RGB_565) 传入所需色样便可,默认一样是ARGB_8888
---------------------------------------------------------------------
多张图片的缓存池
单张图片的缩放处理其实仍是比较简单的(不考虑不一样缩放效果区别处理的话)~
重点在于多张图片的缓存池控制,下面进行介绍
首先是UIL的内存缓存
一共有八种内存缓存池类型,使用时只要选择其中的一个便可(经过ImageLoaderConfiguration.memoryCache(...)设置)
咱们看下UIL在github上的主页中对于各个内存缓存类型的具体介绍
(主页地址见文章最开始处)
灰色框中标注的七种
只用了强引用,是如今官方推荐的方法,至关于咱们教程(二)里面说的只用LruCache一个强引用缓存池的方式~也是框架的默认内存缓存方式
LruMemoryCache 当超过缓存数量的极限值时删除使用时间离得最远的图片(即LruCache的算法)
同时使用弱引用+强引用,也就是至关于教程(二)里面说的二级缓存技术,区别在于这里对其中的一级缓存即强引用缓存池部分的删除算法进行了细分,采用了不一样的规则,看下小括号后面的英文就知道了,我这里简单的翻译下
UsingFregLimitedMemoryCache 当超过缓存数量的极限值时删除最不经常使用的图片
LruLimitedMemoryCache 当超过缓存数量的极限值时删除使用时间离得最远的图片(即LruCache的算法)
FIFOLimitedMemoryCache 当超过缓存数量的极限值时根据FIFO first in first out先入先出算法删除
LargestLimitedMemoryCache 当超过缓存数量的极限值时删除最大的图片
LimitedAgeMemoryCache 当超过缓存数量的极限值时删除超过存在最长时间的那个图片(这个翻译有可能不太准确= = )
只使用弱引用 因为彻底没有使用强引用,因此确定不会出现OOM异常,可是效率上捉鸡~ UIL使用时基本上不多出现OOM异常的,真是人品爆发出现了,那解决办法之一就是将内存缓存设置成这个只用弱引用的类型,由于没有强引用部分~
WeakmemoryCache 无限制的内存(系统根据弱引用规则自动回收图片)
估计是框架很早之前就开始开发的问题,强引用部分用的是LinkedHashMap类,没有采用LruCache类,但实际上处理逻辑基本都是同样的~因为LinkedHashMap的功能稍微弱一点~教程二里面能够看到咱们是经过size()>阀值判断的,也就是仅根据数量而不是根据全部图片内存总大小,因此UIL框架中本身作了对应处理,在这个自定义个的强引用类型LruMemoryCache中设置了一个总内存size参数,每次put remove等操做时,都计算size的变化,而且在每次put操做时调用一个trimToSize方法,用于判断添加后的缓存池大小,观察size值是否超过了maxSize阀值,超过自定义的内存总大小值时则移除最老的图片
代码以下
package com.nostra13.universalimageloader.cache.memory.impl; import android.graphics.Bitmap; import com.nostra13.universalimageloader.cache.memory.MemoryCache; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; /** * A cache that holds strong references to a limited number of Bitmaps. Each * time a Bitmap is accessed, it is moved to the head of a queue. When a Bitmap * is added to a full cache, the Bitmap at the end of that queue is evicted and * may become eligible for garbage collection.<br /> * <br /> * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.8.1 */ public class LruMemoryCache implements MemoryCache { private final LinkedHashMap<String, Bitmap> map; private final int maxSize; /** Size of this cache in bytes */ private int size; /** * @param maxSize * Maximum sum of the sizes of the Bitmaps in this cache */ public LruMemoryCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true); } /** * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap * was returned, it is moved to the head of the queue. This returns null if * a Bitmap is not cached. */ @Override public final Bitmap get(String key) { if (key == null) { throw new NullPointerException("key == null"); } synchronized (this) { return map.get(key); } } /** * Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of * the queue. */ @Override public final boolean put(String key, Bitmap value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } synchronized (this) { size += sizeOf(key, value); Bitmap previous = map.put(key, value); if (previous != null) { size -= sizeOf(key, previous); } } trimToSize(maxSize); return true; } /** * Remove the eldest entries until the total of remaining entries is at or * below the requested size. * * @param maxSize * the maximum size of the cache before returning. May be -1 to * evict even 0-sized elements. */ private void trimToSize(int maxSize) { while (true) { String key; Bitmap value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator() .next(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= sizeOf(key, value); } } } /** Removes the entry for {@code key} if it exists. */ @Override public final void remove(String key) { if (key == null) { throw new NullPointerException("key == null"); } synchronized (this) { Bitmap previous = map.remove(key); if (previous != null) { size -= sizeOf(key, previous); } } } @Override public Collection<String> keys() { synchronized (this) { return new HashSet<String>(map.keySet()); } } @Override public void clear() { trimToSize(-1); // -1 will evict 0-sized elements } /** * Returns the size {@code Bitmap} in bytes. * <p/> * An entry's size must not change while it is in the cache. */ private int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override public synchronized final String toString() { return String.format("LruCache[maxSize=%d]", maxSize); } }
代码分析
核心方法是put() 和trimToSize()
put方法:
synchronized修饰是为了防止多线程访问时形成size计算错误的,sizeOf是自定义方法,计算图片size大小的,这里首先将添加的图片大小增至缓存池总大小size中。而后map.put方法添加图片对象,返回值是若是map里对应key已经有值了,则返回put前已有的那个对象~固然,put之后value已经被替换了,因此在+=新的图片对象大小后,还要将前一个被替换掉的图片size从总缓存池内存大小中减去,最后完成添加过程之后调用trimToSize方法查看添加操做完成后当前缓存池大小是否超过咱们自定义的阀值总大小maxSize
trimToSize方法:
无限循环直到当前缓存池大小size低于自定义阀值大小maxSize为止~ 若是没有知足size<=maxSize,即当前缓存池过大,则对map添加一个迭代器依次遍历整个map从始至末挨个删除,直到知足size<=maxSize结束循环~ 之因此用循环遍历,是由于可能不止删除一个图片,好比最先添加的图片只有5kb,而新图片有15kb,那只删除最先一张图片是不行的,可能添加进来一张须要挤出去多张。
每次put的时候hashMap都会将新对象添加至末端~key已存在时则替代对应的value对象并移到末端。而当迭代器遍历的时候则是从始端开始遍历的,也就等于个LRU的算法,删除使用时间距今最老的图片,则UIL框架自定义的这个强引用LRU缓存类,主要仍是对内存阀值size进行了处理和判断。
框架里的处理和LinkedHashMap的removeEldestEntry方法最终实现的效果都是同样的。只不过具体代码实现方式不一样而已
这里使用自定义方法而后进行处理,我的目测应该是为了支持框架多重缓存池类型而设计的,毕竟LinkedHashMap的removeEldestEntry仅提供删除最先使用对象的功能,而删除最大图片等这样其余的功能就不支持了。
以上是只用强引用的缓存池部分,还算简单,下面是二级缓存
二级缓存(弱+强)
这部分就凌乱了~因为UIL框架中有5个弱+强的具体实现类(详见上面的内存缓存介绍图),因此基本功能实现都是在基类中进行的处理,实现的子类中仅针对算法进行了自定义规则而已~ 因为代码涉及到多层多个类== 本人的水平又有限,因此...就不仔细研究了,可能后续有时间的时候再补充到帖子里。
大概思路是基类提供一个虚拟方法removeNext,返回bitmap值,而后在基类里put方法后判断强引用的size超过阀值时,对这个方法返回的bitmap对象删除操做,而后将其移至弱引用池里~ 具体移除规则则子类中自行编写,你还能够设计个按图片size单双数删除图片一类的特殊规则。
这就属于设计思想了,能够参考LinkedHashMap的removeEldestEntry源码,能够看到源码中只使用,而方法内则是空的,就是留给开发者自行继承重写逻辑的。
本身如如有兴趣开发框架,能够根据本身阶段性的水平来,开始的时候就直接用系统的LruCache写就能够了,二级缓存也很好处理。
注意:
咱们教程里使用的是软引用,这里是弱引用。二者其实也差很少,弱引用生命周期比软引用还低点。可是效率其实都通常,官方都不推荐使用了。
确定是要跟着官方权威信息走的,因此UIL框架的默认内存缓存方式用的也是LruMemoryCache类, 但同时又保留了其余类型的缓存类, 框架使用者能够根据实际须要在配置时经过ImageLoaderConfiguration.memoryCache(...)方法设置其余提供的类型缓存
关于软引用其余那几个类型算法FIFO等的具体实现,我这里就不研究了,算法神马的水平有限,你们有兴趣能够本身看看
此外还有一个FuzzyKeyMemoryCache的内存缓存类, UIL中缓存里是支持保存同一张图片的多种缩放比例缓存对象的,设置中也能够取消这种设置使得缓存池中一个图片只保留一个size的缓存对象,经过调用ImageLoader.denyCacheImageMultipleSizesInMemory()方法实现, 此时,UIL框架就会自动使用Fuzzy...去包装一下你以前选择的内存缓存类型。即算法仍是按照你选择的类型,可是多了一个只保持一种size缓存对象的功能,这种设计叫作Decorator装饰模式,举个例子帮助理解下,这种设计模式至关于有一个变相怪杰的特殊面具,不管人或狗,谁带上谁就能具备相应的牛逼能力。
算是一个特殊的内存缓存类型,不能单独使用。
即由高到低依次遍历是否能获取到值,得到后做为限定宽高值解析压缩后的图片实例对象
缓存池大小的设置~
分强引用和弱引用,弱引用咱们知道不用限制~主要是针对强引用部分缓存池的限制
教程(二)里面也提到过,主要分两种:限制图片总数量 和 限制所有图片所占内存大小
对于强引用大小的限定,咱们看下UIL框架的默认处理
/** * Creates default implementation of {@link MemoryCache} - * {@link LruMemoryCache}<br /> * Default cache size = 1/8 of available app memory. */ public static MemoryCache createMemoryCache(int memoryCacheSize) { if (memoryCacheSize == 0) { memoryCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8); } return new LruMemoryCache(memoryCacheSize); }
获取程序可用内存的1/8做为缓存池大小,也是推荐设置,有须要能够调用上面的方法传入>0的自定义size值便可设置。
限制图片总数量,恕我眼神拙计= = 源码中没找到默认设置,毕竟设置内存大小更合适点
若是本身项目中有特殊须要,能够参考教程(二)中的相关部分进行设置
此外还有disk缓存池,因为默认是无限制disk缓存类型,没有一些具体参数数值的设定了~
还有图片下载线程数量问题,disk图片取名问题等等,不是核心部分~这里篇幅有限就不介绍了
--------------------------------------------------------------------------------------------------------
以上,核心代码分析结束~都弄懂了的话,图片的处理已经算是小有所成了,好处嘛~
一方面在使用框架的时候你能够更了解内部的机制,适当时候能够根据项目具体须要作相关继承重写进行修改(最好不要直接修改开源框架源码,保证其完整性)
另外一方面更能够看成本身的一个优点点亮点,尤为在面试时多发挥发挥~好比介绍本身项目时,大部分网络数据交互的项目通常都是会有列表多图加载的状况的,你就能够就图片加载部分大谈特谈了~不过聊框架部分有风险,对于此方面知识匮乏的面试官来讲, 有时候不明也不必定会觉历,会白白浪费口水~最好结合项目一边演示一边谈,具体技巧这里就不班门弄斧了,能够网上找些帖子看
下面的内容一样是选择性观看
包括UIL框架的一些扩展功能,以及源码的结
--------------------------------------------------------------------------------------------------------
UIL框架在核心的图片缓存技术,图片压缩,异步线程下载图片的传统框架功能之外~
还作了一些其余的
拓展功能
自定义ImageView控件,叫作ImageAware,支持圆角效果等功能
disk保存图片文件名md5编码
图片加载的显示效果,好比淡入的fade效果等等
(此外还有一些扩展的功能,因为是非核心部分不作详细介绍了)
此外还提供Widget~ 这个我就没研究了
源码结构
主要分三大包~下面挨个介绍
UIL的缓存池部分包/类结构
见下图,很清楚的结构,cache缓存包下分红两个部分,disc(disk缓存)包和memory(内存缓存)包
绿色部分是基类父类~ impl是具体实现子类,好比memory的impl包下的那8个,就是咱们以前介绍的能够直接使用的不一样缓存池类型,项目中有特殊需求的话,也能够自定义一个类继承重写作对应的修改
disk同理,基类+具体实现类,在此基础上多了个naming命名包,是对图片保存文件名称进行md5编码解码处理的
核心包
其中着重研究红框部分
图片解析包decode
基本配置类ImageLoaderCofiguration
显示配置类DisplayImageOptions
和最重要的图片加载器类ImageLoder
其余部分
assist为助手包,里面大部分都是一些Enum枚举类,好比前面提到的图片缩放类型,图片压缩类型等
display图片显示包,框架提供圆角矩形的图片显示效果,以及加载图片时fade效果(从透明到实体慢慢浮现的感受)等
download图片下载包,篇幅有限此功能这里不作分析
imageaware自定义图片控件包,提供圆角的效果等
listener监听包,图片开始加载,正在加载,加载完毕等监听
process进程包,能够继承里面的接口自行定义图片加载过程当中的效果
其余的还有一些线程啊,默认配置文件生成类啊等等的,一样篇幅有限不作分析
工具包
提供一些所需功能,好比IO流处理,disk缓存的路径处理等
其中最重要的是红框所示的图片size工具类,好比前面提到的computeImageSampleSize计算缩放比例就是在这个类里
--------------------------------------------------------------------------------------------------------
完
写了大概将近一周,修改时间占了一大半,反反复复的改,也是由于我的技术有限,部分姿式也是边研究边写的,可能有不太详细或者错误的地方再次但愿你们提出来,多多指正或者共同探讨一下,也但愿你们能收藏一下,我以后也会继续润色或补充文章不足地方的
UIL功能强大是毋庸置疑的,代码框架也很清晰,文档也算齐全~ 可是对开发者尤为是我这样的初学者来讲一点点啃下来仍是很艰难的,最好先看教程一二,彻底懂了之后再看本篇,固然一二看懂基本上图片处理也差很少了
本篇至关于对以前教程作个验证 "你看,最有名的框架基本上也是这么处理嘛~"如此这般从侧面证明一下教程中方法的靠谱性~
其余做用呢? 经过钻研确定是提升咱们的技术了,学习别人的类结构设计~ 也能更好的使用图片框架(其余框架差很少都这样逻辑),且对于拓展部分也能够做为本身的亮点在面试中使用 ~
懒得复制+调整格式了,地址:http://www.eoeandroid.com/thread-334315-1-1.html
(五)图片处理效果对比