A brief introduction to Android UI rendering

1. Screen refresh mechanism

In a typical display system, there are generally three parts: CPU, GPU and display. The CPU is responsible for calculating data and delivering the calculated data to the GPU, which renders the graphics data and stores it in buffer after rendering it. The display is responsible for presenting the buffer data on the screen.

The same is true in Android. The CPU/GPU prepares data and stores it into buffer. Display retrieves data from buffer at regular intervals and displays it

Display in Android, the read frequency is fixed at 16ms. The reason for the 16ms is that the coordination between the human eye and the brain cannot perceive updates beyond 60fps.

When we use the APP, when the interface is stuck and not smooth, it is because the UI processing of the current interface exceeds 16ms, which will occupy the next 16ms, resulting in the same frame of 16ms*2, namely “stuck”.

After understanding the Android rendering mechanism, we will analyze why the app takes more than 16ms to redraw. There are usually the following reasons:

1. Unreasonable level of layout

2. The layout is overdrawn

In view of the above situation, we will talk about some common monitoring methods and layout and optimization methods

2. Monitoring means

1. Check the Layout hierarchy using Layout Inspater

Layout Inspater is a built-in AndroidStudio tool for analyzing Layout levels

1. Run your application on a connected device or simulation

2. Click Tools > Layout Inspector

3. In the Choose Process dialog box that appears, select the application Process you want to check, and then click OK.

4. By default, the Choose Process dialog will only list processes for projects currently open in Android Studio that must be running on the device. To check out other applications on your device, click Show All Processes. If you are using a device with root permissions or do not have an emulator installed for the Google Play Store, you will see all the apps running. Otherwise, you only see running applications that can be debugged.

View Tree: Hierarchy of views in a layout

Screenshot: Screenshot of a device with visual borders of each view.

Properties Table: Layout Properties of the selected view

In this case, we’re using a View Tree. We’re using a RelativeLayout with a SettingItem wrapped around each column of the page. The SettingItem custom View inherits from a RelativeLayout. You can use the Merge tag to optimize and lower the hierarchy

Of course, it is best to use a constrained layout, which can reduce the hierarchy considerably

2. Use the debug GPU overdraw function to check overdraw

Find the debug GPU overdraw function switch in Developer mode and turn it on

  • Blue, green, light red, dark red
  • Divided into four levels, where blue is acceptable, when red should be optimized

This kind of overdrawing is common in background Settings

3. Use Choreographer to monitor frame rates

public class FPSMonitor implements Choreographer.FrameCallback.Runnable{

    private HandlerThread mHandlerThread;

    private long startTime = -1;

    private long endTime = -1;

    private final int MONITOR_TIME = 1000;

    private Handler mWorkHandler;

    private int mFpsCount;

    @Override
    public void doFrame(long frameTimeNanos) {
        if (startTime == -1) {
            startTime = frameTimeNanos;
        }
        mFpsCount++;
        // After one second, send a message to the worker thread to calculate the frame rate
        long duration = (frameTimeNanos - startTime) / 1000000L;
        if (duration >= MONITOR_TIME) {
            endTime = frameTimeNanos;
            mWorkHandler.post(this);
        } else {
            // No more than one second, set the next frame to listen
            Choreographer.getInstance().postFrameCallback(this); }}@Override
    public void run(a) {
        // Calculate the frame rate
        long duration = (endTime - startTime) / 1000000L;
        float frame = 1000.0 f * mFpsCount / duration;
        Log.i("Restart"."Current frame rate:" + frame);
        // Start the next-second calculation
        start();

    }

    public void start(a) {
        if (mHandlerThread == null) {
            mHandlerThread = new HandlerThread("FPS Monitor Thread");
            mHandlerThread.start();
            mWorkHandler = new Handler(mHandlerThread.getLooper());
        }
        // Reset the calculated value
        startTime = -1;
        endTime = -1;
        mFpsCount = 0;
        // Set the frame draw listener
        Choreographer.getInstance().postFrameCallback(this); }}Copy the code

4. Use setFactory2 to count the creation time of a View

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // Note: super.onCreate(savedInstanceState); I didn't get an error before
        LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                long start = System.currentTimeMillis();
                AppCompatDelegate delegate = getDelegate();
                View view = delegate.createView(parent, name, context, attrs);
                long duration = System.currentTimeMillis() - start;
                Log.i("Restart", name + Drawtime of ":" + duration);
                return view;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null; }});super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
    }
Copy the code

Three, common means of layout optimization

1. Use the merge tag to lower levels

Here is one of the most common scenarios:

// Assume that the custom View is RelativeLayout
public class LoginButton extends RelativeLayout {... }// Using merge as the root tag in the XML tag instead of using RelativeLayout again as the root tag eliminates a hierarchy
Copy the code

2. Use ViewStub

The ViewStub is used when a layout may or may not need to be loaded. If the layout delayInflateLayout may or may not be needed. You can use the ViewStub tag, as shown in the following code, in the layout file.

<ViewStub
        android:id="@+id/contentPanel"
        android:inflatedId="@+id/inflatedStart"
        android:layout="@layout/delayInflateLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        />
Copy the code

Control whether it’s actually loaded or not in your code

// Calling the inflate actually loads it, but only once
viewStub.inflate();
Copy the code

3. Use clipRect

The function of clipRect can be understood as using some rectangles of variable size to cut one by one on a large canvas. In a certain rectangle, we draw the graph we want to draw, and do not draw the graph beyond it. When our app writes custom views in this way, we can avoid the superposition between views. This results in the same pixel being drawn multiple times, and that’s how it works. Here’s an example:

The above three graphs, overlapping each other, the onDraw method is optimized as follows:

@Override
protected void onDraw(Canvas canvas)
{

    super.onDraw(canvas);

    canvas.save();
    canvas.translate(20.120);
    for (int i = 0; i < mCards.length; i++)
    {
        canvas.translate(120.0);
        canvas.save();
        if (i < mCards.length - 1)
        {
            // Trim the canvas to reduce unnecessary drawing
            canvas.clipRect(0.0.120, mCards[i].getHeight());
        }
        canvas.drawBitmap(mCards[i], 0.0.null);
        canvas.restore();
    }
    canvas.restore();

}
Copy the code

4. Use constraint layouts to lower levels

5. Load layout files asynchronously using AsyncLayoutInflator

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null.new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) { setContentView(R.layout.activity_main); }}); }Copy the code

Limitations of using AsyncLayoutInflater:

All views built must not directly use Handler or call looper.mylooper () because asynchronous threads do not call looper.prepare () by default;

Asynchronously converted views are not added to the parent View and must be added manually.

AsyncLayoutInflater does not support LayoutInflater.Factory or LayoutInflater.Factory2.

Also, the default cache queue size limit of 10 will cause the main thread to wait if it exceeds 10.

Four, summary

It is also impossible to develop An Android application with unlimited CPU and memory usage, which can cause problems such as application stuttering and memory overflow. Therefore, performance optimization is every Android developer should pay attention to, this article introduces some monitoring methods and common optimization methods, hope to help you ~