Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?
0、写在前面
- density: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.This value does not exactly follow the real screen size (as given by xdpi and 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).
- densityDpi:The screen density expressed as dots-per-inch.
| density | 1 | 1.5 | 2 | 3 | 3.5 | 4 |
| densityDpi | 160 | 240 | 320 | 480 | 560 | 640 |
1、占了多大内存?
|
1
2
3
4
|
public final int getByteCount() { // int result permits bitmaps up to 46,340 x 46,340 return getRowBytes() * getHeight();} |
2、给我一张图我告诉你占多大内存
2.1 getByteCount
|
1
2
3
4
5
6
|
public final int getrowBytes() { if (mRecycled) { Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!"); } return nativeRowBytes(mFinalizer.mNativeBitmap);} |
|
1
2
3
4
|
static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) { SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle) return static_cast<jint>(bitmap->rowBytes());} |
|
1
2
|
/** Return the number of bytes between subsequent rows of the bitmap. */size_t rowBytes() const { return fRowBytes; } |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
size_t SkBitmap::ComputeRowBytes(Config c, int width) { return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);}SkImageInfo.hstatic int SkColorTypeBytesPerPixel(SkColorType ct) { static const uint8_t gSize[] = { 0, // Unknown 1, // Alpha_8 2, // RGB_565 2, // ARGB_4444 4, // RGBA_8888 4, // BGRA_8888 1, // kIndex_8 }; SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1), size_mismatch_with_SkColorType_enum); SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize)); return gSize[ct];}static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) { return width * SkColorTypeBytesPerPixel(ct);} |
2.2 Density
- 读取原始资源,这个调用了 Resource.openRawResource 方法,这个方法调用完成之后会对 TypedValue 进行赋值,其中包含了原始资源的 density 等信息;
- 调用 decodeResourceStream 对原始资源进行解码和适配。这个过程实际上就是原始资源的 density 到屏幕 density 的一个映射。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) {//实际上,我们这里的opts是null的,所以在这里初始化。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; //这里density的值如果对应资源目录为hdpi的话,就是240 }}if (opts.inTargetDensity == 0 && res != null) {//请注意,inTargetDensity就是当前的显示密度,比如三星s6时就是640 opts.inTargetDensity = res.getDisplayMetrics().densityDpi;}return decodeStream(is, pad, opts);} |
|
1
2
3
4
5
|
public Options() { inDither = false; inScaled = true; inPremultiplied = true;} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {...... if (env->GetBooleanField(options, gOptions_scaledFieldID)) { const int density = env->GetIntField(options, gOptions_densityFieldID);//对应hdpi的时候,是240 const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的为640 const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { scale = (float) targetDensity / density; } }}const bool willScale = scale != 1.0f;......SkBitmap decodingBitmap;if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) { return nullObjectReturn("decoder->decode returned false");}//这里这个deodingBitmap就是解码出来的bitmap,大小是图片原始的大小int scaledWidth = decodingBitmap.width();int scaledHeight = decodingBitmap.height();if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f);}if (willScale) { const float sx = scaledWidth / float(decodingBitmap.width()); const float sy = scaledHeight / float(decodingBitmap.height()); // TODO: avoid copying when scaled size equals decodingBitmap size SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType()); // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, colorType, decodingBitmap.alphaType())); if (!outputBitmap->allocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } // If outputBitmap's pixels are newly allocated by Java, there is no need // to erase to 0, since the pixels were initialized to 0. if (outputAllocator != &javaAllocator) { outputBitmap->eraseColor(0); } SkPaint paint; paint.setFilterLevel(SkPaint::kLow_FilterLevel); SkCanvas canvas(*outputBitmap); canvas.scale(sx, sy); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);}......} |
2.3 精度
|
1
2
|
outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, colorType, decodingBitmap.alphaType())); |
|
1
2
3
4
|
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f);} |
“源码之前,了无秘密”。
2.4 小结
- 色彩格式,前面我们已经提到,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节
- 原始文件存放的资源目录(是 hdpi 还是 xxhdpi 可不能傻傻分不清楚哈)
- 目标屏幕的密度(所以同等条件下,红米在资源方面消耗的内存肯定是要小于三星S6的)
3、想办法减少 Bitmap 内存占用
3.1 Jpg 和 Png
『啪!!!』『谁这么缺德!!打人不打脸好么!』
JPG 不适用于所含颜色很少、具有大块颜色相近的区域或亮度差异十分明显的较简单的图片。对于需要高保真的较复杂的图像,PNG 虽然能无损压缩,但图片文件较大。
- alpha 你是否真的需要?如果需要 alpha 通道,那么没有别的选择,用 png。
- 你的图色值丰富还是单调?就像刚才提到的,如果色值丰富,那么用jpg,如果作为按钮的背景,请用 png。
- 对安装包大小的要求是否非常严格?如果你的 app 资源很少,安装包大小问题不是很凸显,看情况选择 jpg 或者 png(不过,我想现在对资源文件没有苛求的应用会很少吧。。)
- 目标用户的 cpu 是否强劲?jpg 的图像压缩算法比 png 耗时。这方面还是要酌情选择,前几年做了一段时间 Cocos2dx,由于资源非常多,项目组要求统一使用 png,可能就是出于这方面的考虑。
3.2 使用 inSampleSize
|
1
2
3
|
BitmapFactory.Options options = new Options();options.inSampleSize = 2;Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options); |
3.3 使用矩阵
『基友』『是在下输了。。』
|
1
2
3
4
5
|
Matrix matrix = new Matrix();matrix.preScale(2, 2, 0f, 0f);//如果使用直接替换矩阵的话,在Nexus6 5.1.1上必须关闭硬件加速canvas.concat(matrix);canvas.drawBitmap(bitmap, 0,0, paint); |
|
1
2
3
|
Matrix matrix = new Matrix();matrix.preScale(2, 2, 0, 0);canvas.drawBitmap(bitmap, matrix, paint); |
|
1
2
3
4
5
|
Matrix matrix = new Matrix();matrix.postScale(2, 2, 0, 0);imageView.setImageMatrix(matrix);imageView.setScaleType(ScaleType.MATRIX);imageView.setImageBitmap(bitmap); |
3.4 合理选择Bitmap的像素格式
| 格式 | 描述 |
| ALPHA_8 | 只有一个alpha通道 |
| ARGB_4444 | 这个从API 13开始不建议使用,因为质量太差 |
| ARGB_8888 | ARGB四个通道,每个通道8bit |
| RGB_565 | 每个像素占2Byte,其中红色占5bit,绿色占6bit,蓝色占5bit |
3.5 高能:索引位图(Indexed Bitmap)
|
01
02
03
04
05
06
07
08
09
10
|
public enum Config { // these native values must match up with the enum in SkBitmap.h ALPHA_8 (2), RGB_565 (4), ARGB_4444 (5), ARGB_8888 (6); final int nativeInt;} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
enum Config { kNo_Config, //!< bitmap has not been configured kA8_Config, //!< 8-bits per pixel, with only alpha specified (0 is transparent, 0xFF is opaque) //看这里看这里!!↓↓↓↓↓ kIndex8_Config, //!< 8-bits per pixel, using SkColorTable to specify the colors kRGB_565_Config, //!< 16-bits per pixel, (see SkColorPriv.h for packing) kARGB_4444_Config, //!< 16-bits per pixel, (see SkColorPriv.h for packing) kARGB_8888_Config, //!< 32-bits per pixel, (see SkColorPriv.h for packing) kRLE_Index8_Config, kConfigCount}; |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr, SkColorType* colorTypep, bool* hasAlphap, SkPMColor* SK_RESTRICT theTranspColorp) {png_uint_32 origWidth, origHeight;int bitDepth, colorType;png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, &colorType, int_p_NULL, int_p_NULL, int_p_NULL);#ifdef PNG_sBIT_SUPPORTED // check for sBIT chunk data, in case we should disable dithering because // our data is not truely 8bits per component png_color_8p sig_bit; if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {#if 0 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green, sig_bit->blue, sig_bit->alpha);#endif // 0 seems to indicate no information available if (pos_le(sig_bit->red, SK_R16_BITS) && pos_le(sig_bit->green, SK_G16_BITS) && pos_le(sig_bit->blue, SK_B16_BITS)) { this->setDitherImage(false); }}#endifif (colorType == PNG_COLOR_TYPE_PALETTE) { bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr); *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha); // now see if we can upscale to their requested colortype //这段代码,如果返回false,那么colorType就被置为索引了,那么我们看看如何返回false if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) { *colorTypep = kIndex_8_SkColorType; }} else {...... }return true;} |
|
01
02
03
04
05
06
07
08
09
10
11
12
|
static bool canUpscalePaletteToConfig(SkColorType dstColorType, bool srcHasAlpha) { switch (dstColorType) { case kN32_SkColorType: case kARGB_4444_SkColorType: return true; case kRGB_565_SkColorType: // only return true if the src is opaque (since 565 is opaque) return !srcHasAlpha; default: return false;}} |
|
01
02
03
04
05
06
07
08
09
10
|
try { Options options = new Options(); options.inPreferredConfig = Config.RGB_565;Bitmap bitmap = BitmapFactory.decodeStream(getResources().getAssets().open("index.png"), null, options); Log.d(TAG, "bitmap.getConfig() = " + bitmap.getConfig()); Log.d(TAG, "scaled bitmap.getByteCount() = " + bitmap.getByteCount()); imageView.setImageBitmap(bitmap);} catch (IOException e) { e.printStackTrace();} |
192 * 192 *4=147456
|
1
2
|
D/MainActivity: bitmap.getConfig() = nullD/MainActivity: scaled bitmap.getByteCount() = 36864 |
public final Bitmap.Config getConfig ()Added in API level 1If the bitmap’s internal config is in one of the public formats, return that config, otherwise return null.
3.6 不要辜负。。。『哦,不要姑父!』
『排期太紧了,这些给我出一系列图吧』『好,不过每张图都是 300*30 0的 png 哈,总共 5 张,为了适配不同的分辨率,需要出 xxhdpi 和 xxxhdpi 的两套图。。』
4、结语
Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?的更多相关文章
- Android开发需要注意的坑
Android开发需要注意的坑一览对于一些Android开发过程中坑爹.细小,但又重要的错误的总结Android开发在路上:少去踩坑,多走捷径其他参考: google官方版本发布图 umeng ...
- Android开发之常用必备工具类图片bitmap转成字符串string与String字符串转换为bitmap图片格式
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 QQ986945193 博客园主页:http://www.cnblogs.com/mcxiaobing ...
- 那些年,Android开发踩过的坑
首先讲一讲环境配置吧,一般刚上手Android编程,推荐的两款软件开发工具有Eclipse和Andriod Studio,配置环境来讲呢,Android Studio配置环境要快得多,而且比起Ecli ...
- 那些年Android开发中遇到的坑
使用静态变量来缓存数据时,不管是在Application类还是其他类,都要注意因应用重建而引发的问题. 使用DecorView作为PopupWindow的anchorView时,在华为P7中它是显示在 ...
- android开发里遇到的坑——eclipse项目导入android studio以后Run按钮灰色
android studio编译有错误但是没有提示给用户,关闭android studio重新启动项目后,会显示错误,修复以后即可!
- android开发里跳过的坑——android studio打包的APK签名无效
近期把一个项目从eclipse上移植到了android studio, 在打包发布APK的时候,应用上传到应用市场时提示取不到签名.但是,我确实使用了 做过签名了. 然后换了一种打包方式 build ...
- android开发里跳过的坑-AS导入NDK工程提示错误 No such property: sdkHandler for class: com.android.build.gradle.LibraryPlugin
接到一个NDK工程需要调试,导入后发现总是提示错误 Error:(37, 1) A problem occurred evaluating project ':libuvccamera'.> N ...
- android开发里跳过的坑——“org.apache.http.message.BasicHeaderValueFormatter.INSTANCE”错误
在android4.4.2的系统里,写了一个系统应用,其中有一个功能是通过表单上传图片的,使用了httpclient-4.5.3.jar httpmime-4.5.3.jar httpcore-4.4 ...
- android开发里跳过的坑——android studio升级完成后eclipse adt无法正常使用
最近有时间,把android studio做了一次升级,升级完成后,悲催的发现eclipse不能正常运行了,网上查了好多资料,试了很多方法都不行,最后把eclipse使用的sdk与AS使用的SDK区分 ...
随机推荐
- MFC程序实现窗口分割,视图快捷插入控件和插入列表
将视图中插入列表: 1.创建一个MFC应用程序,在MFC Wizard中,生成的类选项,如图 2.选择CListView作为基类 3.在CXXView.cpp(XX为你的程序名)重写虚函数OnInit ...
- 常用终端及git命令
终端常用命令 1,打开终端,git version 查看版本 2,pwd 打印工作目录 3,ls(list简写)查看当前目录的所有文件 4,clear 清掉屏幕 5,cd (change direct ...
- QT 常用设置
博文都写在了云笔记里面了,见谅,不想维护两个版本. QT 常用设置
- MySql启动提示:The server quit without updating PID file(…)失败
在网上找了很多 1.可能是/usr/local/mysql/data/rekfan.pid文件没有写的权限解决方法 :给予权限,执行 “chown -R mysql:mysql /var/data” ...
- 队列(顺序存储)C++模板实现
队列:一端进行插入,另一端进行删除的线性结构,具有先进先出性.利用数组来实现队列将面临"假溢出"的情况,如下图所示: front:永远指向队首元素,队首在本文中是允许删除元素的一端 ...
- Java添加事件的四种方式
Java添加事件的几种方式(转载了codebrother的文章,做了稍微的改动) /** * Java事件监听处理——自身类实现ActionListener接口,作为事件监听器 * * @author ...
- jQuery—一些常见方法(3)【width(),innerWidth(),outerWidth()】
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- mysql 高性能
第一章节:共享锁(读锁),排他锁(写锁) 查询数据表所使用的存储引擎:show table status like '表名' \G 转换数据表的存储引擎:alter table 表名 engine=引 ...
- php判断手机浏览还是web浏览,并执行相应的动作
正好需要,在网上找了好久,记录一下 function isMobile(){ $useragent=isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTT ...
- 【springmvc Request】 springmvc请求接收参数的几种方法
通过@PathVariabl注解获取路径中传递参数 转载请注明出处:springmvc请求接收参数的几种方法 代码下载地址:http://www.zuida@ima@com/share/1751862 ...