background

Generally speaking, images are the main reason for the high memory usage of apps, so optimizing the memory usage of images is the fundamental means to avoid OOM. We may have a misconception about how much memory an image occupies: the less memory an image occupies, the less memory it occupies. So it is thought that as long as the image is compressed, it is equivalent to reducing the memory footprint. This is not true. The amount of memory an image occupies is not directly related to the amount of memory it occupies.

Since memory has nothing to do with it, what’s the point of compressing images? For APK, images are compressed to reduce the size of APK, and for images requiring network requests, images are compressed for faster network response.

There are two basic principles that need to be understood before optimization:

  • There is no direct relationship between the size of the image and the size of the image itself.
  • Although WebP format images are small, but take up the same amount of memory as other formats;

The size of memory occupied by the image

MemorySize ≈ Width * height * Number of bytes required for each pixelCopy the code

Optimization strategy

Now that the required memory formula has been obtained, the optimization is obvious, which is nothing more than reducing the values of these three parameters. The specific strategy is as follows:

Here we divide the picture into two cases to discuss:

Image in Drawable

This case is discussed separately because the Android system scales the image in drawable. The scaling factor is related to the screen resolution and the resolution represented by Drawable. The formula is as follows:

Scale = device resolution/resource directory resolution for example: 1080x1920 images display images in XHDPI, scale = 480/320 = 1.5Copy the code

Therefore, the memory occupied by the image is:

MemorySize ≈ (width * scale) * (height * scale) * Number of bytes required per pixel ≈ Width * height * scale ^ 2 * Number of bytes required per pixelCopy the code

For details about the scaling process, see Bitmap memory optimization in Android

Here we only discuss the factors that influence the Scale factor: device resolution and resource catalog resolution. The other variables will be described in another case. We can’t change the device resolution, so the only factor that matters is the resource directory resolution. That is, the same image in different drawable takes up different amount of memory. As can be seen from the formula, when using the same device, the higher resolution represented by Drawable, the smaller memory occupied by the image, and vice versa. So, when do the picture compatibility, if want to use a picture, you should use pictures of 3 times or 4 times (4 times 3 times is the mainstream model, but in mobile phone will be amplified, distortion of images may), so on the low resolution of mobile phone, not only show clearly, and the system will automatically to zoom in, to ensure that take up less memory.

The same place to store images, why not miPMap in this case? Because MipMap is a resource directory that the Android system adds to avoid distortion of the Launcher Icon, that is, images in MiPMap are not scaled. So Google doesn’t recommend putting images other than Launcher Icon in the MipMap directory.

Pictures of other locations

Other location images include Mipmap, Asset, local images, Network images, etc. Images of these locations all have one thing in common – they are not zoomed. So just worry about changing the image resolution and the number of bytes per pixel.

Local image

Local images are usually loaded via Android’s BitmapFactory. Here are a few commonly used apis:

Public static Bitmap decodeFile(String pathName, Options opts); Public static Bitmap decodeResource(Resources res, int id) decodeResource(Resources res, int id) Public static Bitmap decodeByteArray(byte[] data, int offset, public static Bitmap decodeByteArray(byte[] data, int offset, Public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)Copy the code

The image can be optimized using the Options parameter:

Method 1: inSampleSize

InSampleSize can be understood as the image reduction scale. If inSampleSize is less than 1, it is treated as 1. InSampleSize: inSampleSize ^ 2: inSampleSize ^ 2: inSampleSize ^ 2: inSampleSize ^ 2 But how to specify the value, can be obtained by the following code:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.image, options);
options.inSampleSize = getSampleSize(options, 100, 100);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.abc, options);
imageView.setImageBitmap(bitmap);

public static int getSampleSize(BitmapFactory.Options options, int viewWidth, int viewHeight) {
	if (viewWidth == 0 || viewHeight == 0 || options == null) {
		return 1;
	}
	int widthScale = options.outWidth / viewWidth;
	int heightScale = options.outHeight / viewHeight;
	Log.i("out", "width==" + widthScale + " heightScale==" + heightScale);
	return widthScale >= heightScale ? heightScale : widthScale;
}
Copy the code

