Android Bitmap 全面解析

整理自帖子:http://www.eoeandroid.com/thread-331669-1-1.htmlhtml


(一) 加载大尺寸图片

压缩缘由:

1.imageview大小若是是200*300那么加载个2000*3000的图片到内存中显然是浪费可耻滴行为;
2.最重要的是图片过大时直接加载原图会形成OOM异常(out of memory内存溢出)


因此通常对于大图咱们须要进行下压缩处理
权威处理方法参考 安卓开发者中心的大图片处理教程
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

主要处理思路是:
1.获取图片的像素宽高(不加载图片至内存中,因此不会占用资源)
2.计算须要压缩的比例
3.按将图片用计算出的比例压缩,并加载至内存中使用


官网大图片加载教程(上面网址里的)对应代码就是:
/**
* 获取压缩后的图片
* @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);
}
代码详解:
核心方法是BitmapFactory.decode...(...., options)
...的意思是此外还有一系列的decodeFile/decodeStream等等方法,都是利用options灵活解析获取图片,只不过解析图片的来源不一样罢了,好比网络图片获取,通常就是解析字节流信息而后decode获取图片实例

Options是图片配置信息,参数详细介绍下:

inJustDecodeBounds 是否只解析边界
     设为true时去decode获取图片,只会加载像素宽高信息
     设为false时decode则会彻底加载图片

inSampleSize 压缩比例
     好比原图200*300,若是值是2时会压缩成100*150; 是4则图片压缩成50*75
     最好是2的幂数,好比2 4 8 16 .....

outHeight  图片原高度
outWidth  图片原宽度

其余参数自行研究,这里暂时只用到这几个


decodeSampledBitmapFromResource方法内的三段代码对应上面的三步流程。难点在于中间那步,压缩比例的计算,官网一样提供了个calculateInSampleSize方法,其中reqWidth和reqHeight是所需图片限定最小宽高值
/**
* 计算压缩比例值
* @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;
}
利用此方法获取到所需压缩比例值,最终获取到压缩后的图片~

官网的这个方法是: 将图片一半一半的压缩,直到压缩成成 大于所需宽高数的那个最低值。  大于~不是大于等于 ,因此就会出现我上面那种状况,我以为方法不是太好= = 能知足压缩的需求,可是压缩的比例不够准确, 因此最好改为大于等于,以下(我的意见,仅供参考,在实际压缩中不多遇到恰巧等于的这个状况,因此>和>=差异也不大额~看我这扯扯淡就当对计算比例的逻辑加深个理解吧)

----------------------------------------------------------


以上,图片的像素大小已经作了缩放。可是图片的大小除了和像素有关。还和色彩样式有关。 不一样的样式决定了图片单个像素占的字节数
好比。图片默认的色彩样式为ARGB_8888。每一个像素占4byte(字节)大小
参考资料:http://developer.android.com/reference/android/graphics/Bitmap.Config.html

能够看到一共有四种色彩样式
ALPHA_8      每一个像素只要1字节~惋惜只能表明透明度,没有颜色属性
ARGB_4444    每一个像素要2字节~带透明度的颜色~惋惜官方不推荐使用了
ARGB_8888  每一个像素要4字节~带透明度的颜色, 默认色样
RGB_565    每一个像素要2字节~不带透明度的颜色
默认为ARGB_8888,若是想丧心病狂的继续减小图片所占大小。不须要透明度参数的话, 那就能够把色彩样式设为RGB_565

设置方法是在BitmapFactory.decode..获取图片事例时, 修改配置参数的inPreferredConfig 参数
opts. inPreferredConfig = Bitmap.Config. RGB_565 ;


----------------------------------------------------------

想亲自撸一撸试一试压缩图片了吧?
要注意点问题,若是用res包下图片测试的话,你会发现有图片尺寸有点混乱。那是由于在drawable-*dpi文件夹中的图片会根据对应的屏幕密度值不一样自动进行必定的缩放,好比放在drawable-hdpi里的图片,直接不通过压缩BitmapFactor.decode..出来,会发现bitmap的宽高值是原图的2/3。测试的时候图片记得放在drawable包下(没有的话本身res下新建一个),不然你会被奇怪的宽高值弄凌乱的。具体变化缘由参考源代码处理,或者网上搜搜看。

还有就是BitmapFactory.decodeStream方法会偶尔解析图片失败(好像是安卓低版本的一个bug)。
此时推荐作法是将流转换为字节流处理,而后利用decodeByteArray方法获取图片



(二)加载多张图片的缓存处理

通常少许图片是不多出现OOM异常的,除非单张图片过大。那么就能够用第一节里面的方法了。
一般应用场景是listview列表加载多张图片,为了提升效率通常要缓存一部分图片,这样方便再次查看时能快速显示,不用从新下载图片。可是手机内存是颇有限的,当缓存的图片愈来愈多,即便单张图片不是很大,不过数量太多时仍然会出现OOM的状况了。
前端

本篇则是讨论多张图片的处理问题

-----------------------------------------------------------------------

图片缓存的通常处理

1.创建一个图片缓存池,用于存放图片对应的bitmap对象。
2.在显示的时候,好比listview对应适配器的getView方法里进行加载图片的工做, 先从缓存池经过url的key值取,若是取到图片了直接显示,若是获取不到再创建异步线程去下载图片(下载好后同时保存至图片缓存池并显示)

可是缓存池不能无限大啊,否则就会异常了,因此一般咱们要对缓存池进行必定控制。
须要有两个特性: 
     1.总大小有个限制,否则里面存放无限多的图片时会内存溢出OOM异常
     2.当大小达到上限后,再添加图片时,须要线程池可以智能化的回收移除池内一部分图片,这样才能保证新图片的显示保存


异步线程下载图片神马的简单,网上异步下载任务的代码一大堆,下载之后流数据直接decode成bitmap图片便可。
难点在与这个图片缓存池的设计,如今网上的实现主要有两种
1.软引用/弱引用
2.LruCache

-----------------------------------------------------------------------

拓展: java中4种引用分类

官方资料链接:http://developer.android.com/reference/java/lang/ref/Reference.html
强引用
     日常使用的基本都是强引用,除非主动释放(图片的回收,或者==null赋值为空等),不然会一直保存对象到内存溢出为止~
软引用    SoftReference
     在系统内存不够时,会自动释放部分软引用所指对象~
弱引用    WeakReference
     系统偶尔回收扫描时发现弱引用则释放对象,即和内存够不够的状况无关,彻底看心情~
虚引用
     不用了解,其实我也不熟悉

框架基本都比较爱用这个软应用保存图片做为缓存池,这样在图片过多不足时,就会自动回收部分图片,防止OOM。可是有缺点,没法控制内存不足时会回收哪些图片,若是我只想回收一些不经常使用的,不要回收经常使用的图片呢?


因而引入了二级缓存的逻辑,即设置两个缓存池,一个强引用,一个软引用, 强引用保存经常使用图片,软应用保存其余图片。强引用由于不会自动释放对象,因此大小要进行必定限定,不然图片过多会异常, 好比控制里面只存放10张图片,而后每次往里面添加图片的时候,检查若是数量超过10张这个阀值,临界点值时,就移除强引用里面最不经常使用的那个图片,并将其保存至软应用缓存池中。


整个缓存既做为一个总体(一级二级缓存都是内存缓存~每次显示图片前都要检查整个缓存池中有没有图片)。又有必定的区分(只回收二级缓存软引用中图片,不回收一级缓存中强引用的图片)

 

代码实现
软应用缓存池类型做为二级缓存: 
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

Disk缓存

能够简单的理解为将图片缓存到sd卡中。

因为内存缓存在程序关闭第二次进入时就清空了,对于一个十分经常使用的图片好比头像一类的,咱们但愿不要每次进入应用都从新下载一遍,那就要用到disk缓存了,直接图片存到了本地,打开应用时直接获取显示。

网上获取图片的大部分逻辑顺序是:
内存缓存中获取显示(强引用缓存池->弱引用缓存池)  -> 内存中找不到时从sd卡缓存中获取显示 -> 缓存中都没有再创建异步线程下载图片,下载完成后保存至缓存中。

按照获取图片获取效率的速度,由快到慢的依次尝试几个方法

以文件的形式缓存到SD卡中,优势是SD卡容量较大,因此能够缓存不少图片,且屡次打开应用均可以使用缓存,缺点是文件读写操做会耗费一点时间。虽然速度没有从内存缓存中获取速度快,可是确定比从新下载一张图片的速度快,并且还不用每次都下载图片浪费流量。因此使用优先级就介于内存缓存和下载图片之间了。

注意:
sd卡缓存通常要提早进行一下是否装载sd卡的检测, 还要检测sd卡剩余容量是否够用的状况,程序里也要添加注明相应的权限

-----------------------------------------------------------------------

使用LruCache处理图片缓存


以上基本彻底掌握了,每一张图最好再进行一下第一节里面介绍的单张缩放处理,那基本整个图片缓存技术就差很少了。但随着android sdk的更新,新版本其实提供了更好的解决方案,下面介绍一下

先看老版本用的软引用官方文档
http://developer.android.com/reference/java/lang/ref/SoftReference.html


摘取段对软引用的介绍
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有一个有效的回收机制,让用户可以调整有多少内存分配。

简而言之,直接使用软引用缓存的话效果不咋滴,推荐使用LruCache。level12之后开始引入的,为了兼容更早版本,android-support-v4包内也添加了一个LruCache类,因此在12版本以上用的话发现有两个包内都有这个类,其实都是同样的。

那么这个类是作啥的呢~这里是官方文档
http://developer.android.com/reference/android/util/LruCache.html

LRU的意思是LeastRecently Used 即近期最少使用算法。眼熟吧,其实以前的二级缓存中的那个强引用LinkedHashMap的处理逻辑其实就是一个LRU算法,而咱们查看LruCache这个类的源代码时发现里面其实也有一个LinkedHashMap,大概扫了眼,逻辑和咱们以前本身写的差很少。核心功能基本都是: 当添加进去新数据且达到限制的阀值时,则移除一个最少使用的数据。

根据这个新的类作图片加载的话,网上大部分的作法仍是二级缓存处理。只不过将LinkedHashMap+软引用,替换成了LruCache+软引用。都是二级缓存,强引用+软引用的结构。由于LruCache和LinkedHashMap都是差很少的处理逻辑,没有移除软引用的使用,而是将二者结合了起来。

根据官网的介绍来看其实软引用效果不大,二级缓存的处理的话,虽然能提升一点效果,可是会浪费对内存的消耗,因此要不要加个软引用的二级缓存,具体选择就看本身理解和实际应用场景了吧。

LruCache我理解是牺牲一小部分效率,换取部份内存。我我的也是倾向于只使用LruCache的实现不用软引用了,也比较简单。

-----------------------------------------------------------------------

LruCache的具体用法


以前对LinkedHashMap有了必定了解了,其实LruCache也差很少,相似于removeEldestEntry方法的回收逻辑,在这个类里面已经处理好了,通常咱们只须要处理对阀值的控制就好了。

阀值控制的核心方法是sizeOf()方法, 该方法的意思是返回每个value对象的大小size,默认返回的是1。即当maxSize(经过构造方法传入)设为10的时候就至关于限制缓存池只保留10个对象了,和上面LinkedHashMap的例子一个意思。

可是因为图片的大小不一,通常限定全部图片的总大小更加合适,那咱们就能够对这个sizeOf方法进行复写
@Override
protected int sizeOf(String key, Bitmapvalue) {
     return value.getRowBytes() * value.getHeight();
}

这样的话,至关于缓存池里每个对象的大小都是计算它的字节数,则在新建LruCache的时候传入一个总size值就好了,通常传入应用可用内存的1/8大小

-----------------------------------------------------------------------

本篇是讨论对于图片数量的控制问题, 再结合第一节中的方法对每一张图片进行相应处理~OOM的状况基本就能够避免了~


-----------------------------------------------------------------------

(三)开源图片框架分析1-UIL

主要介绍这三个框架,都挺有名的,其余的框架估计也差很少了

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对象。大部分框架其实都这一个尿性,配置稍微麻烦点,可是使用时通常只须要一行,显示方法通常会提供多个重载方法,支持不一样须要。

因为不是框架使用教程,因此下面结合以前两章的内容着重分析下框架对于单张图片的压缩处理,和多图缓存池的处理
---------------------------------------------------------------------

面试

单张图片的压缩

(业界良心的小技巧: 框架确定也是基于android sdk的, 因此获取图片缩放实例的话,option的inSampleSize参数是确定要使用的, 咱们直接crtl+h打开搜索页面,选择file search, 而后file name patterns选择*.java,即搜索全部java文件,最后在containing text上输入想搜索的内容,这里咱们要搜inSampleSize,搜索结果里随便扫一扫,发现BaseImageDecoder里面有个靠谱的方法以下)

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装饰模式,举个例子帮助理解下,这种设计模式至关于有一个变相怪杰的特殊面具,不管人或狗,谁带上谁就能具备相应的牛逼能力。
算是一个特殊的内存缓存类型,不能单独使用。



disk缓存

主要难点在于内存缓存,disk缓存其实比较简单,就是图片加载完成后把图片文件存到本地方便下次使用。

一样,先贴一下官方主页的介绍。和内存缓存差很少,根据算法不一样提供了几种类别,能够自行经过ImageLoaderConfiguration.discCache(..)设置


硬盘缓存,保存是以文件的形式。框架提供了4种类型,具体算法规则不一样,看名字咱们大概也能知道对应意思

UnlimitedDiscCache                  不限制缓存池大小,最快的一种disk缓存, 默认硬盘缓存方式(比其余disk缓存方式快30%)
TotalSizeLimitedDiscCache      限制缓存池总size大小,超出时删除最先保存的图片缓存文件
FileCountLimitedDiscCache      限制缓存池总数量,超出时删除最先保存的图片缓存文件,缓存图片是相同大小时使用此disk缓存类型
LimitedAgeDiscCache               不限制缓存池的大小,只对文件保存时间作限制,若是图片文件超出定义时间时删除之

一样,具体算法不作讨论~

图片缓存文件位置是 优先保存在 内存卡下的Android\data\应用包名\cache文件夹下的
(无权限,没有装sd等 没法保存至sd卡的状况时,则保存在 手机内存data\data\应用包名\cache文件夹下)
下图能够看到,文件缓存的保存位置是区分应用的,每一个应用设置一个缓存文件夹,图片文件的名字也通过了md5编码处理
 



--------------------------------------------------------------------------------------------------------


图片加载一些具体数值的设置
比较重要的好比限定图片宽高多少合适啊~缓存池大小限定多少啊等等。UIL框架中是在 基本配置对象中进行设置的,前面提到过,有一个默认设置ImageLoaderConfiguration.createDefault(this);针对绝大部分状况都适用的,要修改一些经常使用的配置的话,设置方法能够去github UIL主页下载(文章开头有地址,文章结尾处我也会加上附件的)压缩包,里面包括源代码以及示例代码,能够在示例demo中查看一些经常使用基本配置设置和显示配置的设置


这里只介绍几个重要的值设定


图像压缩后的限定宽高值
 

不一样的限定值设定,是有一个优先级的。我专门实验研究了下(actual measured width and height不是太清楚)
优先级  memoryCacheExtraOpstion> width/height > maxWidth/maxHeight > divice screen

即由高到低依次遍历是否能获取到值,得到后做为限定宽高值解析压缩后的图片实例对象

 

 



缓存池大小的设置~
分强引用和弱引用,弱引用咱们知道不用限制~主要是针对强引用部分缓存池的限制
教程(二)里面也提到过,主要分两种:限制图片数量 和 限制所有图片所占内存大小
对于强引用大小的限定,咱们看下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功能强大是毋庸置疑的,代码框架也很清晰,文档也算齐全~ 可是对开发者尤为是我这样的初学者来讲一点点啃下来仍是很艰难的,最好先看教程一二,彻底懂了之后再看本篇,固然一二看懂基本上图片处理也差很少了


本篇至关于对以前教程作个验证 "你看,最有名的框架基本上也是这么处理嘛~"如此这般从侧面证明一下教程中方法的靠谱性~

其余做用呢? 经过钻研确定是提升咱们的技术了,学习别人的类结构设计~ 也能更好的使用图片框架(其余框架差很少都这样逻辑),且对于拓展部分也能够做为本身的亮点在面试中使用 ~


(四)开源图片框架分析2-ImageLoader和Volley

懒得复制+调整格式了,地址:http://www.eoeandroid.com/thread-334315-1-1.html


(五)图片处理效果对比

对比对象: UIL Volley 官方教程中的方法(此系列教程一里介绍的,ImageLoader的处理方法和官方的差很少)

------------------------------------------------------------------------

首先 单张图片的压缩处理,也是分析重点
专门撸了一个小demo(结尾会放出下载链接)将对应计算方法copy了出来,而后计算了几十组数据,进行了对比
原图宽高都是一个10000之内的随机整数,限定大小是400 200,而后进行压缩处理,记录了10组数据以下
 

控件缩放类型FIT_INSIDE和CROP在教程三-1的UIL框架分析中有详细说明,这里再简单说下
FIT_INSIDE是默认控件显示类型,图片会完整的显示在控件内,不必定会填满控件
CROP是使图片填充整个控件,图片可能会显示不全
具体效果你们能够在android项目里建一个imageview不设置缩放类型(即FIT_INSIDE效果), 再建一个scaleType为CROP的同大小控件,而后显示同一张图片,就会看出来区别了


官方/volley/imageloader都没有对缩放类型作区别处理,因此不一样缩放类型下,最终压缩图片是一个size
结果咱们也能够明显看出
官方算法是和UIL的CROP类型时结果同样的,实质上做用是保证压缩后图片宽高 都要大于等于限定宽高,
Volley的那种对限定值的特殊处理方式,则实质上最终的效果是让压缩后的图片宽高 任意一个大于等于限定宽高便可,则对应UIL的FIT_INSIDE效果


哪一种好呢?(只针对图片的压缩处理而言)
UIL咱们已经长篇详细的分析过了(教程系列三-1),对于缩放类型的处理是十分准确的, 明显是最好的~(这么多人用不是没道理的)
那对比起来,官方的处理和Volley的则各有不足了~

官方=UIL CROP 即官方的处理方法其实更适合于CROP缩放类型的图片显示
然而在处理宽高比差异较大的图片时,若是是FIT_INSIDE显示模式,则会形成压缩图片略大,虽然能保证显示质量,可是浪费小部份内存资源
好比上图中的第一组数据,664 3640的压缩结果,明显和咱们所需的400 200差异过大- - 脑补一下大概就是火柴盒跟西瓜刀吧(不是太准确~)

Volley=UIL FIT_INSIDE 则表明Volley更适合默认状况下的图片显示状况了
那么在处理宽高比差异过大的图片时,若是是CROP缩放类型,则压缩大小看起来是差很少了,但实际上显示效果是没法达到预期的,大小咱们能够从size数值上看出来,显示效果我仍是弄个实际图片你们对比看看吧(能够在文章末尾下载demo项目)


丧心病狂的找了个微博长图实验,项目一共四个ImageView控件,上面俩是Volley,下排是UIL,左边俩是FIT_INSIDE效果,右边则是CROP
而我说的Volley在过大宽高差时,用CROP类型显示没法达到预期质量的效果,就是右上角这个图了,简直就是我不戴眼镜看世界滴样子
对比下,UIL在处理CROP状况时效果十分有保障~
 

上面缺点都有个条件 宽高比差异过大,由于大部分状况下,原图的宽高比是比较稳定的,而咱们限定值也都是差很少的,大部分状况都是设为一个正方形的限定大小,原图基本也都是接近正方形的矩形~这就解释了为何大部分的图片框架包括官方教程中都没有专门对不一样缩放类型作区分处理,由于通常状况下,即长宽比稳定差异不大的状况下,官方的那种简单处理都是适用滴
参考上面数据咱们也能够得出,当原图长宽比和限定长宽比越相近,则两种不一样算法(官方和UIL的CROP是一种,Volley和UIL的FIT_INSIDE是另外一种)的区别越小,当原图和限定值的长宽比十分类似甚至相等时,那两种算法最终的结果就同样了(好比数据中红色加粗的部分)
而咱们看长宽比差异较大的第一组数据和倒数第二组数据,两种算法的差异直接是2^4=16倍~你们有兴趣能够自行尝试,弄几个更大比例的特殊值试一试

------------------------------------------------------------------------

图片色彩样式对比
Volley写死了是RGB_565
ImageLoader没有设置,那应该就是默认的ARGB_8888
UIL提供设置API,默认则也是ARGB_8888

------------------------------------------------------------------------

缓存池对比
Volley只有接口...实例类都没有,不过写起来也很简单
ImageLoader则主要是单强引用,单软引用两种
UIL提供了8种类型,弱和强,单独以及混合的,包括不一样的算法

区别就在于软引用和弱引用的不一样,这个直接看教程二里面针对不一样引用类型的介绍就好了,也能够自行百度搜,其实二者区别不大
强引用部分也都差很少,即便使用了最新的LruCache类,其实看源码咱们也会发现内部仍是用LinkedHashMap实现的

------------------------------------------------------------------------


综上,确定是UIL彻底胜出了,还有不少拓展功能部分也都是UIL更增强大没跑了
不过Volley框架咱们以前也说过了,虽然功能不够完善,但十分适合咱们二次开发
ImageLoader吗,稍微简单点,能够当学习源码的小练手来用


------------------------------------------------------------------------

最后滴最后,demo简单介绍下
drawable包下有一个长宽比很大的微博长图,还有一个正常的接近正方形的图,
代码只有一个类,里面把几种压缩算法都贴出来了,测试方法也已经写好,你们能够自行修改数值计算,也能够拷贝一些图片进去看其余比例图片的效果
布局文件对应也只有一个,里面放了4个imageView,俩普通的,俩center_crop的,也就是对应的CROP缩放类型,控件缩放类型和UIL框架缩放类型的对应关系,参考教程三-1的UIL介绍里


连接: http://pan.baidu.com/s/1eQswfcu 密码:na94
相关文章
相关标签/搜索