************************************************前言
我们要说点什么:
1.我们应该怎么加载一个大图片,从而避免OOM(我喜欢stackoverflow.com的名字)
2.我们是否应该鄙视:setbackgroundresource等直接通过一个id构建bitmap/drawable的方法
3.一些参考资料
************************************************正文
1.加载那个大图片
你的图片有多大,在我看来,一张40K+的图片,已经被称为大图片了
当你的程序,通过view.setbackgroundxxx来频繁的(100ms一张图片,或者,更短的ms)更换图片时(或者,你采用surfaceview的style),上述的40K+已经非常的大了
你应该选择什么API来显示你的图片:
bitmapdrawable,而不是setbackgroundresource(int id)
为什么:通过一个id来获取一个bitmap/drawable时,android系统会对该资源进行缓存,从而下次访问迅速响应,但是,它只缓存,不释放,再多的memory也不够用,进而OOM
bitmapdrawable的创建过程很节省时间吗?:
我曾经做过一个实验,在无bitmapfactory.options的时候,反复的加载一个40K+的图片1000次,算的其平均加载一张图片的时间为:68ms
options.inSampleSize
针对于上面的例子,当你选择设置了options.inSampleSize = 2时(width和height均为原来的1/2),你会得到平均30ms左右的加载时间
但是,inSampleSize会使得图片的尺寸缩小(如果view.lp.width/height为wrap_content),会使得图片变得模糊(如果view.lp.width/height被限定成xxdp,这也包含了match_parent)
但是,它是有用的:
一个场景:你已经算的,你的view只有50px*50px的大小,而对应的image的分辨率却是720px*1280px
那么,你应该确定options.inSampleSize,来减少加载时间,甚至避免oom
下面,给出一个最佳实践:
public static final BitmapFactory.Options getBitmapOptions( BitmapFactory.Options... option) { BitmapFactory.Options options = null; if (option == null || option.length == 0) { options = new BitmapFactory.Options(); } else { options = option[0]; } options.inPurgeable = true; options.inInputShareable = true; // options.inDither = true; // options.inScaled = false; options.inPreferredConfig = Bitmap.Config.RGB_565; return options; } /** * 在加载asset图片时,考虑opt.inSampleSize:比较 img.width/height 和 width/height * * @param path * @param am * @param width * 要求的width * @param height * 要求的height * @return */ public static final Bitmap getBitmapConsideringSize(String path, AssetManager am, int width, int height) { InputStream is = null; Bitmap bitmap = null; try { is = am.open(path); BitmapFactory.Options opt = new Options(); opt.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, opt); int widthRatio = (int) Math.ceil(opt.outWidth / (float) width); int heightRatio = (int) Math.ceil(opt.outHeight / (float) height); opt = getBitmapOptions(); if (widthRatio > 1 && heightRatio > 1) { opt.inSampleSize = widthRatio > heightRatio ? widthRatio : heightRatio; } bitmap = BitmapFactory.decodeStream(is, null, opt); } catch (Exception e) { e.printStackTrace(); } finally { closeStream(is); } return bitmap; }
通过上述方法,每次加载40K+的图片,平均只需要26ms,且没有改变图像的尺寸(别被骗了,你的图片规格有可能降低了:RGB_565,但是,还好)
1.1.看看decodestream
转:
使用view.setBackgroundResource,imageview.setImageResource,bitmapFactory.decodeResource这样的方法来设置一张大图片的时候,
这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。
如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常
注意:decodeStream直接拿的图片来读取字节码了,不会根据机器的各种分辨率来自动适应
1.2.使用缓存LruCache
为什么用:使用缓存,可以让我们下次访问一个元素 的消耗时间
lru: Least Recently Used
cache: 缓存
如何访问:我们放入item<key,value>到这个cache中,我们根据key来去取value(注意:value有可能为null,下面说,为什么)
有什么用:
我们在源源不断的往cache里面放入item(而item本身可能是耗内存的,比如<String, Bitmap>),
我们不在意:是否因为生成了足够多的item,而占据了大量的内存,从而导致oom,
为什么:cache在帮我们维护内存,它知道:在我们即将放入一个item时,有可能会导致oom,则,它释放掉lru item(s)
注意什么:当我们根据key来获取value时,value有可能为null,那么,需要自行构建value,(并建议:将<key,value>放入到cache中)
参考:
https://developer.android.com/reference/android/util/LruCache.html
https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
http://stackoverflow.com/questions/12716574/bitmap-recycle-with-largeheap-enabled
1.3别让你的程序卡掉,或者,抛出异常
作为一个单独的小结,我理解,上述问题是多么的重要,以及,令人抓狂
你可以试试每隔60ms来播放1万张40K+图片,并且按照上述方法来进行展示
但是,依然,程序能挂掉,或者,屏幕已经全黑了(你会很确定,你的程序没有死掉,但是,你看到的一切全黑了)
你查看logcat,你会发现:imageref_ashmem create failed <(null)>
可以google,或者stackoverflow,它会告诉你,你需要释放bitmap
你可能会反问,都不会再OOM了,为什么还要释放bitmap,答案,很简单,因为黑屏,因为异常(开玩笑的,如果你知道更确认的原因,请告诉我,请尽量使用通俗易懂的语言,3x)
如何释放一个bitmap:bitmap.recycle()
bitmap会立刻释放吗:不会,但是会在某个时刻被释放
它会立刻引发GC吗:不会(说到这里,如果在你的程序里,频繁的调用gc,或者被动的被调用gc(如:频繁的调用setbackgroundresource,且每次都是个大图片),那么你将有幸看到你的程序离迟钝只有一步之遥了,所以,请不要这样做,因为,它只会让性能更差,你可以实验一下)
它会引发什么样的问题:你可能有机会看到这样一个异常:{你引用了一个已经被recycled的bitmap,然后程序就这样挂了},所以,请你考虑应该进行释放操作的时机,给一个参考:你在显示当前图片的时候,也许,你愿意将之前的图片给释放掉
2.我们该鄙视诸如setbackgroundresource此类吗
的确,setbackgroundresource让人抓狂:它缓存了太多东西,导致OOM
但是,它的存在是为了它的另外一面:当下次访问该图片的时候,能快速响应
接下来的一个问题,下次访问是在什么时候,停100ms再访问这个图片算很长吗?
不幸的是,是的,100ms再访问,会显的很长
当你在编写一个撞球的游戏,每100ms刷新一次屏幕,是那么的漫长,这时,你巴不得刚设置完view的img后,就立刻开始下一次view的img设置(因为android的刷新是有时间的,所以,这样做,是有意义的)(啊,这个球很快,这一秒还在这,下一秒已经在那了,太快了,瞬间转移,也许,你也认为100ms一帧是漫长的)
从bitmapdrawable中实验来看,图片加载的时间为20ms,那么,这个时间很快吗?有直接从缓存里面取来的快吗?
所以,我们不应该鄙视
************************************************一些参考
google上有很多的处理OOM的资料,在此,我贴出一些:
https://developer.android.com/training/displaying-bitmaps/index.html
http://blog.sina.com.cn/s/blog_5de73d0b010117ix.html
相关推荐
突破WPF加载超大图片时,一定概率出现的加载失败,出现白色的问题。 使用多任务分片加载图片的方式,可加载超大高清图片(>20M以上)
大图片分块加载JS代码
加载图片加载图片加载图片加载图片加载图片加载图片加载图片加载图片加载图片
加载大量图片的时候,异步加载防止ui假死卡顿。给你一个类似网页缓慢加载的感受。
解决QT大图片加载失败问题:QImage加载大图片时,malloc返回NULL,导致图片加载失败。 用QT4.8,高版本改改头文件就可以了。
Android有效解决加载大图片时内存溢出的问题
使用RunLoop优化tableView加载大量图片卡顿问题,可有效解决主线程阻塞问题
面对1G以上的超大图片文件,直接加载会导致内存错误,这个代码片段就是用来读取图片文件图块来分块加载大图;
加载大图片时候显示正在加载中图片
js加载base64图片js加载base64图片js加载base64图片js加载base64图片
局部加载超大图片显示,如清明上河图
cxGridTableView图片列加载图片
加载图片,Loading,视频加载图片,旋转图片
Android 图片异步加载 加载网络图片
jq图片延迟加载
甚至还可以从各种各样奇葩的数据源中加载图片。 加载网络图片 很多情况下,我们使用图片加载库就是为了加载网络图片。网络操作是一个很复杂的东西。试想一下,如果没有图片加载库,我们就要手动去下载图片,缓存图片...
用异步加载方式解决GridView加载大量图片出现卡顿的问题。可以查看博文http://blog.csdn.net/yifei1989/article/details/19906821
加载图片源代码加载图片源代码加载图片源代码加载图片源代码加载图片源代码
android加载大量图片内存溢出的三种解决办法
picturebox 利用缓冲加载图片,实现图片的放大,缩小,平移等