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.h static 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 ); } } #endif if (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() = null D/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区分 ...
随机推荐
- sql标准化的后缀
今天在SQL编码风格中看到的sql编码标准
- ### Theano
Theano. #@author: gr #@date: 2014-07-02 #@email: forgerui@gmail.com 一.安装Theano ubuntu下安装相对简单. 安装依赖: ...
- WPF嵌入百度地图完整实现
无论是做App还是web开发,很多都会用到地图功能,一般都会调用第三方的API实现地图功能!而正如国内的地图API提供方,基本上对Android.IOS和web开发提供了很完整的一套API,但是对于桌 ...
- (转)解读Flash矩阵
转自: http://hi.baidu.com/cabtw/item/d2dbd212d4ae3e9398ce337f 图片看不到请去原网站看 Matrix: scale(a,d); 比例变换就是将平 ...
- Redis多机集群
Redis集群.网上很多教程,只是按着它的步骤来做只能在单机上跑,而已不有点抗.也不用密码验证 开始: 1:redis集群最少需要要6个服务器端,因此先搞6台虚拟机 我用 centOS-7 mini ...
- (转)MySQL Workbench的使用教程 (初级入门版)
转自:http://www.cnblogs.com/yqskj/archive/2013/03/01/2938027.html MySQL Workbench 是 MySQL AB 最近释放的可视数据 ...
- Jar mismatch! Fix your dependencies的问题(转)
看到网上有说: 在开发Android项目的时候,有时需要引用多个项目作为library.在引用项目的时候,有时会出现“Jar mismatch! Fix your dependencies”错误. 这 ...
- Eat that Frog
Eat that Frog,中文翻译过来就是“吃掉那只青蛙”.不过这里并不是讨论怎么去吃青蛙,而是一种高效的方法. Eat that Frog是Brian Tracy写的一本书(推荐阅读).这是一个很 ...
- position:absolute,绝对定位和相对定位,JQ隐藏和显示
需要在指定位置,用绝对定位. 如果直接写position:absolute,top:0;left:0,那就是以浏览器的左上角为参照了 现在需要在某一个指定位置用绝对定位 解决方法 在需要用绝对定位(p ...
- javascript检测是否安装了flash
检测是否安装了flash function flashChecker() { var hasFlash = 0; //是否安装了flash var flashVersion = 0; //flash版 ...