preface

I have done some work about Android lag and performance optimization in the project before, but I haven’t had time to summarize it. I will summarize this part during this time.

caton

In application development, if you notice the log, you may sometimes send the following log information:

I/Choreographer(1200): Skipped 60 frames!  The application may be doing too much work on its main thread.
Copy the code

On most Android devices, the refresh rate is 16ms, or 60 frames per second. To achieve this refresh speed, the task time in the UI thread must be less than 16ms. If the processing time in the UI thread is long, the rendering of frames will be skipped, which will cause the interface to look awkward and staid. If the user clicks on event 5s and nothing happens, ANR will result.

Frame rate

Frame Rate (FPS) is the Rate at which a GPU generates frames. The more framere-related class in Android is SurfaceFlinger.

SurfaceFlinger The SurfaceFlinger accepts graphical display data from multiple sources, combines them, and sends them to the display device. For example, when opening an application, it is common to have three layers of display, the Statusbar at the top, the navigation bar at the bottom or side, and the application interface. Each layer is updated and rendered separately. These interfaces are synthesized by SurfaceFlinger into a refresh to the hardware display.

VSync

The Android system sends VSYNC signals every 16ms to trigger UI rendering. VSYNC, short for Vertical Synchronization, is an early and widespread technique used on PCS. It can be simply considered a timed interrupt. VSync has been introduced in Android 4.1(JB) to synchronize rendering, allowing the UI and SurfaceFlinger to work at the VSync rhythm generated by the hardware.

There are two kinds of VSync signals in Android system: 1. Hardware VSync generated by the screen: Hardware VSync is a pulse signal that switches or triggers an operation. 2. Software Vsync signals converted by SurfaceFlinger: sent to Choreographer via Binder.

In addition to Vsync, Android also uses multilevel buffering to optimize UI flow, such as double buffering (A+B). When buffer A is displayed, the CPU/GPU starts preparing the next frame in Buffer B: However, it is not guaranteed that the CPU and GPU of each frame are in good running state. It may be caused by performance problems such as resource preemption that A CERTAIN frame of GPU is off. When the vsync signal arrives, the data of Buffer B is not ready, while the Display is displaying the data of Buffer A. As a result, the subsequent CPU/GPU has no new buffer to prepare data, resulting in a lag (Jank).

Caton reason

At the system level, the following causes may cause the delay: 1. SurfaceFlinger main thread takes time

SurfaceFlinger is responsible for Surface composition. If the SurfaceFlinger main thread times out, SurfaceFlinger mainline time also causes HWC service and CRTC to fail to complete in time. Binder calls such as dequeueBuffer \ queueBuffer are blocked.

2. The system is busy due to too many background processes

Too much background process activity will lead to the system is very busy, CPU, IO, memory and other resources will be occupied, at this time it is easy to stall, which is often encountered in the system. Dumpsys cpuinfo can view CPU usage over a period of time:

3. The main thread cannot be scheduled and is in the Runnable state

When a thread is in a Runnable state, the scheduler can create a long Runnable thread state if it is not aligned for scheduling, causing fluency problems by missing Vsync.

4, the System locks

AMS and WMS locks of system_server can become very serious in the case of system abnormalities. As shown in the figure below, many key tasks of the system are blocked waiting for the release of the lock. At this time, if a Binder request with the lock is sent by App, Then it will enter the wait state, and the App will have performance problems; These locks on system_server will also cause Window animation to stall if you do Window animation at this point

5. Too many layers cause SurfaceFlinger Layer Compute time

Android P has changed the calculation method of Layer and put this part into the main thread of SurfaceFlinger. If there are too many layers in the background, This will cause SurfaceFlinger to take a long time to execute rebuildLayerStacks and cause SurfaceFlinger main thread to take a long time to execute.

1, main thread execution time long main thread execution Input \ Animation \ Measure \ Layout \ Draw \ decodeBitmap and other operations timeout will cause delay.

  • 1, Measure \ Layout time \ timeout

  • 2, Draw time consuming

  • 3. Animation callback time

  • 4. View initialization time

  • 5. Initialization time of List Item

  • 6. The main thread operates the database

2, main Binder time

When the Activity resumes, the communication with AMS needs to hold the AMS lock. At this time, if the background is busy, the lock operation will be time-consuming, resulting in some scenes due to this delay, such as multi-task gesture operation.

