前言
关于bitmap的学习。研究bitmap使用时,占用的内存和一些计算的原理,以及Bitmap优化相关内容
Bitmap占用内存
densityDpi、density说明
以下摘自官方api
/**
* The logical density of the display. This is a scaling factor for the
* Density Independent Pixel unit, where one DIP is one pixel on an
* approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
* providing the baseline of the system's display. Thus on a 160dpi screen
* this density value will be 1; on a 120 dpi screen it would be .75; etc.
*
* <p>This value does not exactly follow the real screen size (as given by
* {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
* the overall UI in steps based on gross changes in the display dpi. For
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
* 320x480 but the screen size remained 1.5"x2" then the density would be
* increased (probably to 1.5).
*
* @see #DENSITY_DEFAULT
*/
public float density;
/**
* The screen density expressed as dots-per-inch. May be either
* {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
*/
public int densityDpi;
趋势如下表
density | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 |
---|---|---|---|---|---|---|---|
densityDpi | 160 | 240 | 320 | 400 | 480 | 560 | 640 |
我们可以通过代码获取到手机的density和densityDpi
//获取densityDpi
getResources().getDisplayMetrics().densityDpi
//获取density
getResources().getDisplayMetrics().density
Bitmap.Config
这边我们需要知道Bitmap有几个像素类型
参数 | 说明 |
---|---|
ALPHA_8 | 表示8位Alpha位图 |
RGB_565 | 表示16位RGB位图 |
ARGB_4444 | 表示16位ARGB位图,这个从API 13 开始被废弃 |
ARGB_8888 | 表示32位ARGB位图 |
不同的配置将会影响图像的画质(色彩深度),位数越高画质越高。
建议:不需要透明度的图片可以使用RGB_565,需要透明度的图片使用ARGB_8888。
占用内存
测试手机和图片如下
测试手机为:小米5s
测试图片为:3200*1800的图片
那么我们就开始计算bitmap占用的内存到底有多少。
首先我们将图片放置在了xxhdpi的目录下,由获取了手机的densityDpi是480
在用Bitmap的方法获取占用资源
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.sakura);
iv.setImageBitmap(bitmap);
if (bitmap != null) {
Log.e("-s-btn1", "size = " + bitmap.getByteCount());
}
打印结果
E/-s-btn1: size = 23040000
我们发现图片占用的内存有 23040000b 也就是21.97M左右的大小
那么我们就来看下为什么有这么大的内存了。
我们先看下getByteCount()
方法
/**
* Returns the minimum number of bytes that can be used to store this bitmap's pixels.
*/
public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}
在深入进去我们会发现这是一个jni方法,也就是计算占用内存的方法其实是用c写的
public final int getRowBytes() {
if (mRecycled) {
Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
}
return nativeRowBytes(mNativePtr);
}
这边就不在深入研究jni了,有想看下去的可以看这篇文章
通过后续jni的代码这边最后知道了计算逻辑是
计算的图片宽度为 int(图片宽度 * 手机dpi / 放置位置dpi + 0.5 )
计算的图片高度为 int(图片高度 * 手机dpi / 放置位置dpi + 0.5 )
然后占用内存为
计算的图片宽度 * 计算的图片高度 * 一个像素点占用的字节数
按照本次测试的图片和手机 我们的计算逻辑为
首先 放置在xxhdpi中 那么 放置位置dpi 为 480
然后是 手机的dpi 为 480
默认的像素类型为ARGB_8888
计算逻辑就是
计算的图片宽度 = int(3200 * 480 / 480f + 0.5) = int(3200.5) = 3200
计算的图片高度 = int(1800 * 480 / 480f + 0.5) = int(1800.5) = 1800
ARGB_8888占用字节为 4
占用内存 = 3200 * 1800 * 4 = 23040000
和打印获取的一模一样
之后我们将图片放到hdpi文件夹下
还是执行相同的代码,打印结果如下
E/-s-btn1: size = 92160000
整整差了4倍。我们也知道hdpi需要转换成xxhdpi也就是需要4倍的差值。
Bitmap优化探究
有了上面的内存占用计算。我们就可以来研究下如何优化Bitmap的内存使用。
图片需要放置在相应的dpi文件夹下
首先我们从上面内存计算的时候发现,放在不同的dpi文件夹下,居然就差了很大的内存占用。所以最优先的就是,将相应的图片放置到相应的dpi文件夹下。
bitmap.compress使用
首先说明下这个方法其实并没有压缩bitmap的内存占用大小
下面是第一个例子代码
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("-s-", "====================================");
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.sakura);
iv.setImageBitmap(bitmap);
if (bitmap != null) {
Log.e("-s-btn1", "size = " + bitmap.getByteCount());
Log.e("-s-btn1", "size = " + bitmap.getByteCount() / 1024.00 / 1024 + "mb");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG,10,baos);
byte[] bytes = baos.toByteArray();
Log.e("-s-btn1", "compress size = " + bytes.length);
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
iv2.setImageBitmap(bitmap);
if (bitmap != null) {
Log.e("-s-btn1", "size2 = " + bitmap.getByteCount());
Log.e("-s-btn1", "size2 = " + bitmap.getByteCount() / 1024.00 / 1024 + "mb");
}
}
});
log和图片如下
E/-s-: ====================================
E/-s-btn1: size = 23040000
E/-s-btn1: size = 21.97265625mb
E/-s-btn1: compress size = 85665
E/-s-btn1: size2 = 23040000
E/-s-btn1: size2 = 21.97265625mb
虽然我们看到读取的压缩length确实变小了,而且图片也变得模糊了。上面是原图,下面是compress过的图片。但是使用的时候还是一样的大小。
压缩图片的宽高
我们从上面就知道了,占用内存是=宽 * 高 * 像素占位,那么我么缩放图片的宽高就可以压缩图片的内存大小了。
代码如下
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("-s-", "====================================");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.sakura,options);
int w = options.outWidth;
int h = options.outHeight;
String type = options.outMimeType;
Log.e("-s-btn2", "w = " + w);
Log.e("-s-btn2", "h = " + h);
Log.e("-s-btn2", "type = " + type);
options.inSampleSize = 3;
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.sakura,options);
w = options.outWidth;
h = options.outHeight;
type = options.outMimeType;
Log.e("-s-btn2", "w2 = " + w);
Log.e("-s-btn2", "h2 = " + h);
Log.e("-s-btn2", "type2 = " + type);
iv2.setImageBitmap(bitmap);
if (bitmap != null) {
Log.e("-s-btn2", "size2 = " + bitmap.getByteCount());
Log.e("-s-btn2", "size2 = " + bitmap.getByteCount() / 1024.00 / 1024 + "mb");
}
}
});
log如下
E/-s-: ====================================
E/-s-btn2: w = 3200
E/-s-btn2: h = 1800
E/-s-btn2: type = image/jpeg
E/-s-btn2: w2 = 1066
E/-s-btn2: h2 = 600
E/-s-btn2: type2 = image/jpeg
E/-s-btn2: size2 = 2558400
E/-s-btn2: size2 = 2.43988037109375mb
这边我们可以看到,相当明显的内存占用压缩了,而且对于大图来说,确实并没有造成很大视觉影响。
像素占位压缩
还是依据占用内存是=宽 * 高 * 像素占位,我们对最后的像素占位进行修改
代码如下
btn3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("-s-", "====================================");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.sakura,options);
iv2.setImageBitmap(bitmap);
if (bitmap != null) {
Log.e("-s-btn3", "size2 = " + bitmap.getByteCount());
Log.e("-s-btn3", "size2 = " + bitmap.getByteCount() / 1024.00 / 1024 + "mb");
}
}
});
log如下
E/-s-: ====================================
E/-s-btn3: size2 = 11520000
E/-s-btn3: size2 = 10.986328125mb
整体缩小了一倍,因为从原来的ARGB_8888变成了RGB_565,就变小了一倍。
总结
对于bitmap优化可以结合缩放图和像素占位进行修改。
依据占用内存是=宽 * 高 * 像素占位只要修改其中的一个值,那么整个内存占用就会变小。