Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize the in-depth analysis of the memory occupied by pictures in Android

2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

The editor today takes you to understand how to realize the in-depth analysis of the memory occupied by pictures in Android. The knowledge points in the article are introduced in great detail. Friends who think it is helpful can browse the content of the article together with the editor, hoping to help more friends who want to solve this problem to find the answer to the problem. Follow the editor to learn more about "how to realize the in-depth analysis of pictures taking up memory in Android".

Preface

One of the things that Android must take into account when loading images is how to prevent OOM, so how much memory does a picture take up when it loads? What factors affect the amount of memory consumed? Knowing this, we can know from which points to optimize, thus avoiding OOM.

I. the relationship between the memory occupied by pictures and the width, height and color mode

First of all, let's prepare a picture of 1920-1080:

Then the test machine I use is Redmi Note 9 Pro with a resolution of 2400 to 1080. Put this image in the corresponding resolution directory, that is, under the drawable-xxhdpi directory, and then use different configurations to load the image:

Override fun initData () {val options = BitmapFactory.Options () val bitmap = BitmapFactory.decodeResource (resources, R.drawable.test_xxhdpi, options) ShowLogUtil.info ("width: ${options.outWidth}, height: ${options.outHeight}, config: ${options.inPreferredConfig}, occupied memory: ${bitmap.allocationByteCount}") options.inSampleSize = 2 val bitmap2 = BitmapFactory.decodeResource (resources, R.drawable.test_xxhdpi) Options) ShowLogUtil.info ("width: ${options.outWidth}, height: ${options.outHeight}, config: ${options.inPreferredConfig}, memory usage: ${bitmap2.allocationByteCount}") options.inPreferredConfig = Bitmap.Config.RGB_565 val bitmap3 = BitmapFactory.decodeResource (resources, R.drawable.test_xxhdpi, options) ShowLogUtil.info ("width: ${options.outWidth}, height: ${options.outHeight}, config: ${options.inPreferredConfig}) Memory usage: ${bitmap3.allocationByteCount} ")}

In the above code, the first time to load the picture using the default configuration, the second time to load the picture to modify the sampling rate, the sampling rate must be set to 2 to the power of n (n ≥ 0); the third time to load the picture to modify the sampling rate based on the modification of the color mode. Observe the log:

We can see that the second time because we set the sampling rate to 2, which is equivalent to setting the picture compression ratio, and then the width and height of the load become the first 1ax 2, taking up memory for the first time 1Unip 4; the third time on the basis of the second time, we set the color mode to RGB_565, which takes up memory for the second time 1ax 2, and the first time 1ax 8.

In fact, there are other color modes, and finally explain the difference between several color modes, now just need to know that ARGB_8888 is ARGB components are 8 bits, so a pixel occupies 32 bits, that is, 4 bytes, it is the best way to ensure the effect of the picture. The RGB component in RGB_565 uses 5 bits, 6 bits and 5 bits respectively, and there is no transparency, so one pixel occupies 16 bits, that is, 2 bytes.

Then we can simply see that the placeholder memory is proportional to the load width and height, and is proportional to the pixel size, and temporarily thinks that their relationship is as follows:

Occupied memory = width * height * pixel size

Second, the relationship between the memory occupied by pictures and the storage folder

In daily development, UI usually cuts images of different resolutions when cutting images. 2x images correspond to Android's xhdpi directory and 3x images correspond to Android's xxhdpi directory, so why do different resource folders need different resolution images? Is it possible to use only one set of pictures? Let's take a look at the memory footprint of putting the same picture into different directories when loading.

I put the above image under different directories and named it under different names:

The resolution of the images here are all the same, all 1920-1080, but put in different directories, and then we load the three images separately.

Override fun initData () {val options1 = BitmapFactory.Options () val bitmap = BitmapFactory.decodeResource (resources, R.drawable.test_xxhdpi, options1) ShowLogUtil.info ("width: ${options1.outWidth}, height: ${options1.outHeight}, config: ${options1.inPreferredConfig}, memory usage: ${bitmap.allocationByteCount}, inDensity: ${options1.inDensity} InTargetDensity: ${options1.inTargetDensity} ") val options2 = BitmapFactory.Options () val bitmap2 = BitmapFactory.decodeResource (resources, R.drawable.test_xhdpi, options2) ShowLogUtil.info (" width: ${options2.outWidth}, height: ${options2.outHeight}, config: ${options2.inPreferredConfig}, memory usage: ${bitmap2.allocationByteCount}, inDensity: ${options2.inDensity}) InTargetDensity: ${options2.inTargetDensity} ") val options3 = BitmapFactory.Options () val bitmap3 = BitmapFactory.decodeResource (resources, R.drawable.test_hdpi, options3) ShowLogUtil.info (" width: ${options3.outWidth}, height: ${options3.outHeight}, config: ${options3.inPreferredConfig}, memory usage: ${bitmap3.allocationByteCount}, inDensity: ${options3.inDensity}, inTargetDensity: ${options3.inTargetDensity} ")}

A default Options object is passed in when loading the three images, and after loading the image, the log of the inDensity and inTargetDensity attributes of Options is added to observe the log:

You can see that when loading xhdpi images, the memory footprint is 2.25 times that of xxhdpi, which is 2.25 times that of xxhdpi, and 4 times that of xxhdpi when loading hdpi pictures, so how does this multiple relationship come from? Don't worry, you can find the answer later through the source code.

Let's modify the picture first. Change the resolution of the picture under the xhdpi directory to 1280, 720, and Hdpi to 960. 540. Run it again and observe the log:

At this time, we found that the memory footprint of loading images in different resolutions is the same.

Now let's look at the source code of the BitmapFactory.decodeResource () method:

Public static Bitmap decodeResource (Resources res, int id, Options opts) {validate (opts); Bitmap bm = null; InputStream is = null; try {final TypedValue value = new TypedValue (); / / Open the resource flow and initialize some properties. Is = res.openRawResource (id, value); / / parse image resources. Bm = decodeResourceStream (res, value, is, null, opts);} catch (Exception e) {/ * do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. * /} finally {try {if (is! = null) is.close ();} catch (IOException e) {/ / Ignore}} if (bm = = null & & opts! = null & & opts.inBitmap! = null) {throw new IllegalArgumentException ("Problem decoding into existing bitmap");} return bm }

The bitmap object in the decodeResource () method is loaded through the decodeResourceStream () method, so continue to look at the decodeResourceStream () method:

@ Nullable public static Bitmap decodeResourceStream (@ Nullable Resources res, @ Nullable TypedValue value, @ Nullable InputStream is, @ Nullable Rect pad, @ Nullable Options opts) {validate (opts); / / check whether the Options object is empty or create a default object. If (opts = = null) {opts = new Options ();} / check whether inDensity is set for the Options object. This is the default, so it is not set. The TypedValue object is also a default object created in the decodeResource () method above, so if it is not null, it must enter this if code block. 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) {/ / when the TypedValue object calls the Resources.openRawResource () method in the above decodeResource () method, the density will be assigned the density value of the directory where the corresponding resource file resides, so it will come to this point and assign a value to the inDensity attribute of the Options. The inTargetDensity of opts.inDensity = density;}} / / Options is not assigned by default, so it will enter the if code block and assign it to the densityDpi of the phone screen. If (opts.inTargetDensity = = 0 & & res! = null) {opts.inTargetDensity = res.getDisplayMetrics () .densityDpi;} return decodeStream (is, pad, opts);}

The last thing this method calls is the decodeStream () method, which continues to look at the decodeStream () method. Here I only explain the key code and omit part of the code:

@ Nullable public static Bitmap decodeStream (@ Nullable InputStream is, @ Nullable Rect outPadding, @ Nullable Options opts) {. Try {if (is instanceof AssetManager.AssetInputStream) {final long asset = ((AssetManager.AssetInputStream) is). GetNativeAsset (); bm = nativeDecodeAsset (asset, outPadding, opts, Options.nativeInBitmap (opts), Options.nativeColorSpace (opts));} else {/ / the resources here are not loaded from the assets directory, so enter the else code block. Bm = decodeStreamInternal (is, outPadding, opts);}...} finally {Trace.traceEnd (Trace.TRACE_TAG_GRAPHICS);} return bm;}

Continue to look at the decodeStreamInternal () method:

Private static Bitmap decodeStreamInternal (@ NonNull InputStream is, @ Nullable Rect outPadding, @ Nullable Options opts) {/ / ASSERT (is! = null); byte [] tempStorage = null; if (opts! = null) tempStorage = opts.inTempStorage; if (tempStorage = = null) tempStorage = new byte [decode _ BUFFER_SIZE] Return nativeDecodeStream (is, tempStorage, outPadding, opts, Options.nativeInBitmap (opts), Options.nativeColorSpace (opts));}

You can see that the nativeDecodeStream () method in the native layer is finally called in this method, so we need to continue to trace it to the C++ layer, call / frameworks/base/core/jni/android/graphics/BitmapFactory.cpp, and check its nativeDecodeStream method:

Static jobject nativeDecodeStream (JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options) {jobject bitmap = NULL; std::unique_ptr stream (CreateJavaInputStreamAdaptor (env, is, storage)); if (stream.get ()) {std::unique_ptr bufferedStream (std::move (stream), SkCodec::MinBufferedBytesNeeded ()); SkASSERT (bufferedStream.get ()! = NULL) Bitmap = doDecode (env, std::move (bufferedStream), padding, options);} return bitmap;}

You can see that the decoding of the image is done through the doDecode () method. Look at this method. Here I only explain the key code and omit part of the code:

