BitmapFactory.Options类是BitmapFactory对图片进行解码时使用的一个配置参数类,其中定义了一系列的public成员变量,每个成员变量代表一个配置参数。
参数inpreferredconfig表示图片解码时使用的颜色模式,也就是图片中每个像素颜色的表示方式
图片颜色:
颜色透明度:
Android颜色和透明度表示
inperferredConfig参数
图片解码时,默认使用ARGB_8888模式:
Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
ARGB_4444已deprecated,在KITKAT(Android4.4)以上,会用ARGB_8888代替ARGB_4444
使用inperferredConfig注意:
java
InputStream stream = getAssets().open(file);
Options op1 = new Options();
op1.inPreferredConfig = Config.ALPHA_8;
Bitmap bm1 = BitmapFactory.decodeStream(stream, null, op1);
Options op2 = new Options();
op2.inPreferredConfig = Config.RGB_565;
Bitmap bm2 = BitmapFactory.decodeStream(stream, null, op2);
Options op3 = new Options();
op3.inPreferredConfig = Config.ARGB_8888;
Bitmap bm3 = BitmapFactory.decodeStream(stream, null, op3);
疑点: 1. 当出现不满足情况时,使用的合适配置是如何选取的?
疑点: 2. 什么情况下使用什么样的配置会出现不满足的情况?
在Android 2.2 (API level 8)以及之前,当垃圾回收发生时,应用的线程是会被暂停的,这会导致一个延迟滞后,并降低系统效率。 从Android 2.3开始,添加了并发垃圾回收的机制, 这意味着在一个Bitmap不再被引用之后,它所占用的内存会被立即回收。
在Android 2.3.3 (API level 10)以及之前, 一个Bitmap的像素级数据(pixel data)是存放在Native内存空间中的。 这些数据与Bitmap本身所占内存是隔离的,Bitmap本身被存放在Dalvik堆中。我们无法预测在Native内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。 自Android 3.0 (API Level 11)开始, 像素级数据则是与Bitmap本身一起存放在Dalvik堆中。
在Android 2.3.3 (API level 10) 以及更低版本上,推荐使用recycle()方法。 如果在应用中显示了大量的Bitmap数据,我们很可能会遇到OutOfMemoryError的错误。 recycle()方法可以使得程序更快的释放内存。
Caution:只有当我们确定这个Bitmap不再需要用到的时候才应该使用recycle()。在执行recycle()方法之后,如果尝试绘制这个Bitmap, 我们将得到”Canvas: trying to use a recycled bitmap”的错误提示。
从Android 3.0 (API Level 11)开始,引进了BitmapFactory.Options.inBitmap字段。如果这个值被设置了,decode方法会在加载内容的时候去reuse已经存在的bitmap. 这意味着bitmap的内存是被reused的,这样可以提升性能, 并且减少了内存的allocation与de-allocation.
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
// inBitmap only works with mutable bitmaps, so force the decoder to
// return mutable bitmaps.
options.inMutable = true;
if (cache != null) {
// Try to find a bitmap to use for inBitmap.
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
// If a suitable bitmap has been found,
// set it as the value of inBitmap.
options.inBitmap = inBitmap;
}
}
}
static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use
// if the byte size of the new bitmap is smaller than
// the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions,
// the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
例如加载一张很大的位图, 如果直接解码会造成OOM,做法是:
1.先拿到位图的尺寸后,进行放缩后再加载位图
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
2.计算inSampleSize
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
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;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。详情参考inSampleSize的文档
3.放缩后再加载小位图:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
设置是否抖动处理图片.
如果设置为true,将返回一个mutable的bitmap,可用于修改BitmapFactory加载而来的bitmap.
BitmapFactory.decodeResource(Resources res, int id)
获取到的bitmap是mutable的,而BitmapFactory.decodeFile(String path)
获取到的是immutable的Bitmap copy(Config config, boolean isMutable)
获取mutable位图用于修改位图pixels.BitmapFactory.decodeResource(Resources,int)
和BitmapFactory.decodeResource(Resources, int,BitmapFactory.Options)
,BitmapFactory.decodeResourceStream()
将inTargetDensity
用DisplayMetrics.densityDpi
来设置,其它函数则不会对bitmap进行任何缩放。BitmapFactory.decodeResource(Resources,int)
, BitmapFactory.decodeResource(Resources, int, BitmapFactory.Options)
,BitmapFactory.decodeResourceStream()
将按照DisplayMetrics的density处理.说三者之间的关系前,先谈下系统位图放缩规则,做个试验(使用小米3作为测试机):将一张144*144的ic_lanucher.png(系统默认在xxhdpi包下)分别放置在hdpi,xhdpi,xxhdpi三个文件夹,打印出位图的大小.
“`java
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.ic_launcher);
Log.d(TAG, "size:" + bitmap.getByteCount());
“`
“`java
hdpi包size:331776
xhdpi包size:186624
xxhdpi包size:82944
“`
我们知道一张144*144
的 ic_lanucher.png所占的实际内存为 144*144*4=82944
字节,那么为什么同一张图片放在不同包下表现不一样的大小?
屏幕密度与Drawable目录有着如下的关系:
目录 | 屏幕密度 |
---|---|
drawable-ldpi | 120dpi |
drawable-mdpi | 160dpi |
drawable-hdpi | 240dpi |
drawable-xhdpi | 320dpi |
drawable-xxhdpi | 480dpi |
当使用decodeResuore()解码drawable目录下的图片时, 会根据手机的屏幕密度,到对应的文件夹中查找图片,如果图片存在于其他目录,则会对该图片进行放缩处理在显示,放缩处理的规则:
scale= 设备屏幕密度/drawable目录设定的屏幕密度
图片内存=int(图片长度*scale+0.5f)* int(图片宽度*scale)*单位像素占字节数
由于实验使用的小米3,屏幕密度为480,则当图片放入在hdpi时:scale= 480/240;
图片放入xhdpi:scale=480/320
;
图片放入xxhdpi时:scale= 480/480
;
说完系统加载位图使用的放缩规则后,再来说说这三个标记之间的关系:
inDesity: 位图使用的像素密度
inTargetDesity: 设备的屏幕密度
inScale: 是否需要放缩位图
清楚这三者的含义,就可以在加载图片时,根据图片在不同设备上的使用,可以放缩来加载位图:
放缩规则 scale= inTargetDensity/inDesity;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = true;
options.inDensity = getBitmapDensity();
options.inTargetDensity =Resources.getSystem().getDisplayMetrics().densityDpi ;
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher, options);
在手机上加载大图时根据屏幕的密度对图片进行缩放,因此我们使用最大的图片资源,这样的话对于任何的手机屏幕,都会对图像进行压缩,不会造成视觉上的问题.但Android系统升级到4.4之后,发现之前开发的App运行起来非常的卡,严重影响了用户体验。后来发现跟Bitmap.decodeByteArray的底层实现有关。而android4.4以前的BitmapFactory.cpp中nativeDecodeByteArray调用doDecode函数时不会根据density进行缩放处理.4.4后由于一张图片缩放加载后,在内存放大很多,导致内存占有量过大,造成卡顿.
参数inpreferredconfig表示图片解码时使用的颜色模式,也就是图片中每个像素颜色的表示方式
图片颜色:
颜色透明度:
Android颜色和透明度表示
inperferredConfig参数
图片解码时,默认使用ARGB_8888模式:
Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
ARGB_4444已deprecated,在KITKAT(Android4.4)以上,会用ARGB_8888代替ARGB_4444
使用inperferredConfig注意:
java
InputStream stream = getAssets().open(file);
Options op1 = new Options();
op1.inPreferredConfig = Config.ALPHA_8;
Bitmap bm1 = BitmapFactory.decodeStream(stream, null, op1);
Options op2 = new Options();
op2.inPreferredConfig = Config.RGB_565;
Bitmap bm2 = BitmapFactory.decodeStream(stream, null, op2);
Options op3 = new Options();
op3.inPreferredConfig = Config.ARGB_8888;
Bitmap bm3 = BitmapFactory.decodeStream(stream, null, op3);
疑点: 1. 当出现不满足情况时,使用的合适配置是如何选取的?
疑点: 2. 什么情况下使用什么样的配置会出现不满足的情况?
在Android 2.2 (API level 8)以及之前,当垃圾回收发生时,应用的线程是会被暂停的,这会导致一个延迟滞后,并降低系统效率。 从Android 2.3开始,添加了并发垃圾回收的机制, 这意味着在一个Bitmap不再被引用之后,它所占用的内存会被立即回收。
在Android 2.3.3 (API level 10)以及之前, 一个Bitmap的像素级数据(pixel data)是存放在Native内存空间中的。 这些数据与Bitmap本身所占内存是隔离的,Bitmap本身被存放在Dalvik堆中。我们无法预测在Native内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。 自Android 3.0 (API Level 11)开始, 像素级数据则是与Bitmap本身一起存放在Dalvik堆中。
在Android 2.3.3 (API level 10) 以及更低版本上,推荐使用recycle()方法。 如果在应用中显示了大量的Bitmap数据,我们很可能会遇到OutOfMemoryError的错误。 recycle()方法可以使得程序更快的释放内存。
Caution:只有当我们确定这个Bitmap不再需要用到的时候才应该使用recycle()。在执行recycle()方法之后,如果尝试绘制这个Bitmap, 我们将得到”Canvas: trying to use a recycled bitmap”的错误提示。
从Android 3.0 (API Level 11)开始,引进了BitmapFactory.Options.inBitmap字段。如果这个值被设置了,decode方法会在加载内容的时候去reuse已经存在的bitmap. 这意味着bitmap的内存是被reused的,这样可以提升性能, 并且减少了内存的allocation与de-allocation.
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
// inBitmap only works with mutable bitmaps, so force the decoder to
// return mutable bitmaps.
options.inMutable = true;
if (cache != null) {
// Try to find a bitmap to use for inBitmap.
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
// If a suitable bitmap has been found,
// set it as the value of inBitmap.
options.inBitmap = inBitmap;
}
}
}
static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use
// if the byte size of the new bitmap is smaller than
// the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions,
// the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
例如加载一张很大的位图, 如果直接解码会造成OOM,做法是:
1.先拿到位图的尺寸后,进行放缩后再加载位图
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
2.计算inSampleSize
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
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;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。详情参考inSampleSize的文档
3.放缩后再加载小位图:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
设置是否抖动处理图片.
如果设置为true,将返回一个mutable的bitmap,可用于修改BitmapFactory加载而来的bitmap.
BitmapFactory.decodeResource(Resources res, int id)
获取到的bitmap是mutable的,而BitmapFactory.decodeFile(String path)
获取到的是immutable的Bitmap copy(Config config, boolean isMutable)
获取mutable位图用于修改位图pixels.BitmapFactory.decodeResource(Resources,int)
和BitmapFactory.decodeResource(Resources, int,BitmapFactory.Options)
,BitmapFactory.decodeResourceStream()
将inTargetDensity
用DisplayMetrics.densityDpi
来设置,其它函数则不会对bitmap进行任何缩放。BitmapFactory.decodeResource(Resources,int)
, BitmapFactory.decodeResource(Resources, int, BitmapFactory.Options)
,BitmapFactory.decodeResourceStream()
将按照DisplayMetrics的density处理.说三者之间的关系前,先谈下系统位图放缩规则,做个试验(使用小米3作为测试机):将一张144*144的ic_lanucher.png(系统默认在xxhdpi包下)分别放置在hdpi,xhdpi,xxhdpi三个文件夹,打印出位图的大小.
“`java
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.ic_launcher);
Log.d(TAG, "size:" + bitmap.getByteCount());
“`
“`java
hdpi包size:331776
xhdpi包size:186624
xxhdpi包size:82944
“`
我们知道一张144*144
的 ic_lanucher.png所占的实际内存为 144*144*4=82944
字节,那么为什么同一张图片放在不同包下表现不一样的大小?
屏幕密度与Drawable目录有着如下的关系:
目录 | 屏幕密度 |
---|---|
drawable-ldpi | 120dpi |
drawable-mdpi | 160dpi |
drawable-hdpi | 240dpi |
drawable-xhdpi | 320dpi |
drawable-xxhdpi | 480dpi |
当使用decodeResuore()解码drawable目录下的图片时, 会根据手机的屏幕密度,到对应的文件夹中查找图片,如果图片存在于其他目录,则会对该图片进行放缩处理在显示,放缩处理的规则:
scale= 设备屏幕密度/drawable目录设定的屏幕密度
图片内存=int(图片长度*scale+0.5f)* int(图片宽度*scale)*单位像素占字节数
由于实验使用的小米3,屏幕密度为480,则当图片放入在hdpi时:scale= 480/240;
图片放入xhdpi:scale=480/320
;
图片放入xxhdpi时:scale= 480/480
;
说完系统加载位图使用的放缩规则后,再来说说这三个标记之间的关系:
inDesity: 位图使用的像素密度
inTargetDesity: 设备的屏幕密度
inScale: 是否需要放缩位图
清楚这三者的含义,就可以在加载图片时,根据图片在不同设备上的使用,可以放缩来加载位图:
放缩规则 scale= inTargetDensity/inDesity;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = true;
options.inDensity = getBitmapDensity();
options.inTargetDensity =Resources.getSystem().getDisplayMetrics().densityDpi ;
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher, options);