An overview of the

In daily development, we often encounter the error of oom when loading pictures. To solve this problem, we should first understand that OOM means out of memory overflow, because the mobile phone has limited memory and the memory allocated to each application is limited. Therefore, to solve this problem is to solve the memory occupied by pictures. In Android, pictures exist in the form of bitmap, so the memory occupied by bitmap directly affects oom. Let’s learn the calculation method of the memory occupied by bitmap

How much memory Bitmap takes up

Loading from the local or network can be calculated using the following formula

Length of image * width of image * number of bytes per pixelCopy the code

What happens if you load from the resources folder?

What happens when you first put the same image in a different resource folder?

  • The same picture in a different folder, the picture will be compressed

Look at the source

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; }}...int scaledWidth = decoded->width();
int scaledHeight = decoded->height();

if(willScale && mode ! = SkImageDecoder::kDecodeBounds_Mode) { scaledWidth =int(scaledWidth * scale + 0.5 f);
    scaledHeight = int(scaledHeight * scale + 0.5 f); }...if (willScale) {
    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.0 f.0.0 f, &paint);
}
Copy the code

We can see that the compression ratio is derived from the following formula

 scale = (float) targetDensity / density;
Copy the code

And the scaling ratio is related to targetDensity, density, so what do these two variables represent?

  • TargetDensity: Device screen pixel density DPI
  • Density: DPI of the pixel density of the folder corresponding to the image

Density is related to the resource directory stored in Bitmap. Different resource directories have different values

density 0.75 1 1.5 2 3 3.5 4
densityDpi 120 160 240 320 480 560
DpiFolder ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi

The following conclusions can be drawn

  • The same picture in a different resource directory, its resolution will change
  • The higher the resolution of the Bitmap, the smaller the width and height of the parsed image, even smaller than the original image (and zoom), thus reducing the memory response
  • The default resolution mdPI: 160 is used when the image does not place any resource directories
  • If the resource directory resolution is the same as the screen resolution, the image size will not be scaled

Therefore, the calculation of Bitmap in the resource directory is

Bitmap Memory usage ≈ Total size of pixel data = Image width x image height x (current device density DPI/folder density DPI) ^2× Size in bytes per pixelCopy the code

Bitmap memory optimization is optimized from the following four aspects

  • coding
  • The sampling
  • reuse
  • Anonymous shared area

Let’s talk about these optimizations one by one

coding

Several encodings are provided in Android

  • ALPHA_8 represents an 8-bit Alpha bitmap, that is, A=8, A pixel takes up 1 byte, it has no color, only transparency
  • ARGB_4444 represents A 16-bit ARGB bitmap, i.e., A=4,R=4,G=4,B=4, A pixel of 4+4+4=16 bits, 2 bytes
  • ARGB_8888 represents A 32-bit ARGB bitmap, i.e. A=8,R=8,G=8,B=8, A pixel of 8+8+8=32 bits, 4 bytes
  • RGB_565 represents a 16-bit RGB bitmap, that is, R=5,G=6,B=5, it has no transparency, a pixel of 5+6+5=16 bits, 2 bytes

That is, we can change the image format by changing the number of bytes per pixel, to change the amount of memory, see the code below

 BitmapFactory.Options options = new BitmapFactory.Options();
        // Do not get images, do not load into memory, only return image properties
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(photoPath, options);
        // Width and height of the image
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        Log.d("mmm"."Picture width =" + outWidth + "Picture height =" + outHeight);
        // Image format compression
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);
        float bitmapsize = getBitmapsize(bitmap);
        Log.d("mmm"."Compressed: Image size in memory" + bitmapsize + "MB/width =" + bitmap.getWidth() + "Height =" + bitmap.getHeight());
Copy the code

Look at the log

07-09 11:10:46.042 15312-15312/com.example.jh.rxhapp D/ MMM: original picture: memory size =45.776367MB/width =4000Height =3000
07-09 11:10:46.043 15312-15312/com.example.jh.rxhapp D/ MMM: picture width =4000High image =3000
07-09 11:10:46.367 15312-15312/com.example.jh.rxhapp D/ MMM: Compressed: memory size of the image22.887695MB/width =4000Height =3000
Copy the code

