Performance Optimization: Several common ways to optimize Bitmap memory size in Android

Android bitmaps are memory intensive, and the size of the bitmap directly affects the memory footprint of the application. The memory occupied by bitmap can be calculated as follows:

Bitmap memory size = Bitmap width (px) * Bitmap length (px) * Number of bytes occupied by a pixelCopy the code

BitmapFactory gives us multiple decode methods that can load bitmaps from different data sources, as shown below:

The number of bytes a pixel occupies corresponds to bitmap. Config, which is an enumeration class with the following values:

The default Config value for each new Bitmap is bitmap.config.argb_8888, which means that a pixel occupies 4 bytes ((8 + 8 + 8 + 8) / 8 = 4 bytes).

Change the Bitmap memory size

According to the above principle, we can reduce the memory footprint of Bitmap from two aspects: one is to change the width and height of Bitmap, and the other is to change the value of bitmap. Config. Change bitmap.config. ARGB_8888 to bitmap.config. ARGB_4444 or bitmap.config.rgb_565 with fewer bytes.

There are the following methods:

1. Sampling rate compression (change Bitmap size).

2. Compress through Martix (change Bitmap size). Bitmap. CreateBitmap or Bitmap. CreateScaledBitmap method.

3. Change the bitmap. Config format.

Let’s first look at the size of the original image:

The size of the original image is 640px * 360px, and it is placed in the Assets directory, indicating that the system will not zoom in and out. If it is placed in the corresponding drawable directory, the system will perform the corresponding zoom operation.

1. Sampling rate compression (change Bitmap size).

val assetFileDescriptor = assets.openFd("maomi.jpg") val fileInputStream = assetFileDescriptor.createInputStream() val bmpInSampleSizeJpg = BitmapOptionsUtil.decodeSampledBitmapFromFileStream( fileInputStream, ScreenUtils.dip2px(this, 80f), ScreenUtils.dip2px(this, 45f) ) val descBmpConfigJpg = "height:${bmpInSampleSizeJpg?.height},\nwidth:${bmpInSampleSizeJpg?.width},\nallocationByteCount:${bmpInSampleSizeJpg?.a llocationByteCount}byte,\n" + "byteCount:${bmpInSampleSizeJpg?.byteCount}byte,\nrowBytes:${bmpInSampleSizeJpg?.rowBytes}byte,\ndensity:${bmpInSampleSi ZeJpg?. Density} "tvInSampleSizeJpgInfo. Text =" Jpg by inSampleSize compressed information: $descBmpConfigJpg" ivInSampleSizeJpg.setImageBitmap(bmpInSampleSizeJpg)Copy the code

Obviously, the width and height of the image are reduced to 1/4 of the original size, and the memory occupied by the bitmap is 1/16 of the original size.

In this case, there is no need to load the Bitmap according to the original large size, which can effectively avoid memory waste and OOM. See BitmapOptionsUtil for the calculation of the specific sampling rate (inSampleSize).

2. Compress through Martix (change Bitmap size).

