Android print execution time of all methods of main thread

Keywords: Java Android Gradle github

Preface:

We know that Android carton is mainly caused by time-consuming operations in the main thread, so how can we easily and quickly get the execution time of all time-consuming methods in the main thread? Today we will introduce two plans

Scheme 1: use the special keyword of logging.print of loop() method in loop.java to print time-consuming:

When the message is distributed, the looper.loop() method of the main thread will traverse all messages for distribution and perform time-consuming tasks. Let's look at the loop() method of the source code:

for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;

... ...
... ...

if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

It can be found that after the distribution and execution of each message, there are special characters > > > > and < < in the logging.print() method. Then we can use this special character as the start and end flag of the message execution, and then print the execution time of the message in the main thread. The specific code is as follows:

/**
     * Scheme 1: only time-consuming messages can be printed, and it is impossible to know which message is time-consuming
     */
    private void methodOne() {
        outputMainLooper();
    }

    private void outputMainLooper() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            @Override
            public void println(String x) {
                if (x.startsWith(">>>>>")) {
                    startTime = System.currentTimeMillis();
                } else if (x.startsWith("<<<<<")) {
                    long end = System.currentTimeMillis();
                    //The time threshold can be defined by yourself. Here is 10
                    if ((end - startTime) > 10) {
                        Log.i("buder mainLoop ------ : ", (end - startTime)+ " ");
                    }
                }
            }
        });
    }

This scheme can simply and quickly print out the specific execution time of the message in the main thread, but our purpose is to find out which method is time-consuming. We need to print out the stack information of the time-consuming method to help us quickly locate the stuck point. Therefore, scheme one only prints the time-consuming message and cannot locate the specific function.

Scheme 2: use sendMessageAtTime() and dispatchMessage() methods of Handler.java to find out the time-consuming function and print the time-consuming function:

We know that the main thread sends messages, and finally calls the sendMessageAtTime method to enter the message queue, and then the message is distributed and executed through dispatchMessage. Then we can use these two methods to monitor who sent the message and the execution time of the message. In order to do this, we use the epic framework to hook the two functions, as follows:

Step 1: add library dependency in gradle:

implementation 'me.weishu:epic:0.6.0'

Step 2: hook sendMessageAtTime and dispatchMessage

/**
     * Scheme 2: print time-consuming messages and time-consuming messages
     */
    private void methodTwo() {
        final long[] startTime = {0};
        //Who is the specific msg message
        DexposedBridge.findAndHookMethod(Handler.class, "sendMessageAtTime", Message.class, long.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                sMsgDetail.put((Message) param.args[0], Log.getStackTraceString(new Throwable()).replace("java.lang.Throwable", ""));
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
            }
        });

        //hook dispatchMessage, printing time-consuming
        DexposedBridge.findAndHookMethod(Handler.class, "dispatchMessage", Message.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                startTime[0] = System.currentTimeMillis();
                super.beforeHookedMethod(param);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                long costTime = System.currentTimeMillis() - startTime[0];
                String stackMessage = null;
                //The time threshold can be defined by yourself. Here is 10
                if (costTime > 10) {
                    stackMessage = sMsgDetail.get(param.args[0]);
                    Log.i("buder", costTime + "***********");
                    Log.e("buder", stackMessage);
                }
            }
        });
    }

After executing the method, you can see clearly where the time-consuming operation is executed in the program:

  

Here we print out the time-consuming of the two schemes, and see that lines 24 and 36 of MainActivity contain time-consuming operations.

Full code:

  • MainActivity.java, click event simulation time-consuming task:
public class MainActivity extends AppCompatActivity {

    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton  = findViewById(R.id.btn);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Time consuming operation task 1
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        for(int i = 0; i < 100000; i++) {
                            System.out.println("test 1");
                        }
                        Toast toast = Toast.makeText(getApplicationContext(),"Click Finish", Toast.LENGTH_LONG);
                        toast.show();
                    }
                }, 1000);

                //Time consuming operation task 2
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        for(int i = 0; i < 200000; i++) {
                            System.out.println("test 1");
                        }
                    }
                });

            }
        });
    }
}
  • DemoApplication.java includes the above two statistical schemes:
  • Remember to add android:name=".DemoApplication" to Manifest
public class DemoApplication extends Application {

    private long startTime = 0;
    private static ConcurrentHashMap<Message, String> sMsgDetail = new ConcurrentHashMap<>();

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        //Obtain time-consuming scheme I
        methodOne();
        //Obtain time consuming scheme II
        methodTwo();
    }

    /**
     * Scheme 2: print time-consuming messages and time-consuming messages
     */
    private void methodTwo() {
        final long[] startTime = {0};
        //Who is the specific msg message
        DexposedBridge.findAndHookMethod(Handler.class, "sendMessageAtTime", Message.class, long.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                sMsgDetail.put((Message) param.args[0], Log.getStackTraceString(new Throwable()).replace("java.lang.Throwable", ""));
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
            }
        });

        //hook dispatchMessage, printing time-consuming
        DexposedBridge.findAndHookMethod(Handler.class, "dispatchMessage", Message.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                startTime[0] = System.currentTimeMillis();
                super.beforeHookedMethod(param);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                long costTime = System.currentTimeMillis() - startTime[0];
                String stackMessage = null;
                //The time threshold can be defined by yourself. Here is 100
                if (costTime > 100) {
                    stackMessage = sMsgDetail.get(param.args[0]);
                    Log.i("buder", costTime + "***********");
                    Log.e("buder", stackMessage);
                }
            }
        });
    }

    /**
     * Scheme 1: only time-consuming messages can be printed, and it is impossible to know which message is time-consuming
     */
    private void methodOne() {
        outputMainLooper();
    }

    private void outputMainLooper() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            @Override
            public void println(String x) {
                if (x.startsWith(">>>>>")) {
                    startTime = System.currentTimeMillis();
                } else if (x.startsWith("<<<<<")) {
                    long end = System.currentTimeMillis();
                    //The time threshold can be defined by yourself. Here is 100
                    if ((end - startTime) > 100) {
                        Log.i("buder mainLoop ------ : ", (end - startTime)+ " ");
                    }
                }
            }
        });
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}

Full address: https://github.com/buder-cp/base_component_learn/tree/master/timeConsumingPrinter

Reference blog: https://blog.csdn.net/qq_20798591/article/details/104354723

epic related: http://weishu.me/

android-hacker/epic : https://gitter.im/android-hacker/epic?at=5ac9c6951130fe3d36b39c6a

206 original articles published, 86 praised, 220000 visitors+
Private letter follow

Posted by askbapi on Sat, 22 Feb 2020 01:57:41 -0800