We changed the format of the image from ARGB_8888 to RGB_565, the number of pixels in bytes was reduced by half, and the memory per log was reduced by half. It worked

Note: Since ARGB_4444 is a horrible image, you can usually change it to RGB_565 if there is no transparency requirement, which will save half the memory overhead compared to ARGB_8888.

The sampling

We have learned how to calculate the memory footprint of a bitmap, which is determined by the width and height of the bitmap and the number of bytes occupied by each pixel. Let’s talk about the two concepts respectively

1 Width and height of the bitmap

As the name implies, the size of the image is the width and height of the bitmap. According to the formula, we can reduce the width and height of the bitmap to achieve the purpose of compression image memory, see the following code, to reduce the width and height to achieve the purpose of compression

  BitmapFactory.Options options = new BitmapFactory.Options();
        // Do not get images, do not load into memory, only return image properties
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(photoPath, options);
        // Width and height of the image
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        Log.d("mmm"."Picture width =" + outWidth + "Picture height =" + outHeight);
        // Calculate the sampling rate
        int i = utils.computeSampleSize(options, -1.1000 * 1000);
        // Set the sampling rate, not less than 1. If it is 2, the width is 1/2 of the previous, and the height is 1/2 of the previous
        options.inSampleSize = i;
        Log.d("mmm"."Sampling rate =" + i);
        // Image format compression
        //options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);
        float bitmapsize = getBitmapsize(bitmap);
        Log.d("mmm"."Compressed: Image size in memory" + bitmapsize + "MB/width =" + bitmap.getWidth() + "Height =" + bitmap.getHeight());
Copy the code

Look at the printed message

07-09 11:02:11.714 8010-8010/com.example.jh.rxhapp D/ MMM: original picture: memory size =45.776367MB/width =4000Height =3000
07-09 11:02:11.715 8010-8010/com.example.jh.rxhapp D/ MMM: picture width =4000High image =3000
07-09 11:02:11.715 8010-8010/com.example.jh.rxhapp D/ MMM: The sampling rate is =4
07-09 11:02:11.944 8010-8010/com.example.jh.rxhapp D/ MMM: Compressed: memory size of the image1.4296875MB/width =1000Height =750
Copy the code

So we compress it according to the BitmapFactory sampling rate and we set the sampling rate to be no less than 1 and if it’s 2 it’s 1/2 the width, 1/2 the height, 1/4 and so on and so forth, and we see log, and it does compress

reuse

Image reuse refers to the inBitmap property

What does this property do?

If you load three images without this property, you will be allocated three memory Spaces for each image

If inBitmap is used and three images are loaded, they will point to the same memory instead of opening up three memory Spaces

The limitation of inBitmap

  • 3.0-4.3
    • Images must be of the same size for reuse
    • The codes must be the same
  • More than 4.4
    • Reuse space is greater than or equal to
    • The codes don’t have to be the same
  • Does not support WebP
  • Image reuse, this property must be set to true; options.inMutable = true;

Anonymous Shared Memory (Ashmem)

FaceBook’s Fresco was first used in the Android operating system for sharing data between processes, since this area is not limited by the size of the app’s Head and can bypass the OOM

Limitations: Since 5.0 the use of anonymous shared memory has been limited

Where are the images stored?

2.3 – 3.0-4.4 5.0-7.1 8.0
Bitmap object java Heap java Heap java Heap
Pixel data Native Heap java Heap Native Heap
The migration reasons Resolve Native Bitmap memory leaks Shared memory across the system reduced by OOM

8.0Bitmap pixel data stored in Native, why changed to Native storage?

Since 8.0 shares the entire system’s memory, if you keep creating bitmaps on the 8.0 phone, if the phone has 1 GB of memory, your app will not get oom if it loads 1 GB of memory

LRU management Bitmap

We can use LRU open management Bitmap, set it to the maximum memory, timely recycling

Image compression

There are two kinds of compression

  • By sampling compression, as we’ve seen above
  • The quality of compressed
bitmap.compress(Bitmap.CompressFormat.JPEG, 20.new FileOutputStream("sdcard/result.jpg"));
Copy the code

This we have used this, this compression is to maintain the pixel under the premise of changing the depth and transparency of the picture, to achieve the purpose of compression, but this compression will not change the picture in the memory band, and this compression will lead to distortion of the picture, but there is no compression to 100K or so, but also true method?

