Following the first analysis, look at the start() method of the TracePlugin.
TracePlugin init() and start()
The start method is the core, and we need to analyze it.
Before analyzing the start method, let’s review the init(Application app, PluginListener Listener) method of the tracePlugin.
1.1 Init (Application app, PluginListener listener)
@Override
public void init(Application app, PluginListener listener) {
super.init(app, listener);
MatrixLog.i(TAG, "trace plugin init, trace config: %s", traceConfig.toString());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
unSupportPlugin();
return;
}
looperAnrTracer = new LooperAnrTracer(traceConfig);
frameTracer = new FrameTracer(traceConfig);
evilMethodTracer = new EvilMethodTracer(traceConfig);
startupTracer = new StartupTracer(traceConfig);
}
Copy the code
Brief analysis: I’ll look at the logic of each tracer individually later.
- As the name implies, the concrete logic of the ANR that encapsulates Looper
- Frame rate and FPS related logic
- Encapsulate slow method logic
- Encapsulate startup time logic
From a class design point of view, tracePlugin manages tracers with four different functions.
Unsurprisingly, the next start method must be to initialize the tracer and manage the life cycle of the four tracers so that the specific logic is shred into the specific tracer, in accordance with the single principle.
1.2 the start () method
@Override public void start() { super.start(); if (! isSupported()) { MatrixLog.w(TAG, "[start] Plugin is unSupported!" ); return; } MatrixLog.w(TAG, "start!" ); Runnable runnable = new Runnable() { @Override public void run() { if (willUiThreadMonitorRunning(traceConfig)) { if (! UIThreadMonitor. GetMonitor (.) isInit ()) {try {/ / 1, the UI thread to monitor initialization, include: UIThreadMonitor. GetMonitor () init (traceConfig); } catch (java.lang.RuntimeException e) { MatrixLog.e(TAG, "[start] RuntimeException:%s", e); return; }}} / / 2, AppMethodBeat open slow method to monitor the if (traceConfig. IsAppMethodBeatEnable ()) {AppMethodBeat. GetInstance (), onStart (); } else { AppMethodBeat.getInstance().forceStop(); } / / 3, UIThreadMonitor getMonitor (), onStart (); //4, Looper anR monitor?? if (traceConfig.isAnrTraceEnable()) { looperAnrTracer.onStartTrace(); } //5, idleHandler monitor? if (traceConfig.isIdleHandlerEnable()) { idleHandlerLagTracer = new IdleHandlerLagTracer(traceConfig); idleHandlerLagTracer.onStartTrace(); } //6, signal anR monitoring? if (traceConfig.isSignalAnrTraceEnable()) { if (! SignalAnrTracer.hasInstance) { signalAnrTracer = new SignalAnrTracer(traceConfig); signalAnrTracer.onStartTrace(); }} / / 7, thread priority monitoring the if (traceConfig. IsMainThreadPriorityTraceEnable ()) {threadPriorityTracer = new threadPriorityTracer (); threadPriorityTracer.onStartTrace(); If (traceconfig.isfpSenable ()) {frameTracer.onStartTrace(); } / / 9, slow start method if (traceConfig. IsEvilMethodTraceEnable ()) {evilMethodTracer. OnStartTrace (); } / / 10, start monitoring start method if (traceConfig. IsStartupEnable ()) {startupTracer. OnStartTrace (); }}}; if (Thread.currentThread() == Looper.getMainLooper().getThread()) { runnable.run(); } else { MatrixLog.w(TAG, "start TracePlugin in Thread[%s] but not in mainThread!" , Thread.currentThread().getId()); MatrixHandlerThread.getDefaultMainHandler().post(runnable); }}Copy the code
Brief analysis:
-
Init () of the UIThreadMonitor class. This class is very important and its main job is to get to Choreographer’s queues and callback listeners by reflection to implement its own logic. LoopMonitor listens to the logic before and after message processing in LoOPER, which will be analyzed later.
-
Change the state in AppMethodBeat to STATUS_STARTED so that isAlive() returns true, indicating that AppMethodBeat’s I and O methods are active.
-
This will be explained later by calling the start method of UIThreadMonitor
-
4, 5, 6, 7, 8, 9, and 10 will eventually call the onAlive() method corresponding to each tracer
At this point, the initial invocation process is analyzed and we know that the ultimate functionality of the tracePlugin is embodied in each of the different types of Tracers. Therefore, two questions are raised:
- How is each of the different tracers implemented? How does that work?
- Also, how does the tracePlugin manage these tracers? Relationships between classes?
But there’s another puzzle to be solved:UIThreadMonitor
和 AppMethodBeat
What is it for? Without a clear understanding of the problem, we cannot continue to analyze…
UIThreadMonitor and AppMethodBeat
2.1 UIThreadMonitor
This class does the following:
- Reflection to
Choreographer
ClasscallbackQueues
To facilitate the external to directly join events - through
LooperMonitor
In the starsmLooging
To replace
2.1.1 Init (TraceConfig Config) method
public void init(TraceConfig config) { if (Thread.currentThread() ! = Looper.getMainLooper().getThread()) { throw new AssertionError("must be init in main thread!" ); } this.config = config; / / 1, to obtain the Choreographer choreographers object Choreographer = Choreographer. GetInstance (); / / 2, get the Choreographer mLock lock Object callbackQueueLock = ReflectUtils. ReflectObject (Choreographer, "mLock", the new Object ()); / / 3, Reflection get Choreographer callbackQueues callbackQueues = ReflectUtils. ReflectObject (Choreographer, "mCallbackQueues", null); if (null ! Queues) {// queues) {// queues); To get the input event queue, animation queue, start drawing the queue addInputQueue = ReflectUtils. ReflectMethod (callbackQueues [CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class); addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class); addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class); } / / 5, reflection vsync receiver vsyncReceiver = ReflectUtils. ReflectObject (choreographer, "mDisplayEventReceiver", null); //5, reflection gets frameIntervalNanos, default is 16.666ms, But this is a nanosecond units frameIntervalNanos = ReflectUtils. ReflectObject (choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION); // 6, register listener. When stars distribute the messages in the beginning and the end of the trigger callback LooperMonitor. Register (new LooperMonitor. LooperDispatchListener () {@ Override public Boolean isValid() { return isAlive; } @Override public void dispatchStart() { super.dispatchStart(); UIThreadMonitor.this.dispatchBegin(); } @Override public void dispatchEnd() { super.dispatchEnd(); UIThreadMonitor.this.dispatchEnd(); }}); this.isInit = true; MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null, addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, vsyncReceiver == null, frameIntervalNanos); If (config.isdevenv ()) {//7, if the development environment, print log?? addObserver(new LooperObserver() { @Override public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) { MatrixLog.i(TAG, "focusedActivity[%s] frame cost:%sms isVsyncFrame=%s intendedFrameTimeNs=%s [%s|%s|%s]ns", focusedActivity, (endNs - startNs) / Constants.TIME_MILLIS_TO_NANO, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs); }}); }}Copy the code
Logical analysis:
To understand the logic above, let’s take a look at the Choreographer classes in action, which can be summarized as follows:
1. Choreographer instance is returned using the getInstance() method (this returns an instance belonging to the current thread as it is driven by Looper in the ThreadLocal of the current thread, normally used by the main thread).
2, the outside through postCallback method (such as calling the mChoreographer viewRootImpl. PostCallback) according to the type to register a callback. The purpose is to wait for the next onVsync signal to arrive and call the run method of the callback.
3. Callback has four types: Input, Animation, Inserts, and Travelsal. Corresponding to screen input events, animation execution, INSERT, measurement + layout + drawing three processes. Multiple callbacks of the same type are stored as queues in the mCallbackQueues array, which has a default length of 5. That is, each item in the array is a queue, and each queue holds the registered callback.
4, the general process: FrameDisplayEventReceiver will register a onVsync callback to the system. FrameDisplayEventReceiver. OnVsync – Choreographer. DoFrame () – doCallback (execute sequentially so registered callback). Callbacks without types are executed in the order input>animation>inserts> Travelsal
For a detailed analysis of Choreographer, see Gityuan’s article: Choreographer Principles
All right, moving on:
- Reflection returns Choreographer’s mLock object, which can be used as a lock object when the purpose adds its own callback to keep it consistent with the system lock object.
- Reflection yields the mCallbackQueues array object used to retrieve the specific type of CallbackRecord’s callback queue (the underlying queue is implemented via a linked list)
- The reflection results in different types of addCallbackLocked methods, including input, animation, and Travelsal, to add custom callbacks to.
- The onVsync receiver is used to reflect the time synchronization of the onVsync callback with mtimethon.
- Reflection yields mFrameIntervalNanos, the time between frames, generally equal to 16.66ms. Used to calculate the frame loss rate
- By setting the mLogger object of Looper to determine the timing of callbacks before and after message processing, you can calculate the message processing time.
2.1.2 LooperMonitor class
Before we dive into it, let’s understand how it works:
LooperMonitor
The principle of monitoring is to useLooper
thenext()
The message will be printed before and after the message processing, we can calculate the time of the message as long as we can listen between the two messages!
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 ! Println (">>>>> Dispatching to "+ msg.target +" "+ msg.callback + ":" + msg.what); }... Message processing // After message processing, if (logging! = null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); }Copy the code
In step 6, you see that callbacks are registered with LooperMonitor to listen for message processing callbacks in Looper. We need to figure out how it works.
public static LooperMonitor of(@NonNull Looper looper) {
LooperMonitor looperMonitor = sLooperMonitorMap.get(looper);
if (looperMonitor == null) {
looperMonitor = new LooperMonitor(looper);
sLooperMonitorMap.put(looper, looperMonitor);
}
return looperMonitor;
}
Copy the code
fromof
As you can see from static methods, LooperMonitor can listen not only for the main thread but also for the message holdup time of other threads. Just pass in loopers from different threads.
Moving on to the constructor:
private LooperMonitor(Looper looper) { Objects.requireNonNull(looper); this.looper = looper; // Replace the mLogging object resetPrinter() in looper; // Add an external handler to the IdleHandler to do logic addIdleHandler(looper) when idle; }Copy the code
Continue with resetPrinter() method:
private synchronized void resetPrinter() { Printer originPrinter = null; try { if (! IsReflectLoggingError) {// As long as reflectLoggingError is not reported, it will go here. OriginPrinter = reflectutils. get(looper.getClass(), "mLogging", looper); // Compare with the previously reflected printer, return if the same. This is generally the same unless a different classloader has loaded it. if (originPrinter == printer && null ! = printer) { return; } // Fix issues that printer loaded by different classloader if (originPrinter ! = null && printer ! = null) { if (originPrinter.getClass().getName().equals(printer.getClass().getName())) { MatrixLog.w(TAG, "LooperPrinter might be loaded by different classloader" + ", my = " + printer.getClass().getClassLoader() + ", other = " + originPrinter.getClass().getClassLoader()); return; } } } } catch (Exception e) { isReflectLoggingError = true; Log.e(TAG, "[resetPrinter] %s", e); } if (null ! = printer) { MatrixLog.w(TAG, "maybe thread:%s printer[%s] was replace other[%s]!" , looper.getThread().getName(), printer, originPrinter); } // Replace looper logging with our custom printer looper.setMessageLogging(printer = new LooperPrinter(originPrinter)); if (null ! = originPrinter) { MatrixLog.i(TAG, "reset printer, originPrinter[%s] in %s", originPrinter, looper.getThread().getName()); }}Copy the code
Brief analysis:
The logic is laid out in the comments. The logic of addIdleHandler(Looper) is to call the resetPrinter () method every 60 seconds after the ideleHandler callback after the message is processed to make sure it is our own Printer object.
Finally, it is through the println method callback in LooperPrinter to determine the start and end of message processing. Then it is distributed to the corresponding listener to realize logical processing.
class LooperPrinter implements Printer { public Printer origin; boolean isHasChecked = false; boolean isValid = false; LooperPrinter(Printer printer) { this.origin = printer; } @Override public void println(String x) { if (null ! = origin) { origin.println(x); if (origin == this) { throw new RuntimeException(TAG + " origin == this"); } } if (! isHasChecked) { isValid = x.charAt(0) == '>' || x.charAt(0) == '<'; isHasChecked = true; if (! isValid) { MatrixLog.e(TAG, "[println] Printer is inValid! x:%s", x); }} if isValid () {/ / x.c harAt (0) = = '>', said the message start / / x.c harAt (0) = = '<' end said news dispatch (x.c harAt (0) = = '>', x); }}}Copy the code
2.2 AppMethodBeat
This class is used to insert I (int methodId) and O (int methodId) methods at the beginning and end of all Java methods in App to count the execution time of the method. The aggregate output will eventually call the chain of relationships.
Trace-plugin will traverse all methods in class bytecode files through ASM bytecode staking technology during the transform phase of compile time, and insert I/O methods before and after each method execution to count the time spent. You can configure the BlackMethodList.txt whitelist to exclude piling for certain methods. In addition, an increment is used to assign a methodId to each method and output it to the mapping file for subsequent stack parsing
XxxTracer logic
Now that we understand what UIThreadMonitor and AppMethodBeat do, we go back and ask two questions:
- How is each of the different tracers implemented? How does that work?
- Also, how does the tracePlugin manage these tracers? Relationships between classes?
OK, let’s start with the LooperAnrTrace class:
3.1 LooperAnrTracer class
OnAlive registers the callback to UIThreadMonitor, which registers the LooperMonitor callback to dispatchBegin and dispatchEnd methods.
@Override public void onAlive() { super.onAlive(); If (isAnrTraceEnable) {/ / listening news UIThreadMonitor. The beginning and end of the event in which getMonitor (). The addObserver (this); this.anrHandler = new Handler(MatrixHandlerThread.getDefaultHandler().getLooper()); this.lagHandler = new Handler(MatrixHandlerThread.getDefaultHandler().getLooper()); }}Copy the code
2. DispatchBegin sends two delayed tasks. AnrTask is 5s, lagTask is 2S. Of course, it subtracts the error cos (t) caused by the code above.
@Override public void dispatchBegin(long beginNs, long cpuBeginMs, long token) { super.dispatchBegin(beginNs, cpuBeginMs, token); // Mark the start flag in anrTask, which will be used to intercept the sBuffer's memory when anR occurs. // anrTask.beginRecord = AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin"); anrTask.token = token; if (traceConfig.isDevEnv()) { MatrixLog.v(TAG, "* [dispatchBegin] token:%s index:%s", token, anrTask.beginRecord.index); } // Have a small question? How does this detect when looper is blocked by the main thread? // Answer: Blocking is a normal phenomenon. When blocked by MessageQueue's next method, dispatchBegin is not called back to trigger the fetch stack logic. long cost = (System.nanoTime() - token) / Constants.TIME_MILLIS_TO_NANO; anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR - cost); lagHandler.postDelayed(lagTask, Constants.DEFAULT_NORMAL_LAG - cost); }Copy the code
3, this is relatively simple, which is to remove the previous two delayed tasks after the dispatchEnd message processing ends. When the code executes here, the message does not lag or anR.
@Override public void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isBelongFrame) { super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isBelongFrame); if (traceConfig.isDevEnv()) { long cost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO; MatrixLog.v(TAG, "[dispatchEnd] token:%s cost:%sms cpu:%sms usage:%s", token, cost, cpuEndMs - cpuBeginMs, Utils.calculateCpuUsage(cpuEndMs - cpuBeginMs, cost)); } if (null ! = anrTask) { anrTask.getBeginRecord().release(); anrHandler.removeCallbacks(anrTask); } if (null ! = lagTask) { lagHandler.removeCallbacks(lagTask); }}Copy the code
4. If the two messages are executed, anR or LAG has occurred. Get the stack, get the process, CPU, memory information, combined with plugin.onDetectIssue(issue) callback to the upper layer.
lagTask:
@ Override public void the run () {/ / 1, to get the top-level activity or fragments name String scene. = AppMethodBeat getVisibleScene (); boolean isForeground = isForeground(); try { TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class); if (null == plugin) { return; } //2, get the main stack StackTraceElement[] stackTrace = looper.getMainLooper ().getThread().getStackTrace(); JSONObject jsonObject = new JSONObject(); //3, device information: device rating, CPU usage of the current process, total memory of the process, and remaining free memory of the process. jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); // If the reporting type is LAG, it indicates a delay. jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.LAG); jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene); //4, stack parse jsonObject.put(shaReplugininfo.issue_thread_stack, utils.getstack (stackTrace)); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_FOREGROUND, isForeground); Issue issue = new Issue(); //5, tag is: evil_method: slow method issue.settag (shareplugininfo.tag_plugin_evil_method); issue.setContent(jsonObject); //6, call plugin.onDetectIssue(issue); MatrixLog.e(TAG, "happens lag : %s ", jsonObject.toString()); } catch (JSONException e) { MatrixLog.e(TAG, "[JSONException error: %s", e); }Copy the code
Brief analysis: here the code execution indicates that the message processing time has reached 2s, collecting the field information and the call stack of the main thread for callback.
AnrTask:
@Override public void run() { long curTime = SystemClock.uptimeMillis(); boolean isForeground = isForeground(); Int [] processStat = utils.getProcesspriority (process.mypId ()); // According to the beginRecord flag, from sBuffer get dispatchBegin up to this time run methods, get an array. long[] data = AppMethodBeat.getInstance().copyData(beginRecord); beginRecord.release(); String scene = AppMethodBeat.getVisibleScene(); // Memory: // Runtime memory occupied // Native memory, run debug to obtain // VM memory, vmSize long[] memoryInfo = dumpMemory(); // Memory: // Runtime memory occupied // Native memory, run debug to obtain // VM memory, vmSize long[] memoryInfo = dumpMemory(); // Thread state Thread.State status = Looper.getMainLooper().getThread().getState(); StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace(); The main thread stack String dumpStack = Utils. GetStack (stackTrace, "|" * t \ \ t ", 12); / / frame calculation choreographer input, animation, traversal time consuming UIThreadMonitor monitor = UIThreadMonitor. GetMonitor (); long inputCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_INPUT, token); long animationCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_ANIMATION, token); long traversalCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_TRAVERSAL, token); Stack = new LinkedList(); // Stack = new LinkedList(); If (data length > 0) {TraceDataUtils. According to the data array, construct the call stack chain structuredDataToStack (data, stack, true, curTime); TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() { @Override public boolean isFilter(long during, int filterCount) { return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS; } @Override public int getFilterMaxCount() { return Constants.FILTER_STACK_MAX_COUNT; } @Override public void fallback(List<MethodItem> stack, int size) { MatrixLog.w(TAG, "[fallback] size:%s targetSize:%s stack:%s", size, Constants.TARGET_EVIL_METHOD_STACK, stack); Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK)); while (iterator.hasNext()) { iterator.next(); iterator.remove(); }}}); } StringBuilder reportBuilder = new StringBuilder(); StringBuilder logcatBuilder = new StringBuilder(); / / the call stack take long stackCost = math.h Max (the DEFAULT_ANR, TraceDataUtils. StackToString (stack, reportBuilder. logcatBuilder)); // stackKey String stackKey = TraceDataUtils.getTreeKey(stack, stackCost); MatrixLog.w(TAG, "%s \npostTime:%s curTime:%s", printAnr(scene, processStat, memoryInfo, status, logcatBuilder, isForeground, stack.size(), stackKey, dumpStack, inputCost, animationCost, traversalCost, stackCost), token / Constants.TIME_MILLIS_TO_NANO, curTime); // for logcat if (stackCost >= Constants.DEFAULT_ANR_INVALID) { MatrixLog.w(TAG, "The checked anr task was not executed on time. " + "The possible reason is that the current process has a low priority. just pass this report"); return; } // report try { TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class); if (null == plugin) { return; } JSONObject jsonObject = new JSONObject(); jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication()); // Todo indicates that anR Type jsonObject.put(ShaRepluginInfo.issue_stack_type, Constants.type.anr); // Todo indicates that anR Type jsonObject.put(ShaRepluginInfo.issue_stack_type, Constants. jsonObject.put(SharePluginInfo.ISSUE_COST, stackCost); jsonObject.put(SharePluginInfo.ISSUE_STACK_KEY, stackKey); jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene); jsonObject.put(SharePluginInfo.ISSUE_TRACE_STACK, reportBuilder.toString()); jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, Utils.getStack(stackTrace)); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_PRIORITY, processStat[0]); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_NICE, processStat[1]); jsonObject.put(SharePluginInfo.ISSUE_PROCESS_FOREGROUND, isForeground); // memory info JSONObject memJsonObject = new JSONObject(); memJsonObject.put(SharePluginInfo.ISSUE_MEMORY_DALVIK, memoryInfo[0]); memJsonObject.put(SharePluginInfo.ISSUE_MEMORY_NATIVE, memoryInfo[1]); memJsonObject.put(SharePluginInfo.ISSUE_MEMORY_VM_SIZE, memoryInfo[2]); jsonObject.put(SharePluginInfo.ISSUE_MEMORY, memJsonObject); Issue issue = new Issue(); issue.setKey(token + ""); issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD); issue.setContent(jsonObject); plugin.onDetectIssue(issue); } catch (JSONException e) { MatrixLog.e(TAG, "[JSONException error: %s", e); } }Copy the code
Obtaining device information:
Public static JSONObject getDeviceInfo(JSONObject oldObj, Application Context) {try { Oldobj.put (DEVICE_MACHINE, getLevel(context)); Oldobj.put (DEVICE_CPU, getAppCpuRate())); ActivityManager getMemoryInfo oldobj.put (DEVICE_MEMORY, getTotalMemory(context)); oldObj.put(DEVICE_MEMORY_FREE, getMemFree(context)); } catch (JSONException e) { MatrixLog.e(TAG, "[JSONException for stack, error: %s", e); } return oldObj; }Copy the code
Iv. Summary:
Advantages: compared with watchDog, non-invasive, good compatibility.
A child thread adds a message to the main thread, and polls the message at a certain interval to see if it is processed. If it is not processed, it indicates that there is a delay. The disadvantage is that the polling interval is not well controlled. If the interval is too small, the main thread will be affected and there will be performance problems, while if the interval is too large, it will be easily missed.
Disadvantages:
-
Can’t monitor touch events?
-
QueueIdle () cannot monitor idleHandler?
Yes, because the idelHandler handler does not call back to logger’s print method. Each time a message is triggered, the idleHandler will only run once, and if there is no message, it will block until a new message arrives.
- Unable to monitor SyncBarrier?
Each time we refresh the UI with invalidate, we end up calling the scheduleTraversals method in the ViewRootImpl, which will post a SyncBarrier to the main thread’s Looper. This is done so that when we refresh the UI, Synchronous messages from the main thread are skipped, while asynchronous messages from the rendering UI are processed first. Note that this method is thread unsafe. If it is called on a non-main thread, it is possible to post multiple SyncBarriers at the same time, but remove only the last one, so that a single SyncBarrier is never removed. The main thread Looper is unable to handle synchronous messages (Message by default is synchronous messages).
Finally, the other Trace subclasses are ready to be examined in a later article
Reference:
Matrix Android TraceCanary
Wechat Android client caton monitoring scheme
Choreographer principle
Do you know android MessageQueue.IdleHandler?
Matrix-tracecanary source code analysis
Matrix-TraceCanary design and principle analysis manual
Android System Ace — Series