Method 2: inDensity

InDensity is the same as the device resolution, inDensity is the same as the device resolution, So scale=1, if inDensity is set to a value greater than the device resolution, the image will be shrunk. For example, current phones with 1dp=2px (2X screen) have an inDensity of 320. If the inDensity can be changed to 480, scale= 320F / 480F =2/3, the memory taken up by images can be as low as 4/9.

Method 3: inPreferredConfig

InPreferredConfig takes the value of type bitmap. Config (only the following cases are considered here), which is an enumeration type that sets the number of bytes required for each pixel:

ALPHA_8:1 byte RGB_565:2 bytes ARGB_4444:2 bytes, deprecated, not recommended ARGB_8888:32-bit true color, with transparency, 4 bytesCopy the code

The default for displaying images is ARGB_8888, so we can optimize memory using the value of inPreferredConfig. The value of inPreferredConfig does not affect the memory of bitmap.config. ALPHA_8 (1 byte), ARGB_4444 and RGB_565 (2 bytes), and ARGB_8888 (4 bytes), but depends on the image format:

  • InPreferredConfig has no effect on JPEG and GIF images. No matter what the value of inPreferredConfig is, a JPEG image always takes 4 bytes per pixel and a GIF image always takes 1 byte per pixel.
  • For webP images, when the inPreferredConfig value is RGB_565, each pixel occupies two bytes, and the other values still occupy four bytes.
  • For PNG images, there are pNG8, PNG24, and PNG32. The number of bytes per pixel of a png8 image varies with the value of inPreferredConfig. The value ALPHA_8 occupies one byte, the value RGB_565 occupies two bytes, and the value ARGB_4444 or ARGB_8888 occupies four bytes. For pNG24 images, when the inPreferredConfig value is RGB_565, each pixel takes up 2 bytes, and the other values (ALPHA_8, ARGB_4444 and ARGB_8888) take up 4 bytes each. For pNG32 images, the inPreferredConfig value (ALPHA_8, RGB_565, ARGB_4444 or ARGB_8888) has no effect on the number of bytes per pixel.

So, if you use inPreferredConfig to optimize the image memory footprint, you need webP or PNG24, pNG24 does not support transparency compared to PNG32, for most images, there is no significant difference between the two. Of course, as a new image format, the Web can be considered a good choice.

Note: although 9Patch will be enlarged according to the size of View when used, its pixels remain unchanged and can be treated as ordinary images.

Network image

Web images are usually loaded using an open source library (ImageSet is recommended). The value of inPreferredConfig can be configured at initialization, and the scaling can be done in the background. That is: return the appropriate size according to the request parameters of the picture. The largest also only need the size of the control can, then large also meaningless, not only waste traffic, but also occupy memory. If you have a lot of images in your APP, you can reduce the width and height of the images according to the memory of the device:

Public static float getDefaultScale() {float scale = 1.0f; int totalMemorySize = AndroidPlatformUtil.getTotalMemorySize(); If (totalMemorySize >= 4) {scale = 1.0f; } else if (totalMemorySize >= 2 && totalMemorySize < 4) {scale = 0.8f; } else {scale = 0.6f; } return scale; } public static int getTotalMemorySize(){String path = "/proc/meminfo"; String firstLine = null; FileReader fileReader = null; BufferedReader bufferedReader = null; try{ fileReader = new FileReader(path); bufferedReader = new BufferedReader(fileReader,8192); firstLine = bufferedReader.readLine().split("\\s+")[1]; } catch (Exception e){ e.printStackTrace(); } finally { try { if (bufferedReader ! = null) { bufferedReader.close(); } } catch (Exception e) { e.printStackTrace(System.out); } try { if (fileReader ! = null) { fileReader.close(); } } catch (Exception e) { e.printStackTrace(System.out); } } if(TextUtils.isEmpty(firstLine)){ return (int)Math.ceil((new Float(Float.valueOf(firstLine) / (1024 * 1024)).doubleValue())); } return 0; }Copy the code

conclusion

For a multi-picture APP, image memory optimization is an essential work. In general, the optimization is achieved by scaling and specifying bitmap. Config values, but it is different for different positions and different formats.