Android 图片加载性能优化总结Word文件下载.docx
《Android 图片加载性能优化总结Word文件下载.docx》由会员分享,可在线阅读,更多相关《Android 图片加载性能优化总结Word文件下载.docx(34页珍藏版)》请在冰豆网上搜索。
resId,int
reqWidth,
reqHeight){
//首先不加载图片,仅获取图片尺寸
final
BitmapFactory.Optionsoptions=
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是图片配置信息,参数详细介绍下:
是否只解析边界
设为true时去decode获取图片,只会加载像素宽高信息
设为false时decode则会完全加载图片
inSampleSize
压缩比例
比如原图200*300,如果值是2时会压缩成100*150;
是4则图片压缩成50*75最好是2的幂数,比如24816.....
outHeight
图片原高度
outWidth
图片原宽度
其他参数自行研究,这里暂时只用到这几个
decodeSampledBitmapFromResource方法内的三段代码对应上面的三步流程
难点在于中间那步,压缩比例的计算,官网同样提供了个calculateInSampleSize方法
其中reqWidth和reqHeight是所需图片限定最小宽高值
*计算压缩比例值
options
解析图片的配置信息
calculateInSampleSize(BitmapFactory.Optionsoptions,int
//保存图片原宽高值
height=options.
outHeight;
width=options.
outWidth;
//初始化压缩比例为1
inSampleSize=1;
//当图片宽高值任何一个大于所需压缩图片宽高值时,进入循环计算系统
if
(height>
reqHeight||width>
reqWidth){
halfHeight=height/2;
halfWidth=width/2;
//压缩比例值每次循环两倍增加,
//直到原图宽高值的一半除以压缩值后都~大于所需宽高值为止
while
((halfHeight/inSampleSize)>
=reqHeight
&
(halfWidth/inSampleSize)>
=reqWidth){
inSampleSize*=2;
inSampleSize;
利用此方法获取到所需压缩比例值,最终获取到压缩后的图片~
以上代码能够看懂的话,下面这段/*扯淡*/可以跳过
逻辑是将原图宽高一半一半的缩减,一直减到宽高都小于自己设定的限定宽高时为止,测试的时候问题来了
原图400*300,我限定值200*150,if满足进入,while循环第一次,400/2/1=200不满足>
的条件~结束循环,
最终返回了个inSampleSize=1给我
马丹我限定值正好是原图的一半啊,你应该返回给我2啊~你特么最后返回个1给我,那压缩处理后的图还是400*300!
!
当我将限定值稍微改一下变成195*145稍微降低一点点时~if满足进入,while循环第一次,400/2/1>
195满足~
然后压缩比例1*2变成了2,在下一次while循环时不满足条件结束,最后返回比例值2~满足压缩预期
官网的这个方法是:
将图片一半一半的压缩,直到压缩成成大于所需宽高数的那个最低值
大于~不是大于等于,所以就会出现我上面那种情况,我觉得方法不是太好==能满足压缩的需求,但是压缩的比例不够准确~
所以最好改成大于等于,如下(个人意见,仅供参考,在实际压缩中很少遇到恰巧等于的这个情况,所以>
和>
=差别也不大额~看我这扯扯淡就当对计算比例的逻辑加深个理解吧)
&
优化:
还是上面例子,如果限定了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图片即可
难点在与这个图片缓存池的设计,现在网上的实现主要有两种
1.软引用/弱引用
2.LruCache
-----------------------------------------------------------------------
拓展:
java中4种引用分类
强引用
平常使用的基本都是强引用,除非主动释放(图片的回收,或者==null赋值为空等),否则会一直保存对象到内存溢出为止~
软引用
SoftReference
在系统内存不够时,会自动释放部分软引用所指对象~
弱引用
WeakReference
系统偶尔回收扫描时发现弱引用则释放对象,即和内存够不够的情况无关,完全看心情~
虚引用
不用了解,其实我也不熟悉
框架基本都比较爱用这个软应用保存图片作为缓存池,这样在图片过多不足时,就会自动回收部分图片,防止OOM
但是有缺点,无法控制内存不足时会回收哪些图片,如果我只想回收一些不常用的,不要回收常用的图片呢?
于是引入了二级缓存的逻辑
即设置两个缓存池,一个强引用,一个软引用,
强引用保存常用图片,软应用保存其他图片~
强引用因为不会自动释放对象,所以大小要进行一定限定,否则图片过多会异常,比如控制里面只存放10张图片,
然后每次往里面添加图片的时候,检查如果数量超过10张这个阀值,临界点值时,
就移除强引用里面最不常用的那个图片,并将其保存至软应用缓存池中~
整个缓存既作为一个整体(一级二级缓存都是内存缓存~每次显示图片前都要检查整个缓存池中有没有图片)
又有一定的区分(只回收二级缓存软引用中图片,不回收一级缓存中强引用的图片~)
代码实现
软应用缓存池类型作为二级缓存:
HashMap<
String,SoftReference<
Bitmap>
>
mSecondLevelCache
=newHashMap<
String,SoftReference<
();
强引用作为一级缓存,为了实现删除最不常用对象,可以用LinkedHashMap<
String,Bitmap>
类
LinkedHashMap对象可以复写一个removeEldestEntry,这个方法就是用来处理删除最不常用对象逻辑的
按照之前的设计就可以这么写:
finalintMAX_CAPACITY=10;
//一级缓存阈值
//第一个参数是初始化大小
//第二个参数0.75是加载因子为经验值
//第三个参数true则表示按照最近访问量的高低排序,false则表示按照插入顺序排序
String,Bitmap>
mFirstLevelCache
=newLinkedHashMap<
(
MAX_CAPACITY/2,0.75f,true){
//eldest最老的对象,即移除的最不常用图片对象
//返回值true即移除该对象,false则是不移除
protectedboolean
removeEldestEntry(Entry<
eldest){
if(size()>
MAX_CAPACITY){//当缓存池总大小超过阈值的时候,将老的值从一级缓存搬到二级缓存
mSecondLevelCache.put(eldest.getKey(),
newSoftReference<
(eldest.getValue()));
returntrue;
}
returnfalse;
}
};
每次图片显示时即使用时,如果存在与缓存中,则先将对象从缓存中删除,然后重新添加到一级缓存中的最前端
会有三种情况
1.如果图片是从一级缓存中取出来的,则相当于把对象移到了一级缓存池的最前端(相当于最近使用的一张图片)~
2.如果图片是从二级缓存中取出来的,则会存到一级缓存池最前端并检测,如果超过阀值,则将最不常用的一个对象移动到二级缓存中
3.如果缓存中没有,那就网上下载图片,下载好以后保存至一级缓存中,同样再进行检测是否要移除一个对象至二级缓存中
结合现实例子理解下(如果以上逻辑了解可以跳过):
美国篮球,比如有一个最高水平的联赛NBA,还有一个次一级的联赛NBDL~
一级联赛NBA的排名按最近一次拿冠军的时间由近到远排列,
我们规定,每一季度比赛都要产生一个冠军,冠军可能是已有的任何一个队伍也可能是一个民间来的新队伍~
而当一个队伍获取冠军的时候就给他加到一级队伍NBA里~由于是最近一次拿冠军,所以加进去的时候也是排名第一
NBA作为最高水平,我们对数量是有限制的,所以每次有新冠军产生的时候我们都做一次检测,
如果队伍总数量超过20支,那么就移除排名最低即离上次获冠军时间最长的那个最差队伍.
如果每季度比赛拿冠军相当于一次图片使用操作,那上面三种情况就对应我们例子中的:
1.NBA的队伍拿冠军,相当于这个队伍排名变成了第一名~但NBA队伍总数不变,没有新加入来的
2.NBDL二级联赛拿冠军,则加入到NBA里面,且变成了第一名~由于NBA队伍相当于增加了一个,那就要检测一下是否超过20支并将最差成绩的挤到NBDL中
3.民间来大神了虐了全部的队伍拿了冠军,那直接加入NBA然后变成第一名,同样,检测NBA球队数量判断是否要挤出去一队
NBDL球队相当于软应用的二级缓存池,不限定数量~多少都可以,直到美国篮联维护全部NBANBDL球队的资金不够了(相当于图片过多应用内存不足了)
则自动解散一部分球队,落入民间,直到下一次获取总冠军再加入进来(相当于图片从缓存中移除了,下次使用要重新下载)~
那NBA就相当于一级缓存,经常拿冠军(相当于高频率使用的图片),那我们就不想因为资金不足随机解散几个球队恰好就解散了NBA队伍,
则规定资金不够时只解散二级联赛NBDL的队伍~因为他们获取比赛几率低一点~
民间队伍存在与联赛系统之外(相当于不存在缓存中的图片),而任何一个NBANBDL联赛球队我们都可以理解为都是民间晋级过来的~
只不过从民间获取总冠军并加入联赛需要一个取名字啊登记啊等等的办手续过程(下载图片),比较麻烦,所以我们要尽可能的少办手续~
而联赛队伍(包括NBANBDL)获取总冠军则不需要麻烦的手续,可以直接参加比赛去拿冠军(直接获取显示)
两个联赛,一个常用的限定数量,一个不常用的不限定数量,但是资金不足时自动回收部分二级球队~
相当于图片的二级缓存
Disk缓存
可以简单的理解为将图片缓存到sd卡中~
由于内存缓存在程序关闭第二次进入时就清空了,对于一个十分常用的图片比如头像一类的~
我们希望不要每次进入应用都重新下载一遍,那就要用到disk缓存了,直接图片存到了本地,打开应用时直接获取显示~
网上获取图片的大部分逻辑顺序是
内存缓存中获取显示(强引用缓存池->
弱引用缓存池)
->
内存中找不到时从sd卡缓存中获取显示->
缓存中都没有再建立异步线程下载图片,下载完成后保存至缓存中
按照获取图片获取效率的速度,由快到慢的依次尝试几个方法
以文件的形式缓存到SD卡中,优点是SD卡容量较大,所以可以缓存很多图片,且多次打开应用都可以使用缓存,
缺点是文件读写操作会耗费一点时间,
虽然速度没有从内存缓存中获取速度快,但是肯定比重新下载一张图片的速度快~而且还不用每次都下载图片浪费流量~
所以使用优先级就介于内存缓存和下载图片之间了
注意:
sd卡缓存一般要提前进行一下是否装载sd卡的检测,还要检测sd卡剩余容量是否够用的情况
程序里也要添加注明相应的权限
使用LruCache处理图片缓存
以上基本完全掌握了,每一张图最好再进行一下教程
(一)里面介绍的单张缩放处理,那基本整个图片缓存技术就差不多了
但随着androidsdk的更新,新版本其实提供了更好的解决方案,下面介绍一下
摘取段对软引用的介绍
AvoidSoftReferencesforCaching
Inpractice,softreferencesareinefficientforcaching.Theruntimedoesn'
thaveenoughinformationonwhichreferencestoclearandwhichtokeep.Mostfatally,itdoesn'
tknowwhattodowhengiventhechoicebetweenclearingasoftreferenceandgrowingtheheap.
Thelackofinformationonthevaluetoyourapplicationofeachreferencelimitstheusefulnessofsoftreferences.Referencesthatareclearedtooearlycauseunnecessarywork;
thosethatareclearedtoolatewastememory.
Mostapplicationsshoulduseanandroid.util.LruCacheinsteadofsoftreferences.LruCachehasaneffectiveevictionpolicyandletstheusertunehowmuchmemoryisallotted.
简单翻译一下
我们要避免用软引用去处理缓存
在实践中,软引用在缓存的处理上是没有效率的。
运行时没有足够的信息用于判断哪些引用要清理回收还有哪些要保存。
最致命的,当同时面对清理软引用和增加堆内存两种选择时它不知道做什么。
对于你应用的每一个引用都缺乏有价值的信息,这一点限制了软引用让它的可用性十分有限。
过早清理回收的引用导致了无必要的工作;
而那些过晚清理掉的引用又浪费了内存。
大多数应用程序应该使用一个android.util。
LruCache代替软引用。
LruCache有一个有效的回收机制,让用户能够调整有多少内存分配。
简而言之,直接使用软引用缓存的话效果不咋滴~推荐使用LruCache
level12以后开始引入的,为了兼容更早版本,android-support-v4包内也添加了一个LruCache类,
所以在12版本以上用的话发现有两个包内都有这个类,其实都是一样的~
那么这个类是做啥的呢~这里是官方文档
LRU的意思是LeastRecentlyUsed即近期最少使用算法~
眼熟吧,其实之前的二级缓存中的那个强引用LinkedHashMap的处理逻辑其实就是一个LRU算法
而我们查看LruCache这个类的源代码时发现里面其实也有一个LinkedHashMap,大概扫了眼,逻辑和我们