Memory Optimization of Bitmap Based on Android Performance Optimization

Source: Internet
Author: User

Memory Optimization of Bitmap Based on Android Performance Optimization
1. How BitmapFactory parses Bitmap

BitmapFactory provides the following static factory methods for parsing Bitmap:

Bitmap decodeFile(...)Bitmap decodeResource(...)Bitmap decodeByteArray(...)Bitmap decodeStream(...)Bitmap decodeFileDescriptor(...)

Three of them are commonly used: decodeFile, decodeResource, and decodeStream.
DecodeFile and decodeResource actually call the decodeStream method to parse Bitmap. decodeStream internally calls two native methods to parse Bitmap:

nativeDecodeAsset()nativeDecodeStream()

The two native methods are only parsed for decodeFile, decodeResource, and decodeStream. For example, decodeByteArray and decodeFileDescriptor also have specialized native methods to parse Bitmap.

Next, let's take a look at the differences between the two methods in parsing Bitmap: decodeFile and decodeResource. After checking, we find that their call paths are as follows:

DecodeFile-> decodeStream
DecodeResource-> decodeResourceStream-> decodeStream

One more decodeResource is called during parsing.DecodeResourceStreamThe decodeResourceStream method code is as follows:

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

It mainly processes the Options.Opts. inDensityIf we do not set a value for this attribute. inDensity = DisplayMetrics. DENSITY_DEFAULT; assign this default Density value. The default value is 160, which is the standard dpi ratio, that is, 1dp = 1px on the device with Density = 160. This method also contains this line.

Opts. inTargetDensity = res. getDisplayMetrics (). densityDpi;

PairOpts. inTargetDensityA value is assigned, which is the densityDpi value of the current device. Therefore, the decodeResourceStream method mainly performs two tasks:

1. assign a value to opts. inDensity. If no value is set, the default value is 160.
2. assign a value to opts. inTargetDensity. If no value is assigned, the current device's densityDpi value is assigned.

