Android performance monitoring - the practice of message scheduling startup optimization scheme

Author: Zhuo Xiuwu K

Simulate Degraded Scenarios

We first simulate a time-consuming message scenario that will affect cold start. In the demo, a time-consuming message is inserted before the message corresponding to startActivity.

package com.knightboost.appoptimizeframework

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.knightboost.optimize.looperopt.ColdLaunchBoost
import com.knightboost.optimize.looperopt.ColdLaunchBoost.WatchingState

class SplashActivity : AppCompatActivity() {
    val handler = Handler(Looper.getMainLooper())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        Log.d("MainLooperBoost", "SplashActivity onCreate")

    }

    override fun onStart() {
        super.onStart()
        Log.d("MainLooperBoost", "SplashActivity onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d("MainLooperBoost", "SplashActivity onResume")
        Handler().postDelayed({
            //发送3秒的耗时消息到队列中
            //这里为了方便模拟,直接在主线程发送耗时任务,模拟耗时消息在 启动Activity消息之前的场景
            handler.post({
                Thread.sleep(3000)
                Log.e("MainLooperBoost", "任务处理3000ms")
            })
            val intent = Intent(this, MainActivity::class.java)
            Log.e("MainLooperBoost", "begin start to MainActivity")
            startActivity(intent)
            //标记接下来需要优化 启动Activity的相关消息
            ColdLaunchBoost.getInstance().curWatchingState = WatchingState.STATE_WATCHING_START_MAIN_ACTIVITY
        },1000)

    }

    override fun onPause() {
        super.onPause()
        Log.d("MainLooperBoost", "SplashActivity onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d("MainLooperBoost", "SplashActivity onStop")
    }

}

The startActivity function here will generate two messages at the bottom of the implementation, and their purposes correspond to "Pause current Activity" and "resume MainActivity". At the end of the execution of the function, the message queue at this time is roughly like this (for the convenience of understanding, the message corresponding to the delay of 1 second and other messages are ignored).

The following video shows the code running effect. It can be found that after the splash screen page is displayed for one second, the page jump operation is not performed immediately, and it is blocked for 3 seconds.

Corresponding to the runtime log:

Then in order to prevent other messages from affecting the operation of startActivity, it is necessary to increase the order of startActivity to operate the corresponding messages.

Optimization

Message Scheduling Monitoring

To improve the order of the target message, we first need an opportunity to check the messages in the message queue. We can do it at the end of each message scheduling. If we find that there is a corresponding message in the current queue that needs to be upgraded, we will move it to the message queue. head.

There are two ways to schedule and monitor messages. In low-version systems, it can be implemented based on setting the Printer instead. However, this method can only get the start and end time of the message, but cannot get the Message object, and the Printer-based solution will have additional Performance overhead of string concatenation. The second is to set the message scheduling observer by calling the Looper's setObserver function. Compared with the Printer solution, it can get the scheduled Message object without additional performance overhead. The disadvantage is the limitation of hiddenApi.

Message type judgment

To modify the order of messages, you need to obtain the target message from the queue first. As mentioned in the previous section, startActivity will have two message schedules, namely: "pause the current Activity" and "resum the new Activity". In versions below Android 9.0, it can be distinguished by judging the target (Handler) and what value of the message, which correspond to LAUNCH_ACTIVITY (100), PAUSE_ACTIVITY (107) of mH Handler in ActivityThread

In Android 9.0 and above, all Activity life cycle transaction changes are merged into one message EXECUTE_TRANSACTION

So how does the high version judge that a message is for PauseActivity? Through source code analysis, it can be found that the obj property of the Message is an object of type ClientTransaction, and the return value of the getTargetState() function of the mLifecycleStateRequest of the object identifies the expected life cycle state

Take pauseActivity as an example, its actual object type is PauseActivityItem, and its getTargetState function returns ON_PAUSE =4.

Therefore, we can judge whether the message is pauseActivity or resumeActivity by first judging the value of Message what is EXECUTE_TRANSACTION (159), and finally obtaining the return value of the mLifecycleStateRequest object getTargetState function through reflection.

The following is the specific implementation code of the entire process: First, after startActivity, actively mark the subsequent messages that need to optimize the startup page

class SplashActivity : AppCompatActivity() {
//...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        Log.d("MainLooperBoost", "SplashActivity onCreate")
        Handler().postDelayed({
            //发送3秒的耗时消息到队列中
            //这里为了方便模拟,直接在主线程发送耗时任务,模拟耗时消息在 启动Activity消息之前的场景
            handler.post({
                Thread.sleep(3000)
                Log.e("MainLooperBoost", "任务处理3000ms")
            })
            val intent = Intent(this, MainActivity::class.java)
            Log.e("MainLooperBoost", "begin start to MainActivity")
            startActivity(intent)
            //标记接下来需要优化 启动Activity的相关消息
            ColdLaunchBoost.getInstance().curWatchingState = WatchingState.STATE_WATCHING_START_MAIN_ACTIVITY

        },1000)
    }
//...
}