Val matrix = matrix () matrix.setScale(0.1f, 0.1f) val bmpMatrixJpg = bitmap.createBitmap (bmpOriginJpg, 0, 0, bmpOriginJpg.getWidth(), bmpOriginJpg.getHeight(), matrix, True) / / Bitmap. CreateScaledBitmap internal Matrix is used to zoom in / / val bmpMatrixJpg = Bitmap. CreateScaledBitmap (/ / bmpOriginJpg, // ScreenUtils.dip2px(this, 60f), // ScreenUtils.dip2px(this, 45f), // true // ) val descBmpConfigJpg = "height:${bmpMatrixJpg? .height},\nwidth:${bmpMatrixJpg? .width},\nallocationByteCount:${bmpMatrixJpg? .allocationByteCount}byte,\n" + "byteCount:${bmpMatrixJpg? .byteCount}byte,\nrowBytes:${bmpMatrixJpg? .rowBytes}byte,\ndensity:${bmpMatrixJpg? . Density} "tvMatrixJpgInfo. Text =" Jpg by Matrix compressed information: $descBmpConfigJpg ivMatrixJpg. "setImageBitmap (bmpMatrixJpg)Copy the code

Since the scaling ratio is set to 0.1F, that is, the width and height are both 1/10 of the previous size, the memory occupied by the compressed bitmap becomes 1/100 of the original size.

This applies if both the original map size and the target bitmap size are known.

3. Change the bitmap. Config format.

val option = BitmapFactory.Options() option.inPreferredConfig = Bitmap.Config.ARGB_4444 // option.inPreferredConfig = Bitmap.config. RGB_565 val bmpBmpConfigJpg = BitmapFactory.decodeStream(assets.open("maomi.jpg"), null, option) val descBmpConfigJpg = "height:${bmpBmpConfigJpg?.height},\nwidth:${bmpBmpConfigJpg?.width},\nallocationByteCount:${bmpBmpConfigJpg?.allocation ByteCount}byte,\n" + "byteCount:${bmpBmpConfigJpg?.byteCount}byte,\nrowBytes:${bmpBmpConfigJpg?.rowBytes}byte,\ndensity:${bmpBmpConfigJpg?.de Nsity}" tvbmpconfigjpginfo. text = "bitmap.config" $descBmpConfigJpg" ivBmpConfigJpg.setImageBitmap(bmpBmpConfigJpg)Copy the code

We change the bitmap. Config value to bitmap.config. ARGB_4444 based on the output byteCount value. It is obvious that the Bitmap memory size has been reduced by half.

If transparency is not required, try bitmap.config.rgb_565.

This situation applies to the image resolution requirements are not high.

Compress by Bitmap#compress method, quality compression.

Another important compression method is the Bitmap#compress method, which modifies the quality value to change the size of the Bitmap generated byte stream. This method does not change the size of memory used by bitmaps.

Quality compression will not reduce the pixel of the picture, it is to maintain the pixel under the premise of changing the picture depth and transparency, to achieve the purpose of compression of the picture. If the length, width, and pixels of the image remain the same, the size of the bitmap will not change. What is changed here is the size of the byte array corresponding to the bitmap, which is suitable for transmitting binary image data, such as wechat sharing.

val bytearray = getBytesFromCompressBitmap(bmpOriginJpg, 32 * 1024) val bmpQualityJpg = BitmapFactory.decodeByteArray(bytearray, 0, bytearray.size) val descQualityJpg = "height:${bmpQualityJpg.height},\nwidth:${bmpQualityJpg.width},\nallocationByteCount:${bmpQualityJpg.allocationByteCount }byte,\n" + "byteCount:${bmpQualityJpg.byteCount}byte,\nrowBytes:${bmpQualityJpg.rowBytes}byte,\ndensity:${bmpQualityJpg.density},\n "+ "bytearray:${bytearray.size}" tvqualityjpginfo.text =" $descQualityJpg" ivQualityJpg.setImageBitmap(bmpQualityJpg)Copy the code
/ * * * condensed the bytes of the Bitmap for the target size for Byte * * @ targetSize unit/private fun getBytesFromCompressBitmap (Bitmap: Bitmap, targetSize: Int ): ByteArray { val baos = ByteArrayOutputStream() var quality = 100 bitmap.compress(Bitmap.CompressFormat.PNG, quality, baos) var bytes = baos.toByteArray() while (bytes.size > targetSize && quality >= 5) { quality -= 5 if (quality < 0) { Quality = 0} Or you will accumulate baos. Reset () bitmap.com press (bitmap.com pressFormat. JPEG, quality, baos) bytes = baos.toByteArray() } try { baos.close() } catch (e: Exception) { e.printStackTrace() } return bytes }Copy the code

As you can see, mass compression does not change the size of the original bitmap, it changes the byte stream via the bitmap #compress method.

In the specific development process, you can choose the appropriate way according to your needs.

The project address

tinyvampirepudge/AndroidStudy

Specific page address: provides JPG and PNG image format demo.

BitmapCompressActivity

reference

How to get FileInputStream to File in assets folder

Bitmap six compression methods, Android image compression