BitMap内存占用

BitMap加载后,占用内存大小:

width * height *每个像素占的字节数

代码中获取bitmap占用内存大小的计算:

public final int getByteCount() @Bitmap.java{
    return getRowBytes() * getHeight();
}

其中的getRowBytes()调用的是native方法,具体在android源码中就是SkBitmap.cpp中的实现。

SkBitmap.h    
/** Returns row bytes, the interval from one pixel row to the next. Row bytes
        is at least as large as width() * info().bytesPerPixel().

        Returns zero if colorType() is kUnknown_SkColorType, or if row bytes supplied to
        setInfo() is not large enough to hold a row of pixels.

        @return  byte length of pixel row
    */
    size_t rowBytes() const { return fPixmap.rowBytes(); }

常见的颜色类型kRGB_565_SkColorType,一个像素占2个字节,

kRGBA_8888_SkColorType 一个像素占4个字节。

占用内存跟容器格式也就是文件后缀名没有关系,但是跟文件放在drawable的那个目录有关系,前提同一张图片,同时只在一个drawable目录存在。

一张477*550的图片,放在drawable-xhdpi中,

如代码:

    public void decodeBitmap() {
        Bitmap jpgFile = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.pngfile);
        Log.d(TAG,"jpgfile,width="+jpgFile.getWidth()+",jpgfile,height="+jpgFile.getHeight()
            +",jpgfile,byteCount="+jpgFile.getByteCount()+",,,="+jpgFile.getDensity());

        Bitmap bmpFile = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.bmpfile);
        Log.d(TAG,"pngfile,width="+bmpFile.getWidth()+",pngfile,height="+bmpFile.getHeight()
                +",pngfile,byteCount="+bmpFile.getByteCount());

        Bitmap pngFile = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.jpgfile);
        Log.d(TAG,"pngfile,width="+pngFile.getWidth()+",pngfile,height="+pngFile.getHeight()
                +",pngfile,byteCount="+pngFile.getByteCount()+",,,="+pngFile.getDensity());
    }

/*output*/

2020-01-02 09:18:10.967 9850-9850/? D/com.jlq.mainthreaddemo.DecodeBitmap: jpgfile,width=477,jpgfile,height=550,jpgfile,byteCount=1049400,,,=320
2020-01-02 09:18:10.986 9850-9850/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=477,pngfile,height=550,pngfile,byteCount=1049400
2020-01-02 09:18:11.000 9850-9850/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=477,pngfile,height=550,pngfile,byteCount=1049400,,,=320

把那三张图片放在drawable-xxhdpi中,会缩小图片

/*output*/

2020-01-02 09:31:07.659 10130-10130/? D/com.jlq.mainthreaddemo.DecodeBitmap: jpgfile,width=318,jpgfile,height=367,jpgfile,byteCount=466824,,,=320
2020-01-02 09:31:07.680 10130-10130/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=318,pngfile,height=367,pngfile,byteCount=466824
2020-01-02 09:31:07.696 10130-10130/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=318,pngfile,height=367,pngfile,byteCount=466824,,,=320

放在drawable-hdpi中,会放大图片。

/*output*/

2020-01-02 09:35:23.787 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: jpgfile,width=636,jpgfile,height=733,jpgfile,byteCount=1864752,,,=320
2020-01-02 09:35:23.821 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=636,pngfile,height=733,pngfile,byteCount=1864752
2020-01-02 09:35:23.850 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=636,pngfile,height=733,pngfile,byteCount=1864752,,,=320

获取当前设备的屏幕密度:

    public void getDisplayMetrics() {
        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
        mDensity = dm.density;
        mDensityDpi = dm.densityDpi;
        TypedValue tv = new TypedValue();
        Log.d(TAG,"mDensity="+mDensity+",mDensityDpi="+mDensityDpi);
    }
2020-01-02 09:35:23.747 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: mDensity=2.0,mDensityDpi=320

跟当前设备匹配的资源目录是drawable-xhdpi,对应屏幕密度是320dpi。

设备常用的屏幕密度:

  • ldpi(低): ~120 dpi 3

  • mdpi(中):120~160dpi

  • hdpi(高):160~240dpi

  • xhdpi(超高):240~320dpi

  • xxhdpi(超超高):320~480dpi

  • xxxhdpi(超超超高):480~640dpi 

