关闭

关于图片加载性能优化总结

标签: android性能优化图片
119人阅读 评论(0) 收藏 举报
分类:

Android 图片加载性能优化总结 

 

一、Android Bitmap加载大尺寸图片优化:  

压缩原因: 

1.imageview大小如果是200*300那么加载个2000*3000的图片到内存中显然是浪费可耻滴行为; 

2.最重要的是图片过大时直接加载原图会造成OOM异常(out of memory内存溢出) 所以一般对于大图我们需要进行下压缩处理 

 

主要处理思路是: 

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; 

利用此方法获取到所需压缩比例值,最终获取到压缩后的图片~

优化: 

还是上面例子,如果限定了200*150,而原图是390*290会是个啥情况? 

还是第一次while循环,390/2/1结果是195不满足>200的情况,结束循环,比例值为1,最后图片压缩成400*300 

虽然压缩一次以后没有满足大于所需宽高,但是和所需宽高很接近啊!!! 能不能做一个获取压缩成最接近所需宽高数的比例值呢? 

我也不知道= = 回头可以慢慢研究, 这个"接近"的定义比较模糊,不好掌握~ 找了几个有名的图片加载开源框架发现也都没有这种处理- -不知道是这样设计是不需要呢,还是没啥用呢

以上,图片的像素大小已经做了缩放,但是图片的大小除了和像素有关,还和色彩样式有关不同的样式决定了图片单个像素占的字节数 

比如,图片默认的色彩样式为ARGB_8888,每个像素占4byte(字节)大小

可以看到一共有四种色彩样式 

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方法获取图片。

 

 

二、Android 加载多张图片的缓存处理 

一般少量图片是很少出现OOM异常的,除非单张图片过~大~ 那么就可以用教程一里面的方法了 
通常应用场景是listview列表加载多张图片,为了提高效率一般要缓存一部分图片,这样方便再次查看时能快速显示~不用重新下载图片 
但是手机内存是很有限的~当缓存的图片越来越多,即使单张图片不是很大,不过数量太多时仍然会出现OOM的情况了~ 
本篇则是讨论多张图片的处理问题

   图片缓存的一般处理是 


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


但是缓存池不能无限大啊~不然就会异常了,所以通常我们要对缓存池进行一定控制 需要有两个特性:  
总大小有个限制,不然里面存放无限多的图片时会内存溢出OOM异常 
当大小达到上限后,再添加图片时,需要线程池能够智能化的回收移除池内一部分图片,这样才能保证新图片的显示保存 
异步线程下载图片神马的简单,网上异步下载任务的代码一大堆,下载以后流数据直接decode成bitmap图片即可


这里主要讲使用LruCache处理图片缓存 
 
以上基本完全掌握了,每一张图最好再进行一下教程(一)里面介绍的单张缩放处理,那基本整个图片缓存技术就差不多了 
但随着android sdk的更新,新版本其实提供了更好的解决方案,下面介绍一下  


摘取段对软引用的介绍

Avoid Soft References for Caching  
In practice, soft references are inefficient for caching. The runtime doesn't have enough information on which references to clear and which to keep. Most fatally, it doesn't know what to do when given the choice between clearing a soft reference and growing the heap. The lack of information on the value to your application of each reference limits the usefulness of soft references. References that are cleared too early cause 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 much memory is allotted.  
简单翻译一下 
 
我们要避免用软引用去处理缓存

在实践中,软引用在缓存的处理上是没有效率的。运行时没有足够的信息用于判断哪些引用要清理回收还有哪些要保存。 
最致命的,当同时面对清理软引用和增加堆内存两种选择时它不知道做什么。   
对于你应用的每一个引用都缺乏有价值的信息,这一点限制了软引用让它的可用性十分有限。 过早清理回收的引用导致了无必要的工作; 而那些过晚清理掉的引用又浪费 了内存。  
大多数应用程序应该使用一个android.util。LruCache代替软引用。LruCache有一个有效的回收机制,让用户能够调整有多少内存分配。 
 
简而言之,直接使用软引用缓存的话效果不咋滴~推荐使用LruCache 
level12以后开始引入的,为了兼容更早版本,android-support-v4包内也添加了一个LruCache类, 
所以在12版本以上用的话发现有两个包内都有这个类,其实都是一样的~

那么这个类是做啥的呢~这里是官方文档 
http://developer.android.com/reference/android/util/LruCache.html  
LRU的意思是Least Recently Used 即近期最少使用算法~ 
眼熟吧,其实之前的二级缓存中的那个强引用LinkedHashMap的处理逻辑其实就是一个LRU算法 
而我们查看LruCache这个类的源代码时发现里面其实也有一个LinkedHashMap,大概扫了眼,逻辑和我们之前自己写的差不多 
核心功能基本都是: 当添加进去新数据且达到限制的阀值时,则移除一个最少使用的数据   
根据这个新的类做图片加载的话,网上大部分的做法还是二级缓存处理 只不过将LinkedHashMap+软引用 替换成了LruCache+软引用


都是二级缓存,强引用+软引用的结构~因为LruCache和LinkedHashMap都是差不多的处理逻辑 
没有移除软引用的使用,而是将两者结合了起来 
 
根据官网的介绍来看其实软引用效果不大,二级缓存的处理的话,虽然能提高一点效果,但是会浪费对内存的消耗~ 
所以要不要加个软引用的二级缓存,具体选择就看自己理解和实际应用场景了吧 
 
LruCache我理解是牺牲一小部分效率,换取部分内存~我个人也是倾向于只使用LruCache的实现不用软引用了,也比较简单~ 
  
结合前面举得例子可以理解为,直接取消NBDL二级联赛(软引用)~ 这样能省下好大一笔钱(内存)然后投资联赛其他方面(处理其他逻辑) 
并扩展下NBA一级联赛(LruCache)的规模~保证复用效率

LruCache的具体用法 
 
之前对LinkedHashMap有了一定了解了,其实LruCache也差不多 
类似于removeEldestEntry方法的回收逻辑,在这个类里面已经处理好了 一般我们只需要处理对阀值的控制就行了 
 
阀值控制的核心方法是sizeOf()方法, 该方法的意思是返回每一个value对象的大小size~ 默认返回的是1~即当maxSize(通过构造方法传入)设为10的时候就相当于限制缓 存池只保留10个对象了~ 
和上面LinkedHashMap的例子一个意思

但是由于图片的大小不一,一般限定所有图片的总大小更加合适,那我们就可以对这个sizeOf方法进行复写 
@Override 
protected int sizeOf(String key, Bitmap value) { 


      return value.getRowBytes() * value.getHeight();

 } 

这样的话,相当于缓存池里每一个对象的大小都是计算它的字节数,则在新建LruCache的时候传入一个总size值就行了, 
一般传入应用可用内存的1/8大小 
 
本篇是讨论对于图片数量的控制问题, 再结合教程(一)中的方法对每一张图片进行相应处理~OOM的情况基本就可以避免了~ 
 
内容比较多,有疑惑的地方或者不足的地方大家可以一起探讨

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:3488次
    • 积分:166
    • 等级:
    • 排名:千里之外
    • 原创:13篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条
    文章分类