Bitmap使用过程中需注意的点及优化
2016-10-26 18:30
316 查看
问题背景
开发的产品跑monkey时在部分机型上会出现oom,最终堆栈信息都是指向BitmapFactory.decodeStream也就是decode图片到内存时出现的。借着这个机会总结一下Bitmap使用过程中需注意的点及优化。decode图片时内存占用是怎么计算的?
BitmapFactory.decodeXXX api会对图片做哪些操作?
该如何优化?
问题分析
我们先看看内存占用是怎么计算的
为了了解图片内存占用是怎么计算的,我们先要了解一下安卓支持的颜色模式:颜色模式 | 备注 | 每个像素存储大小 |
---|---|---|
ARGB_8888 | 四通道高精度(32位) | 4 bytes |
ARGB_4444 | 四通道低精度(16位) | 2 bytes |
RGB_565 | 屏幕默认模式(16位) | 2 bytes |
ALPHA_8 | 仅有透明通道(8位) | 1 bytes |
那我们decode一个图片到内存时计算规则就是图片的像素 width * height * (模式对应的像素大小)
例如一张480*800的ARGB_8888模式的图片,加载到内存中也就是480 * 800 * 4 bytes。(与图片占用空间大小是无关的)
BitmapFactory.decodeXXX api会对图片做哪些操作?
在使用过程中有时候你会发现图片像素是480×800,但是使用decode api 得到的bitmap宽高不是原图片的大小,这是因为使用BitmapFactory的decode api时,会根据BitmapFactory.options参数对图片做缩放处理。让我们看下decodeResourceStream的代码:/** * Decode a new Bitmap from an InputStream. This InputStream was obtained from * resources, which we pass to be able to scale the bitmap accordingly. */ public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { if (opts == null) { opts = new Options(); } if (opts.inDensity == 0 && value != null) { final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } else if (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
我们会发现在decodeResourceStream方法中会对options的inDensity和inTargetDensity进行赋值。native方法会根据options的参数对图片进行加载。这个inDensity就是我们放置图片的drawable目录对应的density,inTargetDensity则是显示设备对应的density。
举个例子:一张1080*797的图片放置在xxhdpi下在nexus5上显示,这种情况下inDensity:320 inTargetDensity:480 然后会根据比例将图片进行拉伸,最终的大小就是1620*1196,也就是放大了1.5倍。
所以才会出现decode出的图片与原图片大小不一致的情况,这也就是android为你做的自动缩放的实质。
该如何优化?
我们看了第一点就了解了,避免加载bitmap过大内存造成oom,我们的优化方向有两个:第一个是加载合适大小的图片,第二则是使用合理的图片颜色模式。具体有以下几点方法:
通过第二个问题我们知道了,图片资源放置的位置会影响都片最终加载的大小,所以我们在放置切图时需要注意放置的位置。避免大图放置在低分辨率下,这样加载到内存中的大小会很恐怖的。
我们设计切图一般是在一个标准上进行的,但是android碎片化屏幕大小不一,所以我们需要对我们的图片进行合适大小的缩放,避免加载过大图片。
SamplingSize:使用的就是Bitmap options的options.inJustDecodeBounds和options.inSampleSize这两个属性了。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { //设置 inJustDecodeBounds=true 后在decode是只会解析出图片大小,并不会把图片加载到内存中的 //我们可以用这种方式来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 根据图片大小与需要显示的大小计算出inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 设置 inJustDecodeBounds=false 这次真正的将图片解析出来 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // 解析出的宽高可以在options中获取到 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // inSampleSize是以2为倍数的,最小为1 //例如:inSampleSize == 4 则最终图片会宽高会缩小1/4 while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
在图片decode阶段,先获取其宽高然后进行判断是否符合我们预期的,否则进行一定比例的缩放。
这种方式采样率只能支持2的幂次方的值进行缩放,所以一般decode出来的bitmap大小往往不是我们预期的大小,有可能大很多也有可能小很多
Matrix:Matrix矩阵变换,可以对bitmap进行任意操作,其中一项是对bitmap进行等比缩放,这种方式可以精确的缩放到符合我们预期的 bitmap大小,代码如下:
int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); Matrix matrix = new Matrix(); float rate = computeScaleRate(bitmapWidth, bitmapHeight); matrix.postScale(rate, rate); Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
在加载大图片时不要使用getResources().getDrawable()的方式去获取图片,因为这种方式也是将图片全部加载到内存的,大图很容易oom。
使用Options.inBitmap 设置这个可以重用的bitmap的内存区域,不需要在重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。但是该属性在不同sdk版本的使用条件不一致。
详细的可以参考 https://developer.android.com/training/displaying-bitmaps/manage-memory.html
在不影响实际显示效果的情况下,图片尽量不使用ARGB_8888
相关文章推荐
- VB下使用adodb.command 执行存储过程注意
- 存储过程和触发器要使用链接服务器时要注意的一点
- 建立数据库的过程中应注意避免使用一些常见字
- 使用SQL存储过程要特别注意的问题-注意顺序读取
- 一次使用临时表优化数据处理的过程
- 优化PL/SQL过程调用,使用NOCOPY提示
- 使用存储过程中应该注意的问题(原创)
- 使用XPO过程中的代码优化
- 使用Cassini取代IIS做为Web服务器(这一过程中必要注意的事情)
- 使用SQL存储过程要特别注意的问题-注意顺序读取
- 数据库查询的优化——索引使用的注意点
- 使用Cassini取代IIS做为Web服务器(这一过程中必要注意的事情)
- 使用SQL存储过程要特别注意的问题-注意顺序读取
- 在存储过程中使用系统存储过程sp_Excute的注意事项
- 使用SQL存储过程要特别注意的问题-注意顺序读取
- 数据库查询的优化——索引使用的注意点(转)
- python web.py使用flup lighttpd优化过程
- Hibernate使用过程中的一些优化措施
- my sql 存储过程使用注意事项
- 使用 Navicat 8 给 MySQL 写存储过程的几点注意