After that, the parameter will be passed into the decodeStream method. This method will be called after the native method is called to parse Bitmap.SetDensityFromOptions (bm, opts );:

    private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {        if (outputBitmap == null || opts == null) return;        final int density = opts.inDensity;        if (density != 0) {            outputBitmap.setDensity(density);            final int targetDensity = opts.inTargetDensity;            if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {                return;            }            byte[] np = outputBitmap.getNinePatchChunk();            final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);            if (opts.inScaled || isNinePatch) {                outputBitmap.setDensity(targetDensity);            }        } else if (opts.inBitmap != null) {            // bitmap was reused, ensure density is reset            outputBitmap.setDensity(Bitmap.getDefaultDensity());        }    }

This method is mainly used to assign values to Bitmap for the two attributes inDensity and inTargetDensity that have just been assigned a value. However, Bitmap is not assigned directly, and there is a judgment in the middle, when the value of inDensity is different from that of inTargetDensity or the screen Density of the device, the value of inTargetDensity is applied. If the value is equal, the value of inDensity is applied.

So to sum up,SetDensityFromOptionsThe method isInTargetDensityThe value is assigned to Bitmap, but the premise is opts. inScaled = true;

Through the above analysis, we can draw the following conclusion:

If Options is not configured:
1. decodeFile and decodeStream do not perform a series of screen adaptation for Bitmap during parsing. The parsed images will be the original size.
2. decodeResource will scale and adapt Bitmap to the value of densityDpi Based on the pixel density of the current device screen during resolution so that the resolved Bitmap matches the resolution of the current device, achieve an optimal display effect, and the Bitmap size will be larger than the original

1.1 relationship between res directories such as Density, resolution, and-hdpi
DensityDpi Resolution Res Density
160 dpi 320x533 Mdpi 1
240 dpi 480x800 Hdpi 1.5
320 dpi 720x1280 Xhdpi 2
480 dpi 1080x1920 Xxhdpi 3
560 dpi 1440x2560 Xxxhdpi 3.5

The conversion formula of dp and px is as follows:

Px = dp * Density

1.2. DisplayMetrics: differences between densityDpi and density

GetResources (). getDisplayMetrics (). densityDpi -- indicates the pixel density of the screen.
GetResources (). getDisplayMetrics (). density -- 1dp equals to how many pixels (px)

For example, 1dp = 1px for a device with a screen density of 160. 1dp = 2px for devices with a screen density of 320.
Therefore, we recommend that you use dp as the unit for layout in Android, because it can be dynamically adjusted based on the screen density of the current device.

2. Bitmap Optimization Strategy 2.1 BitmapFactory. Options attribute Parsing

BitmapFactory. Options has the following attributes:

InBitmap -- reuse the Bitmap when parsing Bitmap, but must wait for a large Bitmap and inMutable must be trueinMutable -- configure whether Bitmap can be changed, for example: add a line segment inJustDecodeBounds in several pixels on Bitmap -- if it is true, only the Bitmap's high width attribute inSampleSize is returned. -- Must> = 1, indicating the Bitmap compression ratio, for example, inSampleSize = 4, A BitmapinPreferredConfig -- Bitmap of 1/16 of the original graph is returned. config. ARGB_8888 and other inDither -- whether to perform jitter. The default value is falseinPremultiplied. The default value is true, generally, it does not change its value inDensity -- Bitmap's pixel density inTargetDensity -- Bitmap's final pixel density inScreenDensity -- the current screen's pixel density inScaled -- whether scaling is supported. The default value is true, when this is set, bitmap scales inPurgeable based on the value of inTargetDensity. It takes effect only when the memory used to store Pixel is insufficient and inininputretriable can be recycled. If inPurgeable is true, whether an InputStreaminPreferQualityOverSpeed can be shared. If this parameter is set to true, Bitmap quality is guaranteed first, and the decoding speed outWidth. The returned Bitmap width outHeight. The returned Bitmap's high inTempStorage. The temporary space during decoding, 16*1024
2.2 Optimization Strategy

1. BitmapConfig Configuration
2. When decodeFile, decodeResource, and decodeStream are used to parse Bitmap, configure inDensity and inTargetDensity. The two values must be equal to the screen pixel density * 0.75f.
3. Use inJustDecodeBounds to pre-determine the Bitmap size and use inSampleSize for compression.
4. Bitmap adaptation (Density scaling) for Density> 240 Devices)
5. Use of inNativeAlloc in analyticdb 2.3
6. inPurgeable and ininputretriable versions earlier than 4.4
7. Reclaim Bitmap

For the above scheme, the Bitmap decoding code is encapsulated into a tool class, as follows:

