Monitor the time-consuming operations of the main thread and solve ANRs from development

background:

In the debug environment, the execution time of the method body in the main thread is compared with the specified time, and the stack information is compared, and the time-consuming method body that exceeds the specified time is optimized in a targeted manner to reduce the occurrence of ANR .

This tool class mainly prints the time-consuming stack information and time-consuming time that exceeds the specified time to the main thread Looper, in which the verification time is defined by itself, and actively checks the time-consuming operations in the main thread to prevent problems before they occur.

principle:

This tool class is the simplest and most direct processing tool to optimize time-consuming operations. Everyone knows Android's judgment criteria for ANR:

The simplest sentence is: ANR - application does not respond, Activity is 5 seconds, BroadCastReceiver is 10 seconds, Service is 20 seconds

Then the solution of this tool class is to compare and monitor the stack information of the main thread for time, and print out the timeout.

Looper.loop parsing:

  1. The reason why the application does not exit is that it is running in the loop. If there is an operation that blocks the loop, ANR and crash will occur.
public static void loop() {
    final Looper me = myLooper();
    //....
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}
  1. Mainly look at the infinite loop loopOnce
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }

    // This must be in a local variable, in case a UI event sets the logger
    // *当有任务的时候打印Dispatching to *
    final Printer logging = me.mLogging;
    if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " "
                + msg.callback + ": " + msg.what);
    }
    //.... 中间部分未任务执行的代码
    
    //执行结束之后打印 Finished to 
    if (logging != null) {
        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }

    // Make sure that during the course of dispatching the
    // identity of the thread wasn't corrupted.
    final long newIdent = Binder.clearCallingIdentity();
    if (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
                + Long.toHexString(ident) + " to 0x"
                + Long.toHexString(newIdent) + " while dispatching to "
                + msg.target.getClass().getName() + " "
                + msg.callback + " what=" + msg.what);
    }

    msg.recycleUnchecked();

    return true;
}
  1. The time-consuming between the above comments is the time-consuming of the main thread when executing a certain task. We can monitor the time-consuming stack information of the main thread by comparing this time with the specified time.

How to use:

  1. Application:
 //主线程中方法体执行的时间与指定的时间做对比后的堆栈信息,针对性的优化超过指定时间的耗时方法体,
MainThreadDoctor.init(500)
  1. View log:

image.png

Log levels use error level for clarity

Tools:

 /**
*  @author  kong
*  @date  2022/7/6 15:55
*  @description  在debug环境中主线程中方法体执行的时间与指定的时间做对比后的堆栈信息,针对性的优化超过指定时间的耗时方法体,减少ANR的发生
**/
object MainThreadDoctor {

    private  var startTime = 0L
    private  var currentJob: Job? = null
 private  const  val START = ">>>>> Dispatching"
    private  const  val END = "<<<<< Finished"

    fun init(diagnoseStandardTime: Long) {
        if (BuildConfigs.DEBUG) {
            diagnoseFromMainThread(diagnoseStandardTime)
        }
    }

    /**
*  @param diagnoseStandardTime 执行诊断的标准时间
*/
 fun diagnoseFromMainThread(diagnoseStandardTime: Long) {
        Looper.getMainLooper().setMessageLogging {
 if (it.startsWith(START)) {
                startTime = System.currentTimeMillis()
                currentJob = GlobalScope.launch(Dispatchers.IO) {
delay(diagnoseStandardTime)
                    val stackTrace = Looper.getMainLooper().thread.stackTrace
 val builder = StringBuilder()
                    for (s in stackTrace) {
                        builder.append(s.toString())
                        builder.append("\n")
                    }
                    PPLog.e("looperMessageMain $builder")
                }
}

            if (it.startsWith(END)) {
                if (currentJob?.isCompleted == false) {
                    currentJob?.cancel()
                } else {
                    PPLog.e("looperMessageMain 总时间 = ${System.currentTimeMillis() - startTime} 毫秒")
                }
            }
        }
}
}

Guess you like

Origin juejin.im/post/7117194640826368036