Based on Looper message scheduling monitoring, each time the message scheduling ends, check the messages in the message queue to determine whether there is a target message

Among them, the message judgment logic of pauseActivity is as follows, and the message judgment of launchActivity is the same.

The judgment of launchActivity message is the same, but the value of judging targetState is different.

Modify message order, optimize page jump

Modifying the order of normal messages is relatively simple. After traversing the message queue to find the target message, you can modify the next value of the previous message to point to the next message, so that the message is removed from the message queue, and then copy the target message and resend it to the head of the queue.

public boolean upgradeMessagePriority(Handler handler, MessageQueue messageQueue,
                                      TargetMessageChecker targetMessageChecker) {
    synchronized (messageQueue) {
        try {
            Message message = (Message) filed_mMessages.get(messageQueue);
            Message preMessage = null;
            while (message != null) {
                if (targetMessageChecker.isTargetMessage(message)) {
                    // 拷贝消息
                    Message copy = Message.obtain(message);
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                        if (message.isAsynchronous()) {
                            copy.setAsynchronous(true);
                        }
                    }
                    if (preMessage != null) { //如果已经在队列首部了,则不需要优化
                        //当前消息的下一个消息
                        Message next = nextMessage(message);
                        setMessageNext(preMessage, next);
                        handler.sendMessageAtFrontOfQueue(copy);
                        return true;
                    }
                    return false;
                }
                preMessage = message;
                message = nextMessage(message);
            }
        } catch (Exception e) {
            //todo report
            e.printStackTrace();
        }
    }
    return false;
}

Here, the original message needs to be copied because: when the message is first enqueued, it will be marked as used, and an isInUse message cannot be re-enqueue into the message queue.

After increasing the priority of mH-related messages, the latest running log results are as follows:

The video effect at this time is as follows, it seems that there is no change from the screen (but the life cycle function is advanced):

Based on the corresponding logs, it can be seen that MainActivity has been executed to the onResume state, but because the Choreographer message is blocked, the first frame of MainActivity has not been rendered. From the interface point of view, the Splash page is still displayed.

First Frame Optimization

Next, continue to analyze how to solve the above problems and optimize the first frame display. First of all, you need to know the logic of triggering the drawing of the first frame. In the launch message processing stage of the Activity, the addView function will be called to add a View to the window, and finally the requestLayout and scheduleTraversal functions will be triggered. In the scheduleTraversal function, a message barrier will be set first and sent to Choreographer Register the traversal Callback, and finally perform the real drawing process in the traversalRunnable function when the next vsync signal occurs.

When the message corresponding to the resume activity is just executed, the message queue at this time is as follows. It can be found that although the message barrier is set, the message barrier is not sent to the head of the queue, because the previous slow message sequence is before the message barrier, so The messages corresponding to vsync are still not prioritized.

Therefore, we can find the barrier message and move it to the head of the queue by traversing the message queue, so as to ensure that the subsequent corresponding asynchronous messages are executed first.

The specific implementation code is as follows: First, we set a new monitoring state in the onResume stage of MainActivity, and mark down the messages that need to optimize frame drawing

Afterwards, at the end of each message dispatch, try to optimize the barrier message

Find the first barrier message by judging whether the target of the message is null, and then directly call removeSyncBarrier to remove the barrier message (of course, it can also be realized by manually operating the next point of the previous message), and finally copy the message barrier and replace it with Sent to the head of the team.

The implementation code is as follows:

/**
 * 移动消息屏障至队首
 *
 * @param messageQueue
 * @param handler
 * @return
 */