public class BitmapDecodeUtil {    private static final int DEFAULT_DENSITY = 240;    private static final float SCALE_FACTOR = 0.75f;    private static final Bitmap.Config DEFAULT_BITMAP_CONFIG = Bitmap.Config.RGB_565;    private static BitmapFactory.Options getBitmapOptions(Context context) {        BitmapFactory.Options options = new BitmapFactory.Options();        options.inScaled = true;        options.inPreferredConfig = DEFAULT_BITMAP_CONFIG;        options.inPurgeable = true;        options.inInputShareable = true;        options.inJustDecodeBounds = false;        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {            Field field = null;            try {                field = BitmapFactory.Options.class.getDeclaredField("inNativeAlloc");                field.setAccessible(true);                field.setBoolean(options, true);            } catch (NoSuchFieldException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }        int displayDensityDpi = context.getResources().getDisplayMetrics().densityDpi;        float displayDensity = context.getResources().getDisplayMetrics().density;        if (displayDensityDpi > DEFAULT_DENSITY && displayDensity > 1.5f) {            int density = (int) (displayDensityDpi * SCALE_FACTOR);            options.inDensity = density;            options.inTargetDensity = density;        }        return options;    }    public static Bitmap decodeBitmap(Context context, int resId) {        checkParam(context);        return BitmapFactory.decodeResource(context.getResources(), resId, getBitmapOptions(context));    }    public static Bitmap decodeBitmap(Context context, String pathName) {        checkParam(context);        return BitmapFactory.decodeFile(pathName, getBitmapOptions(context));    }    public static Bitmap decodeBitmap(Context context, InputStream is) {        checkParam(context);        checkParam(is);        return BitmapFactory.decodeStream(is, null, getBitmapOptions(context));    }    public static Bitmap compressBitmap(Context context,int resId, int maxWidth, int maxHeight) {        checkParam(context);        final TypedValue value = new TypedValue();        InputStream is = null;        try {            is = context.getResources().openRawResource(resId, value);            return compressBitmap(context, is, maxWidth, maxHeight);        } catch (Exception e) {            e.printStackTrace();        } finally {            if (is != null) {                try {                    is.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        return null;    }    public static Bitmap compressBitmap(Context context, String pathName, int maxWidth, int maxHeight) {        checkParam(context);        InputStream is = null;        try {            is = new FileInputStream(pathName);            return compressBitmap(context, is, maxWidth, maxHeight);        } catch (FileNotFoundException e) {            e.printStackTrace();        } finally {            if (is != null) {                try {                    is.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        return null;    }    public static Bitmap compressBitmap(Context context, InputStream is, int maxWidth, int maxHeight) {        checkParam(context);        checkParam(is);        BitmapFactory.Options opt = new BitmapFactory.Options();        opt.inJustDecodeBounds = true;        BitmapFactory.decodeStream(is, null, opt);        int height = opt.outHeight;        int width = opt.outWidth;        int sampleSize = computeSampleSize(width, height, maxWidth, maxHeight);        BitmapFactory.Options options = getBitmapOptions(context);        options.inSampleSize = sampleSize;        return BitmapFactory.decodeStream(is, null, options);    }    private static int computeSampleSize(int width, int height, int maxWidth, int maxHeight) {        int inSampleSize = 1;        if (height > maxHeight || width > maxWidth) {            final int heightRate = Math.round((float) height / (float) maxHeight);            final int widthRate = Math.round((float) width / (float) maxWidth);            inSampleSize = heightRate < widthRate ? heightRate : widthRate;        }        if (inSampleSize % 2 != 0) {            inSampleSize -= 1;        }        return inSampleSize <= 1 ? 1 : inSampleSize;    }    private static 
  
    void checkParam(T param){        if(param == null)            throw new NullPointerException();    }}
  

There are two main methods:
I. decodeBitmap: Bitmap is not compressed, but scaled and compressed Based on the screen density.
Ii. compressBimtap: compress Bitmap over the maximum width and height. It also scales and compresses the Bitmap based on the screen density.

3. Bitmap performance comparison before and after optimization

For the above scheme, compare the performance. The image size is 3.26 MB, and the resolution is 2048*2048.
There are two devices:

3.1 devices with a density of 320

3.2 devices with a density of 560

As you can see, the same image is loaded. The memory required for devices with high screen pixel density is very large. The Bitmap width and height in the memory also change because the pixel density of the device is also changed, as analyzed above, using decodeResource will automatically adapt to the resolution of the current device to achieve an optimal effect, and only this method will automatically adapt to other methods, in the encapsulated tool class, we add automatic adaptation based on the pixel density of the screen in every method, but in reality we do not need such high-definition images, so we can scale according to the density of the device. For example, in the case of 400> = density> 240, x0.8 and density> 400, in this way, the memory occupied by Bitmap will be reduced a lot. You can view the bitmap and decodeBitmap values in the two pictures above. decodeBitmap only scales the density, the memory usage is greatly reduced, and the display effect is no different from the original one.
Then we compared our inSampleSize compressed image, the effect after compression is not much difference, and the memory usage is also reduced a lot. <喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> VcD4NCjxoMiBpZD0 = "4bitmap recycling"> 4. Reclaim Bitmap 4.1. Android 2.3.3 (API 10) and below

In systems lower than 2.3, the pixel data of Bitmap is stored in native, And the Bitmap object is stored in the java heap. Therefore, the space of Bitmap needs to be recycled when the Bitmap is recycled: native and java heap.
That is, call recycle () to release Bitmap pixel data in native, and set Bitmap to null to ensure GC recycles Bitmap objects.

4.2, Android 3.0 (API 11) and above

In more than 3.0 of systems, Bitmap's pixel data and objects are stored in the java heap. You do not need to call recycle (), but you only need to set the object to null, which is automatically managed by GC.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.