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区分 ...
随机推荐
- MyBatis拦截器:给参数对象属性赋值
该拦截器的作用:在进行增加.修改等操作时,给数据模型的一些通用操作属性(如:创建人.创建时间.修改人.修改时间等)自动赋值. 该实现是在DAO层拦截,即存入DB前最后一层.后经分析,不是很合理,改为在 ...
- C++中map用法
/************************************************************************** Map的特点: 1.存储Key-value对* ...
- [Guava官方文档翻译] 2.使用和避免使用null (Using And Avoiding Null Explained)
本文地址:http://www.cnblogs.com/hamhog/p/3536647.html "null很恶心." -Doug Lea "这是一个令我追悔莫及的错误 ...
- hdu 1316 How many Fibs?(高精度斐波那契数)
// 大数继续 Problem Description Recall the definition of the Fibonacci numbers: f1 := 1 f2 := 2 fn : ...
- RX学习笔记:正则表达式
正则表达式 2016-07-03 正则表达式是以字符串模板的形式匹配查找字符的方式. 正则表达式是字符串模板,所以其本身是一个字符串,首尾以反斜杆 / 开始和结束. 在两反斜杆中间的字符串表示要查找的 ...
- 目前IT行业的几个大方向
我简单总结了一下目前it行业的8大方向: 1.嵌入式开发 传统的arm linux开发.新兴的智能硬件.物联网等技术的发展,都让整个方向成为热门领域. 2.游戏开发 cocos2d-x.uni ...
- 关闭一个winform窗体刷新另外一个
例如Form1是你的主窗体,然后Form2是你的要关闭那个窗体,在Form1中SHOW FORM2的窗体那里加上一句f2.FormClosed += new FormClosedEventHandle ...
- 用PHP实现单向链表
供参考,代码还可继续打磨 同时放在了我的github上:https://github.com/hheedat/demo/blob/master/learn_php/58_linked_list.php ...
- 整理grep实战文本搜索过滤技巧
一:grep的简介: 文本搜索工具,根据用户指定的文本模式对目标文件进行逐行搜索,显示能够被模式所匹配到的行.配合正则表达式的使用可以实现强大的文本处理.下面一一说明正则的例子. 二:文本处理工具分类 ...
- 《C和指针》读书笔记——第二章 基本概念
1.编译过程: source code→Compiler→Object code→Linker←Lib ↓ Exe 2.经过初始化的静态变量(static)在程序执行前能获得他们的值. 3.绝大多数环 ...