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区分 ...
随机推荐
- war包编译和打包发布
用IDE写一个基本的webApp 要学习java web技术,除了javaSE基本功之外,基础知识还有servlet技术.我们如果只用IDE的话,会把很多问题屏蔽掉,很多细节就想不清楚了.最好的方式, ...
- 用dubbo+zookeeper+spring搭建一个简单的http接口程序
dubbo是一个分布式服务框架,是阿里巴巴开发的一个解决RPC远程调用优化的核心框架,包含负载均衡算法,能提高分布式系统的性能. zookeeper是hadoop的一个子项目,主要用来解决分布式系统的 ...
- vs调试windows mobile程序时布署时间太长的解决办法
vs调试windows mobile程序时布署时间太长的解决办法 1.VS平台上,选工具-选项-项目和解决方案-MS BUILD项目生成输出详细信息中选择“诊断”,目的是在调试窗口中看出哪个过程编译的 ...
- OC6_类方法
// // Dog.h // OC6_类方法 // // Created by zhangxueming on 15/6/9. // Copyright (c) 2015年 zhangxueming. ...
- mysql innodb 数据打捞(三)innodb 簇不连接页的扫描提取(计划)
操作系统簇大小一般是4K,而innoDB的页大小一般是16K,那么就有可能16K的页没有存储在连续的簇中,这样扫描软件就不会扫描出来这样的页面.为了解决这个问题,决定给软件增加半页扫描功能. 在第一次 ...
- JQuery中的动画
一.show()方法和hide()方法 这两种方法是jQuery动画的最基本方法.当为元素调用show方法时相当于将该元素的display样式改为block或者inline,同理,如果当元素调用hid ...
- php QQ登录
基本原理: 就是获取唯一的openid,此值只要与自己数据库表中的值对应,就说明是此用户, 没有,则说明是新用户,其实就是找对应关系,因为openid与QQ号是唯一对应关系 放置按钮: 如在首页 in ...
- MIT 2012分布式课程基础源码解析-线程池实现
主要内容 ScopedLock 队列实现 线程池实现 在正式讲解线程池实现之前,先讲解两个有用的工具类: ScopedLock fifo队列 ScopedLock: ScopedLock是局域锁的实现 ...
- MySQL中SQL语句的分类
1:数据定义语言(DDL) :创建和删除数据库(CREATE DATABASE || DROP DATABASE):2:创建.修改.重命名.删除表(CREATE TABLE || ALTER TA ...
- apache开启gzip的方法
在Apache中开启gzip压缩方法为: 1. 在httpd.conf 或者博客根目录的.htaccess文件中加入如下规则(Apache服务器需要支持 mod_deflate) 本文出处参考:htt ...