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(Resourcesres, 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/4options.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;// 初始化压缩比例为1int 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;}利用此方法获取到所需压缩比例值,最终获取到压缩后的图片~以上代码能够看懂的话,下面这段/*扯淡*/可以跳过逻辑是将原图宽高一半一半的缩减,一直减到宽高都小于自己设定的限定宽高时为止,测试的时候问题来了原图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~ 满足压缩预期官网的这个方法是: 将图片一半一半的压缩,直到压缩成成大于所需宽高数的那个最低值大于~不是大于等于,所以就会出现我上面那种情况,我觉得方法不是太好= = 能满足压缩的需求,但是压缩的比例不够准确~所以最好改成大于等于,如下(个人意见,仅供参考,在实际压缩中很少遇到恰巧等于的这个情况,所以>和>=差别也不大额~看我这扯扯淡就当对计算比例的逻辑加深个理解吧)while ((halfHeight / inSampleSize) >= reqHeight&& (halfWidth / inSampleSize) >= reqWidth) {inSampleSize *= 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 = new HashMap<String, SoftReference<Bitmap>>();强引用作为一级缓存,为了实现删除最不常用对象,可以用LinkedHashMap<String,Bitmap> 类LinkedHashMap对象可以复写一个removeEldestEntry,这个方法就是用来处理删除最不常用对象逻辑的按照之前的设计就可以这么写: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;}};每次图片显示时即使用时,如果存在与缓存中,则先将对象从缓存中删除,然后重新添加到一级缓存中的最前端会有三种情况1.如果图片是从一级缓存中取出来的,则相当于把对象移到了一级缓存池的最前端(相当于最近使用的一张图片)~2.如果图片是从二级缓存中取出来的,则会存到一级缓存池最前端并检测,如果超过阀值,则将最不常用的一个对象移动到二级缓存中3.如果缓存中没有,那就网上下载图片,下载好以后保存至一级缓存中,同样再进行检测是否要移除一个对象至二级缓存中结合现实例子理解下(如果以上逻辑了解可以跳过):美国篮球,比如有一个最高水平的联赛NBA,还有一个次一级的联赛NBDL~一级联赛NBA的排名按最近一次拿冠军的时间由近到远排列,我们规定,每一季度比赛都要产生一个冠军,冠军可能是已有的任何一个队伍也可能是一个民间来的新队伍~而当一个队伍获取冠军的时候就给他加到一级队伍NBA里~ 由于是最近一次拿冠军,所以加进去的时候也是排名第一NBA作为最高水平,我们对数量是有限制的,所以每次有新冠军产生的时候我们都做一次检测,如果队伍总数量超过20支,那么就移除排名最低即离上次获冠军时间最长的那个最差队伍.如果每季度比赛拿冠军相当于一次图片使用操作,那上面三种情况就对应我们例子中的: 1.NBA的队伍拿冠军,相当于这个队伍排名变成了第一名~但NBA队伍总数不变,没有新加入来的2.NBDL二级联赛拿冠军,则加入到NBA里面,且变成了第一名~由于NBA队伍相当于增加了一个,那就要检测一下是否超过20支并将最差成绩的挤到NBDL中3.民间来大神了虐了全部的队伍拿了冠军,那直接加入NBA然后变成第一名,同样,检测NBA 球队数量判断是否要挤出去一队NBDL球队相当于软应用的二级缓存池, 不限定数量~ 多少都可以, 直到美国篮联维护全部NBA NBDL球队的资金不够了(相当于图片过多应用内存不足了)则自动解散一部分球队,落入民间,直到下一次获取总冠军再加入进来(相当于图片从缓存中移除了,下次使用要重新下载)~那NBA就相当于一级缓存,经常拿冠军(相当于高频率使用的图片),那我们就不想因为资金不足随机解散几个球队恰好就解散了NBA队伍,则规定资金不够时只解散二级联赛NBDL的队伍~因为他们获取比赛几率低一点~民间队伍存在与联赛系统之外(相当于不存在缓存中的图片), 而任何一个NBA NBDL联赛球队我们都可以理解为都是民间晋级过来的~只不过从民间获取总冠军并加入联赛需要一个取名字啊登记啊等等的办手续过程(下载图片),比较麻烦,所以我们要尽可能的少办手续~而联赛队伍(包括NBA NBDL)获取总冠军则不需要麻烦的手续,可以直接参加比赛去拿冠军(直接获取显示)两个联赛,一个常用的限定数量,一个不常用的不限定数量,但是资金不足时自动回收部分二级球队~相当于图片的二级缓存Disk缓存可以简单的理解为将图片缓存到sd卡中~由于内存缓存在程序关闭第二次进入时就清空了,对于一个十分常用的图片比如头像一类的~我们希望不要每次进入应用都重新下载一遍,那就要用到disk缓存了,直接图片存到了本地,打开应用时直接获取显示~网上获取图片的大部分逻辑顺序是内存缓存中获取显示(强引用缓存池->弱引用缓存池) -> 内存中找不到时从sd卡缓存中获取显示-> 缓存中都没有再建立异步线程下载图片,下载完成后保存至缓存中按照获取图片获取效率的速度,由快到慢的依次尝试几个方法以文件的形式缓存到SD卡中,优点是SD卡容量较大,所以可以缓存很多图片,且多次打开应用都可以使用缓存,缺点是文件读写操作会耗费一点时间,虽然速度没有从内存缓存中获取速度快,但是肯定比重新下载一张图片的速度快~而且还不用每次都下载图片浪费流量~所以使用优先级就介于内存缓存和下载图片之间了注意:sd卡缓存一般要提前进行一下是否装载sd卡的检测, 还要检测sd卡剩余容量是否够用的情况程序里也要添加注明相应的权限使用LruCache处理图片缓存以上基本完全掌握了,每一张图最好再进行一下教程(一)里面介绍的单张缩放处理,那基本整个图片缓存技术就差不多了但随着android sdk的更新,新版本其实提供了更好的解决方案,下面介绍一下摘取段对软引用的介绍Avoid Soft References for CachingIn 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.简单翻译一下我们要避免用软引用去处理缓存在实践中,软引用在缓存的处理上是没有效率的。