3. Insufficient WebView performance

When the application involves WebView, if the page is complex, the performance of WebView will be poor, resulting in lag

4. Frame rate doesn’t match refresh rate

If the frame rate of the screen doesn’t match the FPS of the system, the picture may not be smooth. Like using a 90 Hz screen with 60 FPS animation.

Caton detection

Caton detection can be performed simultaneously using dumpsys GFXInfo, Systrace for relevant information, LayoutInspect for layout levels, BlockCanary, and Choreographer. 6. Use StrictMode.

Use Dumpsys gfXInfo

You can use the following command to obtain information about the lag when it is found in the development process:

adb shell dumpsys gfxinfo [PACKAGE_NAME]
Copy the code

Entering this command might print the following information:

Applications Graphics Acceleration Info:
Uptime: 102809662 Realtime: 196891968
** Graphics info for pid 31148 [com.android.settings] **
Stats since: 524615985046231ns
Total frames rendered: 8325
Janky frames: 729 (8.76%)
90th percentile: 13ms
95th percentile: 20ms
99th percentile: 73ms
Number Missed Vsync: 294
Number High input latency: 47
Number Slow UI thread: 502
Number Slow bitmap uploads: 44
Number Slow issue draw commands: 135
Copy the code

Parameter description:

Graphics info for PID 31148 [com.android. Settings]: The pICS are 31148 Total frames rendered: Information of 8325 frames has been collected in this dump

Janky frames :729 (8.76%) 729 frames, or 8.76%

Number Missed Vsync: 294 Indicates the Number of frames that failed Vsync

Number Slow THREAD: 502 Number of frames timed out due to work on the UI thread

Number Slow Bitmap uploads: 44 Specifies the Number of frames required by bitmap loading

Number Slow issue Draw Commands: 135 The Number of frames taken by drawing

2. Use Systrace

Dumpsys, used above, can detect a problem or determine the severity of a problem, but cannot pinpoint the actual cause. To locate the cause, use the Systrace tool.

Systrace use

Systrace can help analyze how the application is running on the device. It focuses the system and application threads on a common timeline. The first step of analyzing Systrace is to capture trace logs during the running period of the program. Interaction.

Systrace is used in Android Studio

1. Set Up Settings for android devices — Developer Options — Monitor — open Traces. 2. Select the category you want to follow and click OK.

After the above configuration is complete, start to capture the trace file

$ python systrace.py --cpu-freq --cpu-load --time=10 -o mytracefile.html
Copy the code

Analyze the trace file and open the trace. HTML file using a Web browser

Each application has a row of circles representing render Frames, usually green, and yellow or red if the drawing takes longer than 16.6 milliseconds. View frames by using the W key.

Trace application code

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {...@Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Trace.beginSection("MyAdapter.onCreateViewHolder");
        MyViewHolder myViewHolder;
        try {
            myViewHolder = MyViewHolder.newInstance(parent);
        } finally {
            Trace.endSection();
        }
        return myViewHolder;
    }

   @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Trace.beginSection("MyAdapter.onBindViewHolder");
        try {
            try {
                Trace.beginSection("MyAdapter.queryDatabase");
                RowItem rowItem = queryDatabase(position);
                mDataset.add(rowItem);
            } finally {
                Trace.endSection();
            }
            holder.bind(mDataset.get(position));
        } finally {
            Trace.endSection();
        }
    }

…

}
Copy the code

3. Use BlockCanary

BlockCanary is a performance monitoring component developed by MarkZhai, a Chinese developer. It monitors the main thread operation transparently and outputs effective information to help developers analyze and locate problems and optimize applications quickly. Its features are: 1, non-invasive, simple two lines open monitoring, no need to dot around, destroy code elegance. 2, accurate, the output information can help locate the problem (accurate to line), do not need to like Logcat, slowly to find. 3, currently includes the core monitoring output file, and THE UI display card information function

Basic principles of BlockCanary

The Android application has only one main thread, ActivityThread. This main thread creates a Looper(looper.prepare), which in turn associates a MessageQueue, The main thread Looper polls (Looper.loop) for the life of the application, fetching messages from MessageQueue to update the UI.