Static jobject doDecode (JNIEnv* env, std::unique_ptr stream, jobject padding, jobject options) {/ / Set default values for the options parameters. ... = 'class1' > initialization scale ratio 1.0 float scale = 1.0F; If (options! = NULL) {. / / gets the density,targetDensity in the Options object in java and calculates the zoom ratio, both of which are assigned in the decodeResourceStream () method in the java code. 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 you load a picture in xhdpi, the inDensity is 320, and the resolution of the test machine used is 1920 to 1080, then the targetDensity is 480, so the scale is 480 cycles 1.5 if (density! = 0 & & targetDensity! = 0 & & density! = screenDensity) {scale = (float) targetDensity / density;}. Int scaledWidth = size.width (); int scaledHeight = size.height ();... / / Scale is necessary due to density differences. If (scale! = 1.0F) {/ / if you need to scale, calculate the width and height after scaling. The width and height are multiplied by the zoom ratio scale, respectively. WillScale = true; scaledWidth = static_cast (scaledWidth * scale + 0.5f); scaledHeight = static_cast (scaledHeight * scale + 0.5f);}. If (willScale) {... OutputBitmap.setInfo (bitmapInfo.makeWH (scaledWidth, scaledHeight) .makeColorType (scaledColorType));...} else {outputBitmap.swap (decodingBitmap);}. / / now create the java bitmap return bitmap::createBitmap (env, defaultAllocator.getStorageObjAndReset (), bitmapCreateFlags, ninePatchChunk, ninePatchInsets,-1);}

Through a simple analysis of the source code, we can draw the conclusion that if the resolution of the loaded image is different from that of the mobile phone screen, the picture will be scaled and the width and height will be recalculated by the zoom ratio. so their relationship is: memory consumption = (width * scale) * (height * scale) * pixel size, that is,

Occupied memory = wide * high * (mobile screen density / resource folder density) ²* pixel size

This explains why different resolution images are needed in different resource directories, mainly to save memory. However, if each resolution has to be adapted, it is bound to increase the picture and packet volume, so when doing picture adaptation, it is necessary to weigh the pros and cons according to the frequency of picture use and the distribution of mobile phone resolution in the market.

Third, loading pictures from files and loading pictures from the network takes up memory

These two ways of loading images, in fact, through the source code analysis just now, we do not need to run the actual operation to know that since inDensity and inTargetDensity are not set, the memory occupied is width * height * pixel size. Because it does not scale, we need to pay particular attention to its memory footprint to avoid OOM when loading pictures from files and from the network.

And through the analysis just now, we can know that in addition to setting inSampleSize to optimize memory footprint, we can also set inDensity and inTargetDensity to indirectly optimize memory footprint through scaling.

Fourth, color mode

There are four main color modes in Android. There are many articles about this, which are briefly mentioned:

The Bitmap.Config.ARGB_8888:ARGB components are all 8 bits, accounting for a total of 32 bits, with the highest degree of reduction.

Bitmap.Config.ARGB_4444:ARGB components are all 4 bits, accounting for a total of 16 bits, retaining transparency, but the degree of reduction is low.

Bitmap.Config.RGB_565: no transparency, the RGB component occupies 5, 6, 5 respectively, accounting for a total of 16 places.

Bitmap.Config.ALPHA_8: only transparency is retained, with a total of 8 digits.

The point here is not to introduce the difference between them, but to mention that it is not necessarily that if you set that color mode, it will be loaded according to this color mode. You need to see whether the image decoder supports it. For example, if we have a need for grayscale loading images, then setting the color mode to Bitmap.Config.ALPHA_8 seems to be the simplest and most efficient way, but in fact this may not be the case. If the image decoder does not support it, then it will still use Bitmap.Config.ARGB_8888 to load, here is a pit, and hope that when this happens, the kids will think of this reason.

Attached: the relationship among inDensity,inTargetDensity,inScreenDensity and inScaled

By tracing the code, we can see that when the picture resource is decoded through the data stream, the inScaled is identified according to the three values of inDensity,inTargetDensity,inScreenDensity and whether it is scaled or not.

InDensity: the pixel density of the image itself (in fact, it is under which density folder the image resources are located, for example, under xxhdpi, it is 480,160 by default under asstes, mobile memory / SD card).

InTargetDensity: the pixel density of the final image in bitmap. If no value is assigned, inTargetDensity will be set to inScreenDensity.

InScreenDensity: the screen density of the phone itself, such as the Samsung phone dpi=640 we tested, if the inDensity is not equal to the inTargetDensity, you need to zoom the picture, inScaled = inTargetDensity/inDensity.

V. Summary

1. Images occupy memory = wide * high * (mobile screen density / resource folder density) ²* pixel size, so we mainly consider two aspects when optimizing image memory usage:

1) there are two ways to control the image loading size:

Set the sampling rate to control the width and height of the load; control the zoom ratio by setting inDensity and inTargetDensity.

2) set the color mode to control the pixel size. If you do not need a transparent picture, you can set the color mode to Bitmap.Config.RGB_565 to directly reduce the memory by half.

two。 Different resolution folders put different resolution images in order to ensure memory overhead, but it will increase the packet size accordingly, so it needs to be weighed according to the actual situation.

What is Android? Android is a free and open source operating system based on the Linux kernel, mainly used in mobile devices, such as smartphones and tablets, led and developed by Google and the Open Mobile Alliance.

Thank you for your reading, the above is the whole content of "how to achieve the in-depth analysis of the memory occupied by pictures in Android", learn friends to hurry up to operate it. I believe that the editor will certainly bring you better quality articles. Thank you for your support to the website!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report