SoFunction
Updated on 2025-03-02

Complete Guide to Picture Optimization in Android

Preface

As a memory consumer, pictures have always been the focus of developers' attempts to optimize. The memory of Bitmap was changed from native before 3.0 to jvm later, and then to 8.0 to native. Fresco spent a lot of effort to change Bitmap memory back to native before the 5.0 system. The higher version followed the system implementation, but was slapped in the face by the official.

Each process of jvm has a memory upper limit, while native has no limit (not without impact, at least not oom), so it may be a dream for many people to move the memory big player Bitmap to native, but the management and implementation of native is obviously more complex than jvm. Unless there is a ready-made implementation, few people will move this part. Most of the picture libraries in the industry do not involve this area, and most programmers have been using it for many years with the attitude of being good enough, which shows that programmers are also lazy. What is the reason for the official strategy modification? In fact, I didn’t find any relevant instructions. Students who know it are welcome to leave a message.

concept

Memory occupied by the image: Image height * Image width * Memory size occupied by a pixel This formula represents the memory size of the final occupied by an image. The optimized memory occupied by the image in the project is optimized through these three parameters.

The first rule: Save Bitmap to native

The pictures in an app will have sizes. Generally speaking, the size of the picture is the size of the view. The size of the view varies from the actual pixels shown on different machines after we use dp units. In order to save traffic overhead, speed up the return speed, and meet the principle of on-demand loading, we should only load pictures of the actual view size. Generally, image storage providers will provide online compression services, and we only need to add parameters to the request link. There is another question here. The code we usually request to load the image is written in the Activity onCreate or Adapter's getView function. At this time, the view size cannot be obtained (not yet measured). Here are several methods:

Visual inspection of use:For example, a list is arranged on the left and right pictures, you can request a picture with half the width of the screen.

The view uses a fixed size:There is no problem with this, just take the width and height of getLayoutParams()

maxWidth/maxHeight of the view:The view cannot be fixed in size. We can configure maxWidth/maxHeight for the view in XML to guide the image library to load the image library with the size of the picture.

Measure before loading the image:Not very recommended, because the view needs to be measured once after the image is loaded

The general approach is to wrap a layer of the image loading library, and determine whether the size has been specified based on the passed url (of course, the developer can decide how big the image you want to load). If it has not been specified, use the above strategy to dynamically adjust it. If the scaling parameters are not added in the end, there is a bottom-up strategy that does not load more than the screen size.

Second rule: Request on demand

After doing the above loading on demand, there is still a problem. You will find that sometimes different pages need to load the same image url, but there are slight differences in size, which leads to duplication of requests (usually, the image loading library is url as cache key), which is a bit backfiring, and instead wastes traffic and time. We need to make some fine adjustments in this situation. For page A, the image size is 200x200, and for page B, the image size is 180x180, we believe that the image of 200x200 can be used to zoom to 180x180. There are two ways to do this: the first is to let developers always load slightly larger images, which is a bit high, and it is difficult to contact front and back when developing a page. The second type is to modify the image loading library to automatically complete this matter. The latter is natural and reasonable. When modifying the image loading library, you can just determine whether there is a larger cache than yourself. Of course, this strategy can be adjusted by each product itself. For example, it is acceptable to think that the existing cache size is smaller than a certain value. There are also complex situations such as how to deal with the different aspects of cached images and the ones that need to be strengthened, etc. You can decide your own strategy, but it is necessary to do this.

I would like to add here that there are several image domain names for large products, which are used for link selection. You must remember that the URL used for keys used to make keys must be removed from the domain name influence.

To add to this, some special usage scenarios can be considered in the first method mentioned above. For example, an operation will definitely load a 100x100 graph, and then it will definitely load the same image of 500x500. This scenario will obviously load twice when dealing with it in the second method, but it is obviously better if the developer loads 500x500 in these two locations. So the method is dead, people live, it depends on the actual use scenario.

There are also some special scenarios, such as there are two processes in the program. Process A will load a 500x500 graph, and Process B will load the same graph of no matter what size. By default, these two requests will be issued at the same time, which is likely to cause duplicate requests. In this case, it is necessary to do some cross-process synchronization, or simply make a little delay processing of one of the processes requests.

Rule 3: Merge similar requests

If you really have to load large images or original sizes from the server, or because of the above strategy, you need to sample them when decode. This is a cliché. Use them to obtain the original size, and then use them as needed to sample the pictures to the size close to the view.

Rule 4: Loading on demand

When decode Bitmap, you can use inPreferredConfig to specify the configuration format. Common ones include:

Parameter value meaning ALPHA_8 each pixel is stored in one byte (8 bits), which stores the transparency value of the 8 bits of the picture RGB_565 Each pixel in the picture is stored in two bytes (16 bits), two bytes are stored in two bytes (16 bits), two bytes are stored in four bytes (32 bits), and the four channels of Alpha, R, G, B are stored in four bytes (32 bits), and each pixel in four bytes (88888 each pixel in the picture is stored in four bytes (32 bits), and each channel is represented in four bytes (8888.

ARGB_888 can be used for pictures with high quality details, which is also the default configuration of fresco. For JPG pictures, RGB_565 can be used. From the above, we can see that the memory usage is reduced by half, which is very attractive. In fact, most of the apps should be JPG. But often development is defeated in the PK with vision, which will not help the quality of the picture! ! Development is always persistent, we can recommend adopting a strategy like this: for JPGs with sizes smaller than a certain size (such as 300), we use 565, while for large images we still use 8888 to preserve the details. Still the same saying is the strategy.

Rule 5: Further loading on demand

Using a Level 3 cache mechanism and memory disk network, this is also the official recommended method. Memory cache is designed to speed up access, and disk cache avoids repeated requests. I won't go into details about this, basically open source image libraries do this

Rule 6: Use Level 3 Caching Mechanism

In many scenarios, we need to display part of the picture, or superimpose the image effect, such as making a reflection. Many students prepare to createBitmap when they come up, and then draw the overlay effect to this temporary Bitmap, or first cut out part of the original Bitmap to generate a new Bitmap, and then set it to ImageView. Or use createScaledBitmap for scaling. Students who are even more careful may write these operation codes directly on the UI thread and then on the child thread, which is more troublesome. The recommended place here is to use custom drawing. Canvas has a drawBitmap method that can draw a certain area to a specified position. The overlay effect can also be completely used to draw it yourself, so that there will be no temporary Bitmap generation and the efficiency will be higher.

If there is difficulty in customizing the view, we can use Drawable. As long as we can get canvas, these two methods are the same.
Here are some examples so that you can further understand:

A button has normal and pressed states. Pressed is a mask that is superimposed on the normal state. There is no need to cut two pictures. The Drawable in the pressed state can use the canvas of the custom Drawable to draw the normal state first, and then draw a layer of color on it. Or press the status to use LayerDrawable, this Drawable will automatically help you do this.

You need to display the area of ​​[0,0,200,200] of Bitmap onto the ImageView, and use (bitmap, [0,0,200,200], [0,0, picture width, picture height], paint)

Drawing reflections is more logical, so I won't go into details here. Under the operation learning of canvas, combining local drawing is actually very simple.

There is a picture that needs to display a corner mark in the upper left corner. Normally, a view needs to be placed in the upper left corner. If you use Drawable to customize it, just draw it, similar to the following example code.

Let me give you a custom drawing example, assemble it as you like:

class WithLineDrawable extends DrawableWrapper {
 private MyConstantState mMyConstantState;
 private boolean mForTop;
 private Paint mLinePaint = new Paint();

 public WithLineDrawable(Drawable drawable, boolean forTop) {
 super(drawable);
 (getLineColor());
 mForTop = forTop;
 }

 @Override
 public void draw(Canvas canvas) {
 (canvas);
 if (mForTop) {
   (0, 0, getBounds().width(), 0, mLinePaint);
  } else {
   (0, getBounds().height(), getBounds().width(), getBounds().height(), mLinePaint);
  }
 }

 @Nullable
 @Override
 public ConstantState getConstantState() {
 if (mMyConstantState == null) {
 mMyConstantState = new MyConstantState();
  }
 return mMyConstantState;
 }

 class MyConstantState extends ConstantState {
 @NonNull
  @Override
 public Drawable newDrawable() {
 return new WithLineDrawable(getWrappedDrawable().getConstantState().newDrawable(), mForTop);
  }

 @Override
 public int getChangingConfigurations() {
 return 0;
  }
 }
}

You must change your concept from Bitmap to Drawable. When you are still trying to figure out how to deal with Bitmap, think about how to use canvas in Drawable for various custom drawings.

Rule 7: Use custom View or Drawable to customize drawing more often

The image format has developed to this day. Currently, many open source libraries support webp instead of jpg and gif. Webp has many advantages in compression ratio. Although it is slightly inferior in decoding, it is still very good after our tests. It is also recommended for everyone to use it. Whether it is online image download or built-in Apk, it is very suitable to replace jpg, and it will take some time to replace png. It is mainly because the low-version system still has some compatibility issues for transparent webp. Android P supports the heif format and wants to replace jpg, but this format has not been carefully studied yet.

For icon classes with built-in Apk, it is recommended to use svg, no longer need to cut several sets of images, and it is very small. The official compat package decoding svg will be cached, which further improves performance. However, it is precisely because of this that try not to use too many different sizes in one image. After most icons use code instead of pictures, the size of the apk can be significantly reduced, which is in line with our principle: if you can draw a program, you will never cut the picture.

Rule 8: Use better image formats

Many times we need to change the color of the icon. There is a set of theory about color mixing. The official has supported it very early. It uses ColorFilter. Later, a tint is released in the compat package. Therefore, if there is a related logic for color mixing processing, do not generate a temporary Bitmap, and use a code similar to the following:

//1: Generate Drawable instance through image resource fileDrawable drawable = getResources().getDrawable(.ic_launcher).mutate();
//2: First call the wrap method of DrawableCompat firstdrawable = (drawable);

//3: Then call the setTint method of DrawableCompat to color the Drawable instance
(drawable, );

Rule 9: Use the Shading API

There are many image resources built-in Apk, and there are always some regular images that still need to use jpg or png. We need to find a way to further compress them so that we can effectively control the size of the apk. Here we recommend using ImageOptim. This tool combines many compression methods and has significant effects.

Rule 10: Use compression tools

postscript:

Many interviews ask how to optimize image loading, and they will answer recycle

bitmap, in fact, this operation must be very cautious, if you are not careful, it will lead to problems. Most applications are not very good at doing this, thankless, so it would be great to leave it to jvm garbage collection. There are some parameters that can be optimized for image decoding, such as inBitmap, so I won’t go into details here.

To summarize

  • Save Bitmap to native
  • On-demand request
  • Load on demand
  • Merge similar requests
  • Use Level 3 Caching Mechanism
  • Use custom view or Drawable to customize drawing
  • Use better image format
  • Using the Shading API
  • Using the compression tool

Okay, the above is the entire content of this article. I hope that the content of this article has a certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.