public boolean upgradeBarrierMessagePriority(MessageQueue messageQueue, Handler handler) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
        return false;
    }
    synchronized (messageQueue) {
        try {
            //反射获取 head Message
            Message message = (Message) filed_mMessages.get(messageQueue);
            if (message != null && message.getTarget() == null) {
                return false;
            }
            while (message != null) {
                if (message.getTarget() == null) { // target 为null 说明该消息为 屏障消息
                    Message cloneBarrier = Message.obtain(message);
                    removeSyncBarrier(messageQueue, message.arg1); //message.arg1 是屏障消息的 token, 后续的async消息会根据这个值进行屏障消息的移除
                    handler.sendMessageAtFrontOfQueue(cloneBarrier);
                    cloneBarrier.setTarget(null);//屏障消息的target为null,因此这里还原下
                    return true;
                }
                message = nextMessage(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return false;
}

removeSyncBarrier directly reflects and calls related functions

private boolean removeSyncBarrier(MessageQueue messageQueue, int token) {
    try {
        Method removeSyncBarrier = class_MessageQueue.getDeclaredMethod("removeSyncBarrier", int.class);
        removeSyncBarrier.setAccessible(true);
        removeSyncBarrier.invoke(messageQueue, token);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }

}

The following is the optimized log:

It can be found that the frame drawing message is successfully optimized to execute before other messages. And this solution can be used for the optimization of the first frame of any page. The following is the optimized video effect:

It can be found from the video that now the MainActivity screen will be displayed immediately after the execution of the onResume function ends. Here I set a button. When I click the button, I find that there is no response. This is because after the first frame message is optimized, other messages start to be processed normally. When the slow message is executed, the message corresponding to the click event has to be No response.

In the end, we completed the time-consuming optimization from the start of the page to the display of the first frame of the new page by modifying the sequence of messages twice. However, this does not solve the problem of slow messages on the main thread. The processing is delayed. If the message is time-consuming, it will still affect the user experience. Therefore, although message scheduling optimization can solve local problems, but to completely eliminate the impact of time-consuming messages on application experience, time-consuming monitoring of messages is essential. By recording the Handler corresponding to slow messages, message processing time, and stack sampling Collect problem site information in a unique way, and then optimize the corresponding message function time-consuming, so as to fundamentally solve specific problems.

Summarize

  1. By optimizing the order of the corresponding messages in key processes, such as starting the page and drawing the first frame of the page, the speed of the corresponding process can be improved, and the key process can be avoided from being blocked by other messages
  2. The modification of the message order can only optimize the partial problem. On the whole, the time-consuming problem has not been solved, but the problem has been delayed.
  3. Time-consuming monitoring and governance of messages is the way to solve fundamental problems

If you want to be fully proficient in the mysteries of performance optimization, it is not enough to understand startup optimization, because performance optimization includes the following :

1. Startup optimization : Improve application experience by reducing application startup time. For example, improve startup speed by using startup pages and deferred initialization.
2. Layout optimization : Optimizing the layout can improve UI rendering speed and fluency. For example, use ConstraintLayout to reduce layout nesting, cache layout rendering, etc.
3. Memory optimization : memory is one of the most precious resources in Android, optimizing memory can improve the performance and stability of the application. For example, use Google's official tools to analyze memory usage, avoid memory leaks, etc.
4. Network optimization : In mobile applications, network transmission speed is often the bottleneck, optimizing network performance can improve application response speed and user experience. For example, use methods such as caching technology and multi-threaded downloading to improve network transmission speed.
5. GPU optimization : Use hardware acceleration to improve UI rendering performance and fluency, for example, use graphics APIs such as OpenGL ES for GPU optimization.
6. Power optimization (power saving) : Optimizing application power consumption is also a direction of performance optimization, which can reduce power consumption by reducing unnecessary background tasks and resource occupation.
7. Code logic optimization : Improve application performance by optimizing code logic, and reduce memory usage and processing time. For example, use a thread pool to handle asynchronous tasks, avoid excessively nested loops, etc.
8. Volume package optimization
9. ……

In short, Android performance optimization is a comprehensive work that needs to be started from multiple aspects, and it also needs to be optimized according to specific application characteristics.

If you want to refine the technology of performance optimization, then this "Android Core Notes" must be indispensable, because it records the analysis of knowledge points contained in performance optimization, so it is a very good learning document:https://qr18.cn/FVlo89

Android core notes:insert image description here

Guess you like

Origin blog.csdn.net/maniuT/article/details/129930085