Recommend to see this blog www.jianshu.com/p/06a1cae9c…


How do I load hd images

BitmapRegionDecoder (BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder, BitmapRegionDecoder

/ / support to the path of the images, flow and pictures modifier BitmapRegionDecoder mDecoder = BitmapRegionDecoder. NewInstance (path,false); Bitmap Bitmap = mDecoder. DecodeRegion (mRect, options);Copy the code

To display part of the region, it is necessary to have gesture control to facilitate sliding up and down, and need to define a custom control, and the idea of the custom control is also very simple: 1 to provide the entrance of the picture, 2 rewrite onTouchEvent, update the parameters of the display area according to gesture movement, 3 after updating the parameters of the region, refresh the control to redraw

Here is the complete code

public class BigImageView extends View {

    private BitmapRegionDecoder mDecoder;
    private int mImageWidth;
    private int mImageHeight;
    // The area where the image is drawn
    private Rect mRect = new Rect();
    private static final BitmapFactory.Options options = new BitmapFactory.Options();

    static {
        options.inPreferredConfig = Bitmap.Config.RGB_565;
    }

    public BigImageView(Context context) {
        super(context);
        init();
    }

    public BigImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BigImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(a) {}/** * Custom view entry, set image flow **@paramPath Image path */
    public void setFilePath(String path) {
        try {
            // Initialize BitmapRegionDecoder
            mDecoder = BitmapRegionDecoder.newInstance(path, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            // Only image attributes are loaded, and bitmap is not loaded into memory
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, options);
            // Width and height of the image
            mImageWidth = options.outWidth;
            mImageHeight = options.outHeight;
            Log.d("mmm"."Picture width =" + mImageWidth + "Picture height =" + mImageHeight);

            requestLayout();
            invalidate();
        } catch(IOException e) { e.printStackTrace(); }}@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // Get the width of the view
        int measuredHeight = getMeasuredHeight();
        int measuredWidth = getMeasuredWidth();


        // Displays the image in the upper left by default
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mRect.left + measuredWidth;
        mRect.bottom = mRect.top + measuredHeight;
    }

    // First press position
    private float mDownX;
    private float mDownY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();
                // The distance moved
                int xDistance = (int) (moveX - mDownX);
                int yDistance = (int) (moveY - mDownY);
                Log.d("mmm"."mDownX=" + mDownX + "mDownY=" + mDownY);
                Log.d("mmm"."movex=" + moveX + "movey=" + moveY);
                Log.d("mmm"."xDistance=" + xDistance + "yDistance=" + yDistance);
                Log.d("mmm"."mImageWidth=" + mImageWidth + "mImageHeight=" + mImageHeight);
                Log.d("mmm"."getWidth=" + getWidth() + "getHeight=" + getHeight());
                if (mImageWidth > getWidth()) {
                    mRect.offset(-xDistance, 0);
                    checkWidth();
                    // Refresh the page
                    invalidate();
                    Log.d("mmm"."Refresh width");
                }
                if (mImageHeight > getHeight()) {
                    mRect.offset(0, -yDistance);
                    checkHeight();
                    invalidate();
                    Log.d("mmm"."Refresh height");
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:}return true;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Bitmap bitmap = mDecoder.decodeRegion(mRect, options);
        canvas.drawBitmap(bitmap, 0.0.null);
    }

    /** * Make sure the diagram does not underline the screen */
    private void checkWidth(a) {


        Rect rect = mRect;
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;

        if (rect.right > imageWidth) {
            rect.right = imageWidth;
            rect.left = imageWidth - getWidth();
        }

        if (rect.left < 0) {
            rect.left = 0; rect.right = getWidth(); }}/** * Make sure the diagram does not underline the screen */
    private void checkHeight(a) {

        Rect rect = mRect;
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;

        if (rect.bottom > imageHeight) {
            rect.bottom = imageHeight;
            rect.top = imageHeight - getHeight();
        }

        if (rect.top < 0) {
            rect.top = 0; rect.bottom = getHeight(); }}}Copy the code

The lines are commented, so it should be easy to understand

https://www.jianshu.com/p/3f6f6e4f1c88 www.jianshu.com/p/e49ec7d05…