通过上面的测试,图片放的位置如果跟当前的屏幕密度不一致,会对图片做缩放。具体的缩放算法:

实际加载后的图片尺寸 = (原图片的尺寸 * 目标设备的屏幕密度) / 图片放置目录的dpi等级。

依据这个公式可以算出,在把图片放置在drawable-xxhdpi中时,实际的图片宽度 = (477(原图宽度)* 320(屏幕密度))/480  (xxhdpi的等级) 。结果是318,跟代码输入是一致的。

下面分析下具体代码实现是不是这样的。

第一步,从decodeResource开始看起,

//仅关心跟图片缩放相关代码   
 public static Bitmap decodeResource(Resources res, int id, Options opts) @BitmapFactory.java{
        validate(opts);
        Bitmap bm = null;
        InputStream is = null; 
        
        try {
            final TypedValue value = new TypedValue();
            //从文件读取图片,转成输入流,这里会解析图片放置目录的dpi等级,并以出参value返回
            is = res.openRawResource(id, value);
            //根据解析出的图片目录的dpi等级,及设备密度,做缩放
            bm = decodeResourceStream(res, value, is, null, opts);
        } 
        return bm;
    }

第二步,跟踪openRawResource的执行,跳过参数透传,看AssetManager.java中的实现。

//从参数 TypedValue outValue的名字有out前缀,可以看出,这是一个出参,也就是说BitmapFactory中创建
//的TypedValue对象,只是一个空壳,里面的实际参数值,会在接下来的解析中去填充。
    boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs)@AssetManager.java {
        synchronized (this) {
            final int cookie = nativeGetResourceValue(
                    mObject, resId, (short) densityDpi, outValue, resolveRefs);

            // Convert the changing configurations flags populated by native code.
            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
            }
            return true;
        }
    }

调到jni层

//解析资源是通过AssetManager2来执行的
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                   jshort density, jobject typed_value,
                                   jboolean resolve_references) @android_util_AssetManager.cpp{
  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  Res_value value;
  ResTable_config selected_config;
  uint32_t flags;
  ApkAssetsCookie cookie =
      assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
                                static_cast<uint16_t>(density), &value, &selected_config, &flags);

  }

先用锁装饰了AAssetManager,实际完成解析的还是AAssetManager2中的实现,也就是AssetManager2.cpp中的

ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
                                           uint16_t density_override, Res_value* out_value,
                                           ResTable_config* out_selected_config,
                                           uint32_t* out_flags) const {}
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
                                         bool /*stop_at_first_match*/,
                                         FindEntryResult* out_entry) const {}

其中的结构体,ResTable_config,对应了资源的类别。

对资源的解析,需要后续在做补充。。。

解析的图片所在目录的dpi等级,会作用到BitmapFactory.Options中,

    public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
            @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
        validate(opts);
        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
//这里value.density的值,会随着图片放置的目录变化,如log打印
            final int density = value.density;
			Log.e("BitmapFactory", "decodeResourceStream:" + value.density);
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }
        
        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
        
        return decodeStream(is, pad, opts);
    }

把图片分别放在hdpi,xxhdpi时:

2020-01-20 16:59:45.739 3723-3723/com.jlq.mainthreaddemo E/BitmapFactory: decodeResourceStream:240

2020-01-20 17:00:33.697 3806-3806/? E/BitmapFactory: decodeResourceStream:480

第三步,把元数据转成流后,接下来就是从流解码成bitmap,并做缩放。

具体从BitmapFactory.java开始

    public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
            @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {}

转到native代码;

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options)@BitmapFactory.cpp {}

static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
                        jobject padding, jobject options) @BitmapFactory.cpp{
 // Update with options supplied by the client.
    if (options != NULL) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        // Correct a non-positive sampleSize.  sampleSize defaults to zero within the
        // options object, which is strange.
        if (sampleSize <= 0) {
            sampleSize = 1;
        }

        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
//这里计算出缩放因子
                scale = (float) targetDensity / density;
            }
        }
    }
    
//对图片做缩放
    // Scale is necessary due to density differences.
    if (scale != 1.0f) {
        willScale = true;
        scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
        scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值