Bitmap基本概念及在Android4.4系统上使用BitmapFactory的注意事项

本文首先总结一下Bitmap的相关概念,然后通过一个实际的问题来分析设置BitmapFactory.options的注意事项,以减少不必要的内存占用率,避免发生OOM。

一、 Bitmap的使用trick

尽量不要使用setImageBitmap或setImageResource 或BitmapFactory.decodeResource来设置一张大图, 因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的, 需要消耗更多内存。因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用 JNI >> nativeDecodeAsset() 来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。

如果在读取时加上图片的Config参数,可以更有效减少加载的内存,从而有效阻止抛出out of Memory异常.另外,decodeStream直接拿的图片来读取字节码了,不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。

BitmapFactory.Options.inPreferredConfig

* ALPHA_8:数字为8,图形参数应该由一个字节来表示,应该是一种8位的位图 
     * ARGB_4444:4+4+4+4=16,图形的参数应该由两个字节来表示,应该是一种16位的位图. 
     * ARGB_8888:8+8+8+8=32,图形的参数应该由四个字节来表示,应该是一种32位的位图. 
     * RGB_565:5+6+5=16,图形的参数应该由两个字节来表示,应该是一种16位的位图. 
     *  
     * ALPHA_8,ARGB_4444,ARGB_8888都是透明的位图,也就是所字母A代表透明。 
     * ARGB_4444:意味着有四个参数,即A,R,G,B,每一个参数由4bit表示. 
     * ARGB_8888:意味着有四个参数,即A,R,G,B,每一个参数由8bit来表示. 
     * RGB_565:意味着有三个参数,R,G,B,三个参数分别占5bit,6bit,5bit. 
     *  
     *  
     * BitmapFactory.Options.inPurgeable; 
     *  
     * 如果 inPurgeable 设为True的话表示使用BitmapFactory创建的Bitmap 
     * 用于存储Pixel的内存空间在系统内存不足时可以被回收, 
     * 在应用需要再次访问Bitmap的Pixel时(如绘制Bitmap或是调用getPixel), 
     * 系统会再次调用BitmapFactory decoder重新生成Bitmap的Pixel数组。  
     * 为了能够重新解码图像,bitmap要能够访问存储Bitmap的原始数据。 
     *  
     * 在inPurgeable为false时表示创建的Bitmap的Pixel内存空间不能被回收, 
     * 这样BitmapFactory在不停decodeByteArray创建新的Bitmap对象, 
     * 不同设备的内存不同,因此能够同时创建的Bitmap个数可能有所不同, 
     * 200个bitmap足以使大部分的设备重新OutOfMemory错误。 
     * 当isPurgable设为true时,系统中内存不足时, 
     * 可以回收部分Bitmap占据的内存空间,这时一般不会出现OutOfMemory 错误。 
下面给出一段读取Bitmap的代码:

[java] view plaincopy

  1. public Bitmap readBitmap(Context context, int resId) {
  2. BitmapFactory.Options opts = new BitmapFactory.Options();
  3. opts.inPreferredConfig = Config.RGB_565;
  4. opts.inPurgeable = true;
  5. opts.inInputShareable = true;
  6. InputStream is = context.getResources().openRawResource(resId);
  7. return BitmapFactory.decodeStream(is, null, opts);
  8. }

二、在Android4.4系统上使用BitmapFactory.options的注意事项
前段时间将手机的Android系统升级到4.4之后,发现之前开发的App运行起来非常的卡,严重影响了用户体验。后来发现跟Bitmap.decodeByteArray的底层实现有关。本文将对问题原因进行总结,希望大家写代码时能留意一下,因为这种问题一旦遇到,要花很多时间才能发现原因。

我们的代码能根据屏幕的密度对图片进行缩放,因此我们使用最大的图片资源,这样的话对于任何的手机屏幕,都会对图像进行压缩,不会造成视觉上的问题。图片解码前需要对BitmapFactory.Options进行设置,部分代码如下:

[java] view plaincopy

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. DisplayMetrics displayMetrics = context.getResources.getDisplayMetrics();
  3. ......
  4. options.inTargetDensity = displayMetrics.densityDpi;
  5. options.inScaled = true;
  6. //getBitmapDensity()用于设置图片将要被显示的密度。
  7. options.inDensity = getBitmapDensity();
  8. ......
  9. Bitmap bitmap = getBitmapFromPath(loadPath, options);

options.inTargetDensity表示的是目标Bitmap即将被画到屏幕上的像素密度(每英寸有多少个像素)。这个属性往往会和options.inDensity和options.inScaled一起来觉得目标bitmap是否需要进行缩放。若果这个值为0,则BitmapFactory.decodeResource(Resources, int)和BitmapFactory.decodeResource(Resources, int, android.graphics.BitmapFactory.Options)decodeResourceStream(Resources, TypedValue, InputStream, Rect, BitmapFactory.Options) 将inTargetDensity用DisplayMetrics.densityDpi来设置,其它函数则不会对bitmap进行任何缩放。
options.inDensity表示的是bitmap所使用的像素密度。如果这个值和options.inTargetDensity不一致,则会对图像进行缩放。 如果被设置成0,则 decodeResource(Resources, int), decodeResource(Resources, int, android.graphics.BitmapFactory.Options), 和decodeResourceStream(Resources, TypedValue, InputStream, Rect, BitmapFactory.Options)将用屏幕密度值来设定这个参数,其它函数将不进行缩放。

图片的缩放倍数是根据inTargetDensity/inDensity来计算得到的。
  我们使用的图片是640 * 1136,编码格式ARGB_8888,则大小为 640*1136*4=291K。手机屏幕密度为480,则options.inTargtetDensity为480;inDensity被设置成160. 安照以上的设置,bitmap的大小将被放大9倍,图片编码后的大小应为640*1136*4=26M。我们的App中总共加载了3张这样的图片,故运行起来非常的卡。
 但为何Android4.4之前的版本没有这样的问题,为此我们分析了Bitmap.decodeByteArray()的源码。

[java] view plaincopy

  1. /**
  2. * Decode an immutable bitmap from the specified byte array.
  3. *
  4. * @param data byte array of compressed image data
  5. * @param offset offset into imageData for where the decoder should begin
  6. *               parsing.
  7. * @param length the number of bytes, beginning at offset, to parse
  8. * @param opts null-ok; Options that control downsampling and whether the
  9. *             image should be completely decoded, or just is size returned.
  10. * @return The decoded bitmap, or null if the image data could not be
  11. *         decoded, or, if opts is non-null, if opts requested only the
  12. *         size be returned (in opts.outWidth and opts.outHeight)
  13. */
  14. public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
  15. if ((offset | length) < 0 || data.length < offset + length) {
  16. throw new ArrayIndexOutOfBoundsException();
  17. }
  18. Bitmap bm;
  19. Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
  20. try {
  21. bm = nativeDecodeByteArray(data, offset, length, opts);
  22. if (bm == null && opts != null && opts.inBitmap != null) {
  23. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  24. }
  25. setDensityFromOptions(bm, opts);
  26. } finally {
  27. Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
  28. }
  29. return bm;
  30. }

我们发现,该函数会调用本地函数 nativeDecodeByteArray(byte[] data, int offset, int length, Options opts)来解析图片。
android4.4以前的BitmapFactory.cpp中nativeDecodeByteArray调用doDecode函数时不会根据density进行缩放处理(没有查到所有的4.4以前的所有代码,以4.2为例):

willscale这个参数决定了它是否能被缩放,由于没有传入scale值到doDecode中,scale一直使用默认值1.0f,所以willScale根据默认值计算将始终为false,即bitmap不会被缩放。

android4.4平台nativeDecodeByteArray对doDecode的调用方式没有改变,但改变了doDecode函数的实现,特别是对willScale的计算方式进行了修改

其中全局变量gOptions_scaledFieldID为java文件中BitmapFactory.Options的inScale变量在native层的id,

因为scale = (float) targetDensity / density;所以缩放倍速由inTargetDensity和inDensity两个值确定。

可知在native层,当scale不等于1.0时会对图片进行缩放,长宽缩放方式如下:

好了,就写到这儿,大家可以查查以前写的代码,如果有根据屏幕密度加载Bitmap的部分,请将App在4.4的系统上跑跑看,观察内存占用的情况。解决上面的问题办法其实也很简单,大家可以想想看。

时间: 2024-10-10 15:03:32

Bitmap基本概念及在Android4.4系统上使用BitmapFactory的注意事项的相关文章

第五十一课 NoSQL基础概念及MongoDB应用、数据库分配概念

NoSQL基础概念及MongoDB MongoDB基础应用 MongoDB索引及复制集 数据库分片的概念及Mongodb  sharding的实现 一.NoSQL基础概念 NoSQL(Not Only SQL),是一种技术流派,非关系型数据库:适合用在大数据领域,各种nosql有各自的查询语句,这也是nosql的缺点之一. 大数据(BigDate)也称海量数据是一个模糊的概念,像Google.百度收集大量数据,分析现在.预测未来:这些数据通过某些特定的特征和算法得出某些预测的结果,这些数据为大数

数据库基本概念及Oracle基本语句

一.数据库分类 通常是按照数据模型的特点将传统数据库系统分成网状数据库.层次数据库和关系数据库三种. 1.网状数据库 顾名思义,网状数据库采用的是以记录类型为结点的网状数据模型,是一种导航式(Navigation)的数据库--用户在操作数据库时不但要指明要访问的对象(数据),还要规定其存取路径. 网状数据库模型对于层次和非层次结构的信息都能比较自然的模拟,在关系数据库出现之前网状DBMS要比层次DBMS用得普遍.在数据库发展史上,网状数据库占有重要地位. 2.层次型数据库 紧随网络型数据库之后出

马哥教育第二十四ftp协议、vsftpd的高级应用、rpc概念及nfs的基本应用、samba及其基本应用

1.ftp协议及vsftpd的基本应用         文件共享服务:                 工作在应用层:ftp(file transfer protocol)                          应用层协议:tcp,                  工作在内核:nfs                 跨平台:samba           ftp工作在221号端口,传输数据:                  命令连接:文件管理类命令,始终在线的连接       

iOS开发多线程网络———多线程概念及GCD

@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css); @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css); @import url(htt

57-4 数据库分片概念及mongodb sharding的实现

04 数据库分片的概念及mongodb sharding的实现 配置环境: node1: 192.168.1.121 CentOS release 6.7 node2: 192.168.1.122 CentOS release 6.7 node3: 192.168.1.123 CentOS release 6.7 [[email protected] ~]# vim /etc/hosts 添加 192.168.1.121   node1 192.168.1.122   node2 192.168

正则表达式基本概念及在java中的使用

正则表达式,我是从why,what,how开始探索的... why:正则表达式用“俗语”解释是什么意思?“历史”上发生了什么事情,让正则表达式诞生了? what:正则表达式具体内容是什么?包含什么定义?这些定义分别用来做什么? how:我在用java,那么正则表达式和java又是怎么联系起来的呢? 伴随着这些问题的解决,对正则表达式有了初步的理解,习惯性用脑图将这些内容整理,希望和大家一起沟通,互相进步~ 正则表达式基本概念及在java中的使用,布布扣,bubuko.com

基于android4.4系统行车记录应用黑屏问题分析及对策

基于android4.4系统行车记录应用黑屏问题分析及对策 笔者最近遇到一个棘手的问题,那就是行车记录应用出现黑屏的问题,现象就是进入行车记录应用surface是黑的,录像文件几分钟一个的那种,每个文件的大小都是零.看到这个大家都非常重视,对于车载产品来说,行车记录功能需要保持长时间正常工作,出现这种问题肯定是不能接受的,必须解决!那这个问题是怎么出现的呢? 跟了很长时间,同时动用了8台相同的机器来单独做行车记录的拷机测试,12个小时内都不会出问题,但是超过24小时,就有那么2-3台机器会出现黑

android4.4系统解决“ERRORcouldn&#39;t find native method”方法

android4.4系统解决"ERRORcouldn't find native method"方法 今天笔者在移植一个tv模块从android4.2到android4.4系统的设备上,同样的代码,同样的方法,就是运行不起来.大概方法就是上层写一个apk,调用一个静态的java库,java库加载调用JNI的库文件,这一套代码在4.2上都是好好的,正常能跑,移到4.4上编译也都没有问题,运行会出下面的问题: /***************************************

android4.4系统永不锁屏

android4.4\frameworks\base\packages\Keyguard\src\com\android\keyguard\KeyguardViewMediator.java /** * External apps (like the phone app) can tell us to disable the keygaurd. */ private boolean mExternallyEnabled = false;//系统此属性默认是true,本类把所有的mExternal