Android中一张图片需要占用多少内存

您所在的位置:网站首页 一张照片内存多大 Android中一张图片需要占用多少内存

Android中一张图片需要占用多少内存

2024-02-08 06:43| 来源: 网络整理| 查看: 265

前置概念-屏幕密度

搞清楚 DisplayMetrics 的两个变量, density 是显示的逻辑密度,是密度与独立像素单元的比例因子, densityDpi 是屏幕每英寸对应多少个点

关于DisplayMetrics更多细节点击这里

图片占内存多少的计算原理

找到每个像素占用的字节数*总像素数即可

Android API 有个方便的方法可以获取到占用的内存大小

public final int getByteCount() { // int result permits bitmaps up to 46,340 x 46,340 return getRowBytes() * getHeight(); }

getHeight 就是图片的高度(单位:px) 那么getrowBytes()呢

public final int getrowBytes() { if (mRecycled) { Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!"); } return nativeRowBytes(mFinalizer.mNativeBitmap); } #Bitmap.cpp static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) { SkBitmap* bitmap = reinterpret_cast(bitmapHandle) return static_cast(bitmap->rowBytes()); } #SkBitmap.h /** Return the number of bytes between subsequent rows of the bitmap. */ size_t rowBytes() const { return fRowBytes; } # SkBitmap.cpp 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 //实际上,我们这里的opts是null的,所以在这里初始化。 if (opts == null) { opts = new Options(); } if (opts.inDensity == 0 && value != null) { //密度等于TypedValue.DENSITY_NONE,那么就没有与资源相关的密度,它不应该被缩放 final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) { //密度等于这个值,那么这个密度应该被视为系统的默认密度值 opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;//默认密度160 } 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); }

我们重点关注两个值 inDensity 和 inTargetDensity,他们与BitmapFactory.cpp文件里面的 density 和 targetDensity相对应 inDensity 就是原始资源的 density,inTargetDensity 就是屏幕的 density。 接着,用到了 nativeDecodeStream 方法,其中最关键的 doDecode 函数的代码:

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"); } //这里这个decodingBitmap就是解码出来的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); } ...... }

density 其实是decodingBitmap的 densityDpi ,跟这张图片的放置的目录有关(比如 hdpi 是240,xxhdpi 是480), targetDensity 实际上是我们加载图片的目标 densityDpi,三星s6为640。sx 和sy 实际上是约等于 scale 的,因为 scaledWidth 和 scaledHeight 是由 width 和 height 乘以 scale 得到的。我们看到 Canvas 放大了 scale 倍,然后又把读到内存的这张 bitmap 画上去,相当于把这张 bitmap 放大了 scale 倍。

所以回到上面 一张522*686的PNG 图片,我把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B,其中 density 对应 xxhdpi 为480,targetDensity 对应三星s6的密度为640:

522/480 * 640 * 686/480 *640 * 4 = 2546432B

值还是不一样

精度影响内存占用 outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, colorType, decodingBitmap.alphaType()));

最终输出的 outputBitmap 的大小是scaledWidth*scaledHeight,

if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); }

在我们的例子中,

scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696

scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915

915 * 696 * 4 = 2547360

Bitmap 在内存当中占用的大小的影响因素

色彩格式,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节

原始文件存放的资源目录

目标屏幕的密度

如何优化

知道了原因,那么据此即可优化内存使用。 详情可以查看优化Bitmap内存占用



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3