您的位置:首页 > 其它

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
这些模式在Bitmap.Config下有定义,不多解释各自模式的含义了。我们只需要知道模式位数越高代表其可以存储的颜色信息越多,图像也就越逼真。我们一般使用最多的就是ARGB_8888了。

那我们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.inJustDecodeBoundsoptions.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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  bitmap oom 优化 内存 位图