android中加载本地图片到内存

首先先来了解下下面这些名词的关系:

名词 解释
px 屏幕上的点就是由像素点组成的,可以说是绝对的单位
Density 屏幕密度
DensityDpi 每英寸的像素数:160dpi代表着每英寸有160px的像素
Dip 设备独立像素,简称dp,android中控件的大小推荐设置这种单位


Density和DensityDpi的关系:
。。。。 DensityDpi Density
mdpi 160 1
hdpi 240 1.5
xhdpi 320 2
xxhdpi 480 3
????? 560 3.5
xxxhdpi 640 4



不同的手机又着不同的dpi,那么这是怎么计算的呢?

以nexus 6p来计算:5.7’ 1440*2560
由于英寸是屏幕的对角线尺寸,所以这里我们通过屏幕的像素的宽高比,计算出屏幕对角线的像素:2937px
那么,我们接下来就可以计算屏幕每英寸的像素点:2937/5.7=515dpi
归属于560dpi类比的手机


以小米2s来计算:4.7’ 720*1280
由于英寸是屏幕的对角线尺寸,所以这里我们通过屏幕的像素的宽高比,计算出屏幕对角线的像素:1469px
那么,我们接下来就可以计算屏幕每英寸的像素点:1469/4.7=312dpi
通过代码
归属于320dpi(也就是xhdpi)类比的手机


以魅族没休 pro来计算:5.7’ 1080*1920
由于英寸是屏幕的对角线尺寸,所以这里我们通过屏幕的像素的宽高比,计算出屏幕对角线的像素:2203px
那么,我们接下来就可以计算屏幕每英寸的像素点:2203/4.7=469dpi
归属于480dpi(也就是xxhdpi)类比的手机


Bitmap在内存中所占的大小,由宽高像素,和每个像素的颜色通道有关,一般来说,有以下几种颜色通道:

颜色通道 字节
Alpha_8 1
RGB_565 2
ARGB_4444 2
ARGB_8888 4


1.这里是android的decodeResourceStream方法:

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

        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;
            }
        }

        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }

        return decodeStream(is, pad, opts);
    }

opts.inDensity:取决于图片drawable所在的目录对应的density,这个可以从上面的表格中得出答案。
opts.inTargetDensity:就是设备的目标密度值(可以使用res.getDisplayMetrics().densityDpi)方法获得。

2. 在这个方法里面初始化了Options对象:

 public Options() {
            inDither = false;
            inScaled = true;
            inPremultiplied = true;
        }

这里默认设置了inScaled的值为true,就是说明可以根据inDensity和inTargetDensity进行缩放。

3.然后对应的是/ core / jni / android / graphics / BitmapFactory.cpp中的方法

