preface

Recently, some online users reported that when they encountered large pictures in the process of using the App, the App would be abnormally slow and even crash. Later investigation, found that a colleague in the processing of pictures, direct loading of the original image without any “compression”. The emergence of this case leads to the necessity of this article.

We use a variety of photo libraries such as Glide in our daily development. Since all image manipulation is handed over to the image library, there is no way to “reproduce” problems even when large images are loaded.

Because the major image libraries are helping us to process the big picture (which confirms the saying: when you can easily get in, you should know that it’s not you, just someone ahead of you – “lu Xun”).

Since the words are open, we as the new era of blessing programmers, it must be explored in this way. In fact, there are many ways of image compression, today we only want one, that is Google’s native efficient loading of large image scheme.

The body of the

Before we compress, let’s get a feel for what happens without compression…

First, load the large image directly without compression

I randomly new the project and made a diagram like this:

It’s not that big, it’s just a 1080P image.

Then use a random ImageView to load it:

iv.setImageResource(R.drawable.test)
Copy the code

When I tried run, I overestimated my test machine…. It didn’t load, it just crashed. Logcat is also direct and merciless:

Such a picture requires 132710400Bytes of memory, that is, 132m…. Wait, no? ! How can a 1080 * 1920 resolution image use 100+ M of memory?

As we all know, the normal file size of an image loaded into memory = width of image resolution * height of image resolution * color format. Put this into the formula: memory size = 1080 * 1920 * 4 = 7.9m, it can’t be 100+ M!

JPEG format has no alpha channel and should not take up so much space. The final value of bitmap. Config is ARGB_8888, so *4.

If you have the same question, then you should read the following content. I’m afraid this knowledge is a blind spot…

What is the difference between drawBLE and xxHDPI

As an extra content section. This chapter has nothing to do with image compression, just a little extra talk about the drawble folder, okay

The root cause of the above problem is the location of the file, I only put the image resources in the Drawble folder.

So… In this case, if the phone loaded the resource has a high-density screen, then the image is displayed instead of 1080 * 1920…

Let’s take a look at why throwing resource files around can cause such a big problem! (Below, partly from official documentation)

As mentioned in the documentation, scaling distortion can result if resources are not provided properly… . It’s easy to see why the system should be scaled:

  • For the system, if it goes down (low density) to find the resource file to reference, the best strategy is to zoom in on the image resource as a whole. Because the graphics there, they’re supposed to be for low resolution phones.

  • Similarly, if the system finds resource files that need to be referenced up (high density), then scaling down is the best option. Because the graphics there, they’re supposed to be for high-resolution phones.

Based on this, the OOM memory value of 132710400bytes is calculated as follows: 1080 * 4 (the 4 is the phone DPI640 / resource DPI160) * 1920 * 4 * 4

Tip: Dpi = square root of the length and width of the phone’s resolution, divided by the length of the diagonal in inches. Of course we can also through the API: resources. DisplayMetrics. Xdpi. This is basically the dPI of the current phone


So, is it irresponsible to force loading such a large graph? So big, just shove it in, who can stand it?

3. Solutions provided by Google

Now that we have made it clear that hard work is no good, we still need to adopt some techniques. The article begins with the problem:

Images come in all shapes and sizes. In many cases they are larger than required for a typical application user interface (UI). For example, the system Gallery application displays photos taken using your Android devices’s camera which are typically much higher resolution than the screen density of your device.

Given that you are working with limited memory, ideally you only want to load a lower resolution version in memory. The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.

If it is too big, do not plug it. Shrink it to a suitable size

Here’s another interesting line from the document: There are several libraries that follow best practices for loading images. You can use these libraries in your app to load images in the most optimized manner. We recommend the Glide

Officially, it’s the deadliest

Ctrl +C/V: Ctrl +C/V

imageView.setImageBitmap(
    decodeSampledBitmapFromResource(resources, R.id.myimage, 100.100))fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    // Raw height and width of image
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1

    if (height > reqHeight || width > reqWidth) {

        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2}}return inSampleSize
}

fun decodeSampledBitmapFromResource(
        res: Resources,
        resId: Int,
        reqWidth: Int,
        reqHeight: Int
): Bitmap {
    // First decode with inJustDecodeBounds=true to check dimensions
    return BitmapFactory.Options().run {
        inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, this)

        // Calculate inSampleSize
        inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)

        // Decode bitmap with inSampleSize set
        inJustDecodeBounds = false

        BitmapFactory.decodeResource(res, resId, this)}}Copy the code

The code is easy to understand, is to load the image, according to the target required loading size for a sample, by sampling the value of the scale.

But here’s an interesting detail: the official code is to inSampleSize the sample result by * 2 (*= 2). InSampleSize doesn’t have to be a power of 2, it can be a power of 3 or 5.

The document says:

Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, As per the inSampleSize documentation, the decoder uses the final value by rounding to the nearest power of 2.

If inSampleSize is 2/3, the effect is the same. After all, the closest power of 3 to 2 is 2. When the facts run, 2 and 3 have different results:

When inSampleSize = 3, the image length and width ratio is reduced by 3 times… So I don’t know what’s on the website.

The end of the

To this, the Lao basic Lao also finished… The content is not abstruse, but also be necessary knowledge point ~

I am a fresh graduate, recently and friends maintain a public account, the content is that we in the transition from fresh graduate to the development of this way stepped on the pit, as well as our step by step learning records, if interested in friends can pay attention to it, together with fuel ~