I usually like to open the GPU rendering mode to see how the application rendering performance of my own App and other apps is, and to think about the business logic they may exist. Of course, this process is still based on my own App, but I am an ordinary employee in the business line, with a full schedule of daily development function iterations, so I have no time to care about performance, let alone the so-called optimization and thinking. But finally after an event, I had time to focus on performance and related learning. Let’s get down to business:
I. Identify problems
As shown above, this is a list of recommendations for a normal waterfall stream display. We can clearly and intuitively find that the yellow bar takes too long in the rendering processThe official documentation, indicating a problem with processing/swapping buffers.
The official description of the problem is blunt:
It is important to note when this segment is long that the GPU and CPU work in parallel. The Android system issues a draw command to the GPU, and then moves on to the next task. The GPU reads and processes these draw commands from the queue.
If the CPU issues commands faster than the GPU can process them, the communication queue between the two processors fills up. When this happens, the CPU blocks and waits until there is a place in the queue for the next command. This queue full state usually occurs during the swap buffer phase, when the command for the entire frame has been submitted.
But the official solution is very vague:
The key to mitigating this problem is to reduce the complexity of GPU work, just as you did in the “issue commands” phase.
Don’t you know what you’ve done? Check it out. Personally, when it comes to GPU, the system can’t distinguish what the upper layer is, because the upper layer is the system SDK API used by common applications. Is it a custom OpenGL drawing with SurfaceView, or is it a game application, the system is not clear at this step, the system only knows this step, you submitted something to the GPU to render, but the task is quite large. Therefore, the document does not provide a clear investigation plan. As a result, I have no idea what to do when facing this problem.
2. Train of thought analysis
Although the official did not provide a clear direction for investigation, since it is the GPU that is overloaded, we can face up to the problem and what causes the GPU to be overloaded according to Android Rendering mechanism — Principle (analysis of the whole process of display principle), the initial guess is that the application side frequently calls setLayoutParams() method to update the DisPlayList frequently, or because there are too many pictures in the recommended list, using Glide improper. Then following this idea, I did the following investigation:
Three. Investigation process
The preliminary screening
The screenshots of GPU presentation mode in the process of investigation are not shown here, and the following analysis process is not sorted in chronological order. It is only a summary of investigation process. Besides, because the corresponding business logic is simple, the investigation method is simple and rough, and each one is analyzed separately without superposition analysis.
- Eliminate all calls to setLayoutParams() method in business code, analyze, tuning effect is not ideal
- Eliminate the business code in addition to set the picture of other code implementation, the tuning effect is not ideal
- Kill the business code to set the picture of the code to achieve good tuning effect, temporarily achieve the goal in the heart, but against the business
- Eliminate the code implementation of sliding listening in the business code, the tuning effect is not ideal
Here we see an improvement in performance after killing the code that sets the image, which, in terms of impact, is probably the problem. Although there are scenarios where a single image has little impact and multiple performance problems have a great impact, in this example, it is suitable for a single performance problem because the performance of images before and after setting is quite different. So the next step is a targeted sweep
Targeted investigation: picture related
There is nothing in the code here, just a simple call to the image loading library:
ImageLoader.with(getContext()).load(ecItem.picUrl).placeholder(R.color.colorFAFAFA).into(((MineRecommendViewHolder) holder).ivItem);
Copy the code
Because the business is relatively simple, the ImageLoader class is just a simple encapsulation of Glide, so EVEN if I used Glide directly for image loading, there was no performance impact. But the performance issue here made me take a hard look at this code. This code passes ImageView to Glide, which makes me wonder what method Glide calls to set the image on. So I added a breakpoint to the ImageView source code and discovered that Glide is finally set by calling the ImageView setImageDrawable() method.
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> { public DrawableImageViewTarget(ImageView view) { super(view); } /** @deprecated Use {@link #waitForLayout()} instead. */ // Public API. @SuppressWarnings({"unused", "deprecation"}) @Deprecated public DrawableImageViewTarget(ImageView view, boolean waitForLayout) { super(view, waitForLayout); } @Override protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); }}Copy the code
In ImageView setImageDrawable(resource) source:
public void setImageDrawable(@Nullable Drawable drawable) { if (mDrawable ! = drawable) { mResource = 0; mUri = null; final int oldWidth = mDrawableWidth; final int oldHeight = mDrawableHeight; updateDrawable(drawable); if (oldWidth ! = mDrawableWidth || oldHeight ! = mDrawableHeight) { requestLayout(); } invalidate(); }}Copy the code
I found that requestLayout() would be called every time this method was called during debug, which made me very confused. Is it possible that requestLayout() would be called every time a picture was set up due to the incorrect use of Glide? Therefore, I decided to search for it and found the culprit.
Four. Find the problem
I used the search engine to search “Glide requestLayout” and one of the results was an onLayout loop of NineGridImageView. Since the image in this example shows a View that is not an official ImageView but a custom View introduced earlier, I replaced the custom View in the current list with the official ImageView, and the rendering performance improved immediately :
From the GPU rendering mode, the yellow becomes lighter and the bars are basically shortened near the green line, rather than the red line before.
V. Problem analysis
The custom View used in this case is called NBImageView, which is an online custom View introduced by an arrogant developer in the early stage of the project. This View can realize the processing of some circles, rounded corners and borders on the image. According to some comments in the custom View, I found NBImageView The corresponding original version on the web, called the NiceImagerView project link, found that the project was created 3 or 4 years ago, around the same time as the project started, and was not very popular. The project should just be a small Demo for its authors, so there is no adaptation for android9 and its successors.
The core of the project is the NiceImagerView class, which inherits from the system ImageView and adds some processing of its own. In the onDraw() method, this looks like this:
Override fun onDraw(canvas: canvas) {// Use graphics blend mode to display the image of the specified region canvas.saveLayer(srcRectF, null, canvas. ALL_SAVE_FLAG) if (! IsCoverSrc) {val sx = 1.0f * (_width - 2 * borderWidth - 2 * innerBorderWidth) / _width val sy = 1.0f * (_height - 2 *) BorderWidth - 2 * innerBorderWidth) / _height // Shrink the canvas, Canvas. Scale (sx, sy, _width / 2.0f, } super.ondraw (canvas) paint. Reset () path.reset() if (isCircle) {path.addCircle(_width / 2.0f, _height / 2.0f, radius, path.direction.ccw)} else {path.addroundrect (srcRectF, srcRadii, Path.Direction.CCW) } paint.isAntiAlias = true paint.style = Paint.Style.FILL paint.xfermode = xfermode if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { canvas.drawPath(path, Paint)} else {srcpath.reset () srcpath.addRect (srcRectF, path.direction.ccw) // Calculate the difference between tempPath and Path. Path.op. DIFFERENCE) canvas. DrawPath (srcPath, paint)} paint. Xfermode = null // Draw the mask if (maskColor! Paint. Color = maskColor canvas. DrawPath (path, paint)} // Restore canvas.Copy the code
The onDraw() method starts by calling canvas.savelayer (). What does this method do? As described in the first edition of Android Custom Development:
A new Bitmap is generated that is the size of the area we specify to save. The newly generated canvas is completely transparent, and all drawing operations are performed on this canvas after the saveLayer() function is called.
The flow looks fine, but when I click into the saveLayer() method to check the source code, the official comment reads:
Note: this method is very expensive, incurring more than double rendering cost for contained content. Avoid using this method, especially if the bounds provided are large. It is recommended to use a hardware layer on a View to apply an xfermode, color filter, or alpha, as it will perform much better than this method.
This comment could not be more explicit, calling this method doubles the rendering cost, and recommends that we use hardware acceleration to handle custom views.
To this, the rendering problem investigation on the temporary break.
6. Summary
(1). Relevant to this example
1. Is the rendering performance bottleneck found in this example
You could say you only caught one point. First, in the generic layer, we see a performance bottleneck in the custom ViewNBImageView in the onDraw() method. As for the other code in the method, there is no performance bottleneck in the other code in the class, which needs to be analyzed in detail to know. Second, in the business layer, we see a performance bottleneck after the replacement of the official ImageView The GPU rendering rendering bar also has no excellent performance, which needs to be further analyzed in combination with the business.
2. How to prove that the custom View analyzed in this article is the performance bottleneck of this example
In fact, what is described above is just like what I wrote in the title of an investigation record, which describes my thinking and my investigation practice based on previous experience. There is no data to support that the performance bottleneck in this example is a custom View. In fact, a more scientific way is to use performance analysis tools (such as Systrace) to data the time consuming of each method, so as to analyze and draw conclusions. I stumbled across the saveLayer() rendering bottleneck while analyzing the list performance bottleneck and simply thought I should document the whole process. And even if you follow the solution in section 3 below, the performance improvement may not be as good as using the ImageView replacement in this example. It will take practice, and I will probably write a blog post on this.
3. If it is a custom View problem as mentioned in the investigation, how to solve this problem
In fact, for this example, the use of the View is nothing more than to achieve the rounded corner processing of the picture, Android application development to now, there are many frameworks to achieve this function, nothing more than whether the developers themselves know and use habits.
- Continue to use the custom View, analyze the entire custom View class code, change the code to replace the performance bottleneck
- Replace it with another good third party custom View library
- Custom Glide library
DrawTransformation
To implement the function - Use the related classes provided with the official SDK
(2). Other thoughts
Application developers should evaluate the target tripartite library whenever they need to introduce it:
- What is the community reputation of this library? Is it being maintained
- Does the library have all the features I need now, does it have more features than I need, and does it have features I want to add at a certain time in the future
- How customizable is the library extensible
- What is the performance of the library, data safety, thread safety, etc.