public static void loop(a) {...for(;;) {...// This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if(logging ! =null) {
            logging.println(">>>>> Dispatching to " + msg.target + "" +
                    msg.callback + ":" + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if(logging ! =null) {
            logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); }... }}Copy the code

BlockCanary mainly detect MSG. Target. DispatchMessage (MSG); The interval between the previous >>>>> Dispatching to and the later <<<<< Finished to. A time-consuming operation must have been performed in dispatchMessage. By setting a Printer for the main thread Looper, the execution time of dispatchMessage method is counted. If it exceeds the threshold value, it means that the delay occurs, and various information is dumped to provide the developer to analyze the performance bottleneck.

4. Use Choreographer

The essence of Android main thread operation is actually the process of Message processing. Our various operations, including the rendering operation of each frame, are sent to the MessageQueue of the main thread in the form of Message, and MessageQueue continues to wait for the next Message after processing the Message.

Choreographer has two main functions

1. Responsible for receiving and processing various update messages and callbacks of App, and dealing with them uniformly when Vsync arrives. For example, processing Input(mainly processing Input events), Animation(Animation related), Traversal(including measure, layout, draw and other operations), judging the frame delay, recording CallBack time, etc.

2. Startup: responsible for requesting and receiving Vsync signals. Receive Vsync event callback (through FrameDisplayEventReceiver onVsync); Request Vsync (FrameDisplayEventReceiver scheduleVsync).

Use Choreographer to calculate frame rates

Choreographer processing drawing logical core in Choreographer. DoFrame function, from the image below you can see, FrameDisplayEventReceiver. OnVsync post himself, The run method directly calls the doFrame logic to start a frame:

public class MyFrameCallback implements Choreographer.FrameCallback {
        private String TAG = "Performance Test";
        private long lastTime = 0;

        @Override
        public void doFrame(long frameTimeNanos) {
            if (lastTime == 0) {
                // The code is initialized for the first time. Do not test statistics.
                lastTime = frameTimeNanos;
            } else {
                long times = (frameTimeNanos - lastTime) / 1000000;
                int frames = (int) (times / 16);

                if (times > 16) {
                    Log.w(TAG, "UI thread timed out (over 16ms):" + times + "ms" + ", frame loss :"+ frames); } lastTime = frameTimeNanos; } Choreographer.getInstance().postFrameCallback(mFrameCallback); }}Copy the code

Caton optimization

According to the above analysis, object allocation, garbage collection (GC), thread scheduling and Binder invocation are common causes of lag in Android system. Therefore, the main methods of lag optimization are as follows, and more should be carried out in combination with specific applications:

1. Layout optimization

  • Reduce view hierarchies by reducing redundancy or nesting layouts. For example, use constrained layouts instead of linear and relative layouts.
  • Replace UI controls that do not need to be displayed during startup with viewStubs.
  • Use custom views instead of complex View overlay.

2, reduce the main thread time-consuming operation

  • Do not operate on the database directly in the main thread; database operations should be done in the database thread.
  • Use apply rather than COMMIT for sharePreference. MMKV framework can be used instead of SharePreference.
  • Data parsing for network requests should be kept in child threads rather than replicated data parsing operations in the main thread.
  • Do not perform time-consuming operations, such as a large number of calculations, in the activity’s onResume and onCreate.

3. Reduce overdrawing. Overdrawing means that the same pixel is drawn many times, which generally reduces the layout background overlay, as shown below.

4. List optimization

  • RecyclerView uses optimization, uses DiffUtil and notifyItemDataSetChanged for local updates, etc.

5. Optimization of object allocation and recycling

Since Android introduced ART and made it the default runtime on Android 5.0, the lag caused by object allocation and garbage collection (GC) has been significantly reduced, but it can still overload threads due to the additional overhead of object allocation and GC. Allocating objects in a place where they are called infrequently (such as button clicks) is fine, but in a tight loop where they are called frequently, object allocation needs to be avoided to reduce GC stress.

  • Reduce frequent allocation and collection of small objects.

reference

1, source. Android. Google. Cn/devices/gra… 2, www.bradcypert.com/what-is-and… 3, ashishb.net/tech/demyst… 4, devblogs.microsoft.com/xamarin/tip… 5, developer.android.com/training/te… 6, www.androidperformance.com/2019/09/05/… 7, developer.android.com/tools/help/… 8 zhuanlan.zhihu.com/p/87954949.