static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        jobject options, bool allowPurgeable, bool forcePurgeable = false,
        bool applyScale = false, float scale = 1.0f) {
    int sampleSize = 1;
    SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
    SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
    bool doDither = true;
    bool isMutable = false;
    //这里需要满足两个条件才可以进行缩放
    bool willScale = applyScale && scale != 1.0f;
    bool isPurgeable = !willScale &&
            (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));
    bool preferQualityOverSpeed = false;
    jobject javaBitmap = NULL;
    if (options != NULL) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        if (optionsJustBounds(env, options)) {
            mode = SkImageDecoder::kDecodeBounds_Mode;
        }
        // initialize these, in case we fail later on
        env->SetIntField(options, gOptions_widthFieldID, -1);
        env->SetIntField(options, gOptions_heightFieldID, -1);
        env->SetObjectField(options, gOptions_mimeFieldID, 0);
        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
        isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
        preferQualityOverSpeed = env->GetBooleanField(options,
                gOptions_preferQualityOverSpeedFieldID);
        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
    }
    if (willScale && javaBitmap != NULL) {
        return nullObjectReturn("Cannot pre-scale a reused bitmap");
    }
    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    if (decoder == NULL) {
        return nullObjectReturn("SkImageDecoder::Factory returned null");
    }
    decoder->setSampleSize(sampleSize);
    decoder->setDitherImage(doDither);
    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
    NinePatchPeeker peeker(decoder);
    JavaPixelAllocator javaAllocator(env);
    SkBitmap* bitmap;
    if (javaBitmap == NULL) {
        bitmap = new SkBitmap;
    } else {
        if (sampleSize != 1) {
            return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
        }
        bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
        // config of supplied bitmap overrules config set in options
        prefConfig = bitmap->getConfig();
    }
    SkAutoTDelete<SkImageDecoder> add(decoder);
    SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL);
    decoder->setPeeker(&peeker);
    if (!isPurgeable) {
        decoder->setAllocator(&javaAllocator);
    }
    AutoDecoderCancel adc(options, decoder);
    // To fix the race condition in case "requestCancelDecode"
    // happens earlier than AutoDecoderCancel object is added
    // to the gAutoDecoderCancelMutex linked list.
    if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
        return nullObjectReturn("gOptions_mCancelID");
    }
    SkImageDecoder::Mode decodeMode = mode;
    if (isPurgeable) {
        decodeMode = SkImageDecoder::kDecodeBounds_Mode;
    }
    //这是解析出来的bitmap,这里有可能设置了simpleSize后的bitmap
    SkBitmap* decoded;
    if (willScale) {
        decoded = new SkBitmap;
    } else {
        decoded = bitmap;
    }
    SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL);
    if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) {
        return nullObjectReturn("decoder->decode returned false");
    }
    //这里获取的是没有进行scale缩放后bitmap
    int scaledWidth = decoded->width();
    int scaledHeight = decoded->height();
    //如果是需要进行缩放的,就根据两个密度值计算并进行缩放
    if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
    //这是计算后需要缩放的宽高
        scaledWidth = int(scaledWidth * scale + 0.5f);
        scaledHeight = int(scaledHeight * scale + 0.5f);
    }
    // update options (if any)
    if (options != NULL) {
        env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
        env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
        env->SetObjectField(options, gOptions_mimeFieldID,
                getMimeTypeString(env, decoder->getFormat()));
    }
    // if we're in justBounds mode, return now (skip the java bitmap)
    if (mode == SkImageDecoder::kDecodeBounds_Mode) {
        return NULL;
    }
    jbyteArray ninePatchChunk = NULL;
    if (peeker.fPatchIsValid) {
        if (willScale) {
            scaleNinePatchChunk(peeker.fPatch, scale);
        }
        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
        if (ninePatchChunk == NULL) {
            return nullObjectReturn("ninePatchChunk == null");
        }
        jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
        if (array == NULL) {
            return nullObjectReturn("primitive array == null");
        }
        peeker.fPatch->serialize(array);
        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    }
    // detach bitmap from its autodeleter, since we want to own it now
    adb.detach();
    if (willScale) {
        // This is weird so let me explain: we could use the scale parameter
        // directly, but for historical reasons this is how the corresponding
        // Dalvik code has always behaved. We simply recreate the behavior here.
        // The result is slightly different from simply using scale because of
        // the 0.5f rounding bias applied when computing the target image size
        const float sx = scaledWidth / float(decoded->width());
        const float sy = scaledHeight / float(decoded->height());
        bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);
        bitmap->allocPixels(&javaAllocator, NULL);
        bitmap->eraseColor(0);
        SkPaint paint;
        paint.setFilterBitmap(true);
        SkCanvas canvas(*bitmap);
        //设置画布缩放
        canvas.scale(sx, sy);
        canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
    }
    if (padding) {
        if (peeker.fPatchIsValid) {
            GraphicsJNI::set_jrect(env, padding,
                    peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
                    peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
        } else {
            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
        }
    }
    SkPixelRef* pr;
    if (isPurgeable) {
        pr = installPixelRef(bitmap, stream, sampleSize, doDither);
    } else {
        // if we get here, we're in kDecodePixels_Mode and will therefore
        // already have a pixelref installed.
        pr = bitmap->pixelRef();
    }
    if (!isMutable) {
        // promise we will never change our pixels (great for sharing and pictures)
        pr->setImmutable();
    }
    if (javaBitmap != NULL) {
        // If a java bitmap was passed in for reuse, pass it back
        return javaBitmap;
    }
    // now create the java bitmap
    return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
            isMutable, ninePatchChunk);
}

比如一张400*800像素的png图片,使用的颜色通道是ARGB_8888,将他放到xhdpi的文件夹中:
xhdpi的手机中(假设denstity为2),内存:400*800*4=1 280 000B.
xxhdpi的手机中(假设denstity为3),内存:400*800*4*3/2=1 920 000B
denstity为4的手机,内存:400*800*4*2=2 560 000B.


由于设置option的参数inDenstity和inTargetDensity可以在c++层进行缩放,这样就不需要先通过采样将bitmap加码到java里,再通过缩放来控制bitmap(如果需要先采样,只能通过2的倍数采样,限制比较大,如果不采用直接解码则可能会oom)

可以通过如下代码来设置缩放,直接拿到比例相同的bitmap,同时可以直接控制总像素

 public static void createImageThumbnail(Context context, String largeImagePath, String thumbfilePath, int quality) throws Exception {
        if (largeImagePath != null) {
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(largeImagePath, opts);
            opts.inJustDecodeBounds = false;
            LogUtil.LLJe("原图片的宽高比:" + opts.outWidth + "x" + opts.outHeight);

            Bitmap bitmap;
            if (opts.outWidth * opts.outHeight < 4500000) {
                bitmap = BitmapFactory.decodeFile(largeImagePath, opts);
            } else {
                //原始的高度
                opts.inDensity = opts.outHeight;
                //目标的高度
                double temp = opts.outHeight * 4500000.0 / opts.outWidth;
                opts.inTargetDensity = (int) Math.sqrt(temp);
                bitmap = BitmapFactory.decodeFile(largeImagePath, opts);
            }

            saveImageToSD(context, thumbfilePath, bitmap, quality);

            LogUtil.LLJe("已经完成一张图片的压缩,bitmap:" + bitmap.getWidth() + "x" + bitmap.getHeight());
            LogUtil.LLJe("已经完成一张图片的压缩,bitmap的总像素:" + bitmap.getWidth() * bitmap.getHeight() / 10000.0 + "万");
            LogUtil.LLJe("已经完成一张图片的压缩,文件大小:" + Formatter.formatFileSize(BaoBaoApplication.getInstance(), new File(thumbfilePath).length()));
            if (bitmap != null && !bitmap.isRecycled())
                bitmap.recycle();
            System.gc();

        }
    }

你可能感兴趣的:(知识点,android,内存,dp)