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的代码:
- public Bitmap readBitmap(Context context, int resId) {
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inPreferredConfig = Config.RGB_565;
- opts.inPurgeable = true;
- opts.inInputShareable = true;
- InputStream is = context.getResources().openRawResource(resId);
- return BitmapFactory.decodeStream(is, null, opts);
- }
二、在Android4.4系统上使用BitmapFactory.options的注意事项
前段时间将手机的Android系统升级到4.4之后,发现之前开发的App运行起来非常的卡,严重影响了用户体验。后来发现跟Bitmap.decodeByteArray的底层实现有关。本文将对问题原因进行总结,希望大家写代码时能留意一下,因为这种问题一旦遇到,要花很多时间才能发现原因。
我们的代码能根据屏幕的密度对图片进行缩放,因此我们使用最大的图片资源,这样的话对于任何的手机屏幕,都会对图像进行压缩,不会造成视觉上的问题。图片解码前需要对BitmapFactory.Options进行设置,部分代码如下:
- BitmapFactory.Options options = new BitmapFactory.Options();
- DisplayMetrics displayMetrics = context.getResources.getDisplayMetrics();
- ......
- options.inTargetDensity = displayMetrics.densityDpi;
- options.inScaled = true;
- //getBitmapDensity()用于设置图片将要被显示的密度。
- options.inDensity = getBitmapDensity();
- ......
- 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()的源码。
- /**
- * Decode an immutable bitmap from the specified byte array.
- *
- * @param data byte array of compressed image data
- * @param offset offset into imageData for where the decoder should begin
- * parsing.
- * @param length the number of bytes, beginning at offset, to parse
- * @param opts null-ok; Options that control downsampling and whether the
- * image should be completely decoded, or just is size returned.
- * @return The decoded bitmap, or null if the image data could not be
- * decoded, or, if opts is non-null, if opts requested only the
- * size be returned (in opts.outWidth and opts.outHeight)
- */
- public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
- if ((offset | length) < 0 || data.length < offset + length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- Bitmap bm;
- Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
- try {
- bm = nativeDecodeByteArray(data, offset, length, opts);
- if (bm == null && opts != null && opts.inBitmap != null) {
- throw new IllegalArgumentException("Problem decoding into existing bitmap");
- }
- setDensityFromOptions(bm, opts);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
- }
- return bm;
- }
我们发现,该函数会调用本地函数 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,
可知在native层,当scale不等于1.0时会对图片进行缩放,长宽缩放方式如下:
好了,就写到这儿,大家可以查查以前写的代码,如果有根据屏幕密度加载Bitmap的部分,请将App在4.4的系统上跑跑看,观察内存占用的情况。解决上面的问题办法其实也很简单,大家可以想想看。
Bitmap基本概念及在Android4.4系统上使用BitmapFactory的注意事项的更多相关文章
- CNA, FCoE, TOE, RDMA, iWARP, iSCSI等概念及 Chelsio T5 产品介绍 转载
CNA, FCoE, TOE, RDMA, iWARP, iSCSI等概念及 Chelsio T5 产品介绍 2016年09月01日 13:56:30 疯子19911109 阅读数:4823 标签: ...
- 深入浅出-Android系统移植与平台开发(一)- Android4.0系统的下载与编译
作者:唐老师,华清远见嵌入式学院讲师. 一.Android4.0系统的下载与编译 Android系统的下载与编译,Google的官方网站上已经给出了详细的说明,请参照Android的官方网址: htt ...
- 关于解决android4.0系统中菜单无法添加Icon的问题
在Android4.0系统中,创建菜单Menu,然后通过setIcon方法给菜单添加图标是无效的,图标不会显出来,而之前的系统中是可以显示出来的.这个问题的根本原因在于4.0系统中,涉及到菜单的源码类 ...
- 解决android4.0系统中菜单(Menu)添加Icon无效问题
本文转载自: http://blog.csdn.net/stevenhu_223/article/details/9705173 在Android4.0系统中,创建菜单Menu,通过setIcon方法 ...
- 原码,补码,反码的概念及Java中使用那种存储方式
原码,补码,反码的概念及Java中使用那种存储方式: 原码:原码表示法是机器数的一种简单的表示法.其符号位用0表示正号,用:表示负号,数值一般用二进制形式表示 补码:机器数的补码可由原码得到.如果机器 ...
- 转 RabbitMQ 基础概念及 Spring 的配置和使用 推荐好文 举例讲解
从不知道到了解—RabbitMQ 基础概念及 Spring 的配置和使用 原理同上 请求地址:http://localhost:8080/home?type=3&routing_key=myO ...
- android4.4系统解决“ERRORcouldn't find native method”方法
android4.4系统解决"ERRORcouldn't find native method"方法 今天笔者在移植一个tv模块从android4.2到android4.4系统的设 ...
- LVM逻辑卷基本概念及LVM的工作原理
这篇随笔将详细讲解Linux磁盘管理机制中的LVM逻辑卷的基本概念以及LVM的工作原理!!! 一.传统的磁盘管理 其实在Linux操作系统中,我们的磁盘管理机制和windows上的差不多,绝大多数都是 ...
- Linux磁盘管理:LVM逻辑卷基本概念及LVM的工作原理
一.传统的磁盘管理 其实在Linux操作系统中,我们的磁盘管理机制和windows上的差不多,绝大多数都是使用MBR(Master Boot Recorder)都是通过先对一个硬盘进行分区,然后再将该 ...
随机推荐
- /root/.bashrc与/etc/profile的异同
要搞清bashrc与profile的区别,首先要弄明白什么是交互式shell和非交互式shell,什么是loginshell 和non-loginshell. 交互式模式就是shell等待你的输入,并 ...
- 【NOI2006】最大获利
[问题描述] 新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战.THU 集团旗下的CS&T 通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就 ...
- 35 Search Insert Position(找到数的位置Medium)
题目意思:在递增数组中找到目标数的位置,如果目标数不在数组中,返回其应该在的位置. 思路:折半查找,和相邻数比较,注意边界 class Solution { public: int searchIns ...
- 常用命令(ubuntu)
1.打开终端的方法 Ubuntu 中按左侧栏的第一个“面板主页(Dash 主页)”(可以按win键调出),在里面输入terminal可以打开终端,另外打开终端的快捷键是Ctrl+Alt+T 2.修改用 ...
- MFC枚举USB设备碰到的一个疑难,还没解决
代码如下: 打开USB Hub设备之后,返回句柄hHubDevice,然后使用EnumerateHubPorts来枚举Hub的端 口.疑问在代码的中文注释中. bool CUsbEnumHub::En ...
- 构建高可用web站点学习(三)
分布式的构建 做为网站访问的生命线(数据访问),当然也可以采用分布式的方法来减轻单台服务器的访问压力.之前有讲过Memcached的分布式,但是Memcached服务器互不通信,所以我们也提过redi ...
- angular2 学习笔记 ( rxjs 流 )
RxJS 博大精深,看了好几篇文章都没有明白. 范围牵扯到了函数响应式开发去了... 我对函数式一知半解, 响应式更是第一次听到... 唉...不过日子还是得过...混着过先呗 我目前所理解的很浅, ...
- h.264 mvp求解过程
h.264标准中由于分为宏块分割块(8x8),子宏块分割块(4x4),所以各种各样的求解过程比较繁琐 下面整理出标准中mvp的求解过程 8.4.1.3 已知条件有当前块的属性:位置.块类型需要得到当前 ...
- 关于KeilC51的指针(参见, page 106-113, keil uv2 user's guide 09,2001)
keil中的指针分为两种,一种是普通指针,兼容标准C语言的指针:另一种是我翻译成内存特殊指针(memory-specific pointers,翻译的不好:>) 一.普通指针 普通指针的定义方式 ...
- latch:library cache
一:硬解析造成的shared pool latch 争用: 每一个sql被执行之前,先要到library cache中根据hash_value查找parent cursor,这就需要先获得librar ...