BlockCanary open source decoding

use

Implementation 'com. Making. Markzhai: blockcanary - android: 1.5.0'Copy the code

In the Application

BlockCanary.install(this,new BlockCanaryContext()).start();
Copy the code

Let’s add a time-consuming action to the Activity

public class BlockCanaryActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_block_canary); } public void evilMethod(View view) { SystemClock.sleep(1000); }}Copy the code

Click the button to perform the time-consuming operation, and observe the log of BlockCanary. You can see that BlockCanary prints the execution time of the time-consuming operation and the location of the corresponding code for us

How is BlockCanary implemented

BlockCanary executes the procedure

How to Print Logs

  1. When start() is called, the mLogging member variable of Looper is assigned by calling the looper.setMessagelogging () method of the main thread.

  2. In a Looper loop, println is called before and after dispatchMessage(MSG), respectively.

    • Therefore, by customizing Printer objects, we can obtain the time of dispatchMessage, so as to determine whether there is application lag.
    • We know that everything that happens in the main thread of Android is in Looper.loop, so we can monitor all kinds of things that happen in the main thread. If the dispacthMessage takes too long, it means that there may be a delay. Print via logging.println

      Pseudo code can be understood as follows
      logging.println(">>>>> Dispatching to " + msg.target + " " + msg.target.dispatchMessage(msg); Logging. Println ("<<«< Finished to "+ msg.target + "" + msg.callback);Copy the code

      Since we passed in the monitor, the above code can actually be considered as such

      monitor.start()
      msg.target.dispatchMessage(msg);
      monitor.end()
      Copy the code
  3. BlockCanary also performs a timed task in the child thread to get the main thread stack information, which is removed when dispatchMessage ends.

Now that we know how BlockCanary prints, how does BlockCanary locate the code

How do I locate the error stack

  • LooperMonitor inherits self-printer and rewrites println methods
    class LooperMonitor implements Printer { @Override public void println(String x) { if (mStopWhenDebugging && Debug.isDebuggerConnected()) { return; } if (! mPrintingStarted) { mStartTimestamp = System.currentTimeMillis(); mStartThreadTimestamp = SystemClock.currentThreadTimeMillis(); mPrintingStarted = true; startDump(); } else { final long endTime = System.currentTimeMillis(); mPrintingStarted = false; if (isBlock(endTime)) { notifyBlockEvent(endTime); } stopDump(); }}}Copy the code
  • The startDump() and stopDump() methods are called
    private void startDump() { if (null ! = BlockCanaryInternals.getInstance().stackSampler) { BlockCanaryInternals.getInstance().stackSampler.start(); } if (null ! = BlockCanaryInternals.getInstance().cpuSampler) { BlockCanaryInternals.getInstance().cpuSampler.start(); } } private void stopDump() { if (null ! = BlockCanaryInternals.getInstance().stackSampler) { BlockCanaryInternals.getInstance().stackSampler.stop(); } if (null ! = BlockCanaryInternals.getInstance().cpuSampler) { BlockCanaryInternals.getInstance().cpuSampler.stop(); }}Copy the code
  • What we need to focus on are the start() and stop() methods of stackSampler
    public void start() { if (mShouldSample.get()) { return; } mShouldSample.set(true); HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable); HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable, BlockCanaryInternals.getInstance().getSampleDelay()); } public void stop() { if (! mShouldSample.get()) { return; } mShouldSample.set(false); HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable); }Copy the code
    • As you can see, in the start method, a Runnable is actually executed by the Handler after a delay of 800ms. The Runnable triggers the doSample method
  • The concrete implementation of doSample is in the subclass StackSampler
    class StackSampler extends AbstractSampler { @Override protected void doSample() { StringBuilder stringBuilder = new StringBuilder(); for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) { stringBuilder .append(stackTraceElement.toString()) .append(BlockInfo.SEPARATOR); } synchronized (sStackMap) { if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) { sStackMap.remove(sStackMap.keySet().iterator().next()); } sStackMap.put(System.currentTimeMillis(), stringBuilder.toString()); }}}Copy the code
    • In this code, you create a StringBuilder, and thenmCurrentThread.getStackTrace()By traversing the current thread (It’s basically the main threadThe stack data is collected by getting the stack information and concatenating it, then the sStackMap stores the timestamp and the error stack information, and finally we can see the error stack information

Problems caused by 800ms delay

Suppose we when click the button to perform the following in turn three time consuming method, so the last error stack location to method b, only 21 ms b, however, it should not be which error stack, so you can see BlockCanary when positioning code will have certain deviation, in our development, the actual situation may be more complicated than that.

However, because BlockCanary prints enough stack information for us to analyze, we can still analyze the problem code

public void evilMethod(View view) {
        //SystemClock.sleep(1000);
        a();
        b();
        c();
    }

    public void a() {
        SystemClock.sleep(780);
    }

    public void b() {
        SystemClock.sleep(21);
    }

    public void c() {
        SystemClock.sleep(200);
    }
Copy the code

disadvantages

  • Depending on the method of timing acquisition stack, positioning is not accurate enough.
  • The println method concatenates string objects (the concatenation process constructs too many StringBuilder objects)

Gets the method runtime

Choreographer monitors Caton

The Android system has added the Choreographer class since 4.1(API16) to control the synchronization of Input, Animation, and Draw UI operations. Choreographer provides callbacks to detect UI draws. We know that android UI refreshes with the VSync signal, usually at 60HZ, which means that each frame is drawn between 16 and 17ms. If you go beyond this, you can say that you are stuck

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { long lastTimeNano = 0; @override public void doFrame(long frameTimeNanos) {log. e(" frameTimeNanos ", "frameTimeNanos: "+(frameTimeNanos - lastTimeNano)/(1024*1024)); // between 16-17ms lastTimeNano = frameTimeNanos; Choreographer.getInstance().postFrameCallback(this); }});Copy the code

Hugo monitoring method is time-consuming

Using JakeWharton’s Hugo, you can monitor method execution time in logcat with the @debuglog annotation

  • Annotations can be either class names or methods
  • All method execution time can be monitored directly on the class
@DebugLog
public class BlockCanaryActivity extends AppCompatActivity {
    ...
}
Copy the code

The effect is as follows:

Limitations of Hugo

  • Too intrusive to add annotations to a large number of classes or methods
  • If it’s deprecated, you’ll need to change a lot of code

Time consuming of TraceView monitoring method (deprecated)

If you enable method tracing with Debug, the sample.trace file will be generated under sdcard. Android can open it directly, and then you can view the method execution time through a visual diagram

public class BlockCanaryActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { Debug.startMethodTracing("sample"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_block_canary); Debug.stopMethodTracing(); }}Copy the code

Reasons for abandonment

TraceView is too expensive and the monitoring data is not accurate enough. Therefore, Google no longer recommends using TraceView

Other performance analysis tools

Systrace

  • Use python terminal commands to generate the official documentation of the Trace file
  • In higher versions, trace files can be generated through System Tracing, which can be analyzed online

Common operations on Trace analysis interface:

  • W: Zoom in (double effect with Shift)
  • S: Zoom out (Shift double effect)
  • A: left
  • D: moves to the right
  • M: Marks the currently selected timeline
  • 1: Select a region
  • 2: drag and drop
  • 3: Zoom in and out
  • 4: Cut the timeline

View function time

View draw frame

Green indicates completion within 16ms, yellow and red indicates over 16ms

View drawing Status

  • Green (Running) : Running
  • Blue (Runnbale) indicates that it can be run but not allocated to CPU
  • Grey (white)(Sleeping)
  • Orange (Uninterruptible Sleep) Indicates that an I/O operation is being performed

Aspect J

AspectJ is a framework for faceted programming that extends the Java language. AspectJ defines the AOP syntax, which has a specialized compiler for generating Class files that comply with the Java byte-encoding specification. AspectJ also supports native Java, with annotations provided by AspectJ. In Android development, it generally provides annotations and some simple syntax can achieve most of the functional requirements.

AspectJ is now hosted in the Eclipse project at the AspectJ class Library Reference

We know that AOP’s aspect oriented AspectJ can do log tracking, performance monitoring, and statistical burying, essentially by weaving in code, such as bytecode after using AspectJ to jump to news details

 @ClickCollect(
        type = "Home"
    )
    private void skipNewsDetail(String id) {
        JoinPoint var3 = Factory.makeJP(ajc$tjp_2, this, this, id);

        try {
            Intent intent = new Intent(this, NewsDetailActivity.class);
            intent.putExtra("id", id);
            this.startActivity(intent);
        } catch (Throwable var6) {
            ClickCollectAspect.aspectOf().clickCollect(var3);
            throw var6;
        }

        ClickCollectAspect.aspectOf().clickCollect(var3);
    }
Copy the code

ASM

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate classes or enhance the functionality of existing classes. ASM can either generate binary class files directly or dynamically change the behavior of classes before they are loaded into the Java virtual machine. Java classes are stored in rigorously formatted.class files that have enough metadata to parse all the elements of the class: class names, methods, attributes, and Java bytecodes (instructions). After reading information from class files, ASM can change class behavior, analyze class information, and even generate new classes based on user requirements.

Both Kotlin and Groovy use ASM to generate bytecode

ASM writes a kotlin plug-in

The core is to insert code before and after methods

  • First create the plug-in class TracePlugin and register TraceTransform (written later)

    class TracePlugin : Plugin<Project> {
        override fun apply(project: Project) {
            project.extensions
                .getByType(AppExtension::class.java)
                .registerTransform(TraceTransform())
        }
    }
    Copy the code
  • Write TraceTransform code

    • Important: In the Transform method, folders are recursively traversed.
    • Class files are then processed using ASM

  • Code insertion logic is handled in the ClassTraceVisitor for class access (visit method) and method access (visitMethod)

    • The visit method gets the class name
    • The visitMethod method method is handled with MethodTraceVisitor
    class ClassTraceVisitor(cv: ClassVisitor?) : ClassVisitor(Opcodes.ASM7, cv), Opcodes { lateinit var className: String override fun visit(version: Int, access: Int, name: String, signature: String? , superName: String? , interfaces: Array<out String>?) { super.visit(version, access, name, signature, superName, interfaces) this.className = name } override fun visitMethod(access: Int, name: String? , descriptor: String? , signature: String? , exceptions: Array<out String>?) : MethodVisitor? { return MethodTraceVisitor( Opcodes.ASM7, super.visitMethod(access, name, descriptor, signature, exceptions), access, name, descriptor ).also { it.className = this.className } } }Copy the code
  • Method visitor MethodTraceVisitor

    • Overridden onMethodEnter, when the method enters
      • First the method name is pushed
      • Then access the method directive
    • Overrides onMethodExit when the method exits
    class MethodTraceVisitor(api: Int, methodVisitor: MethodVisitor, access: Int, name: String? , descriptor: String?) : AdviceAdapter(api, methodVisitor, access, name, descriptor) { lateinit var className: String override fun onMethodEnter() { super.onMethodEnter() mv.visitLdcInsn("$className/${this.name}") mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/os/Trace", "beginSection", "(Ljava/lang/String;) V", false) } override fun onMethodExit(opcode: Int) { super.onMethodExit(opcode) mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/os/Trace", "endSection", "()V", false) } }Copy the code
  • Back to TraceTransform, the following code writes the enhanced bytecode to the file, and the code is woven in

    val classTraceVisitor = ClassTraceVisitor(classWriter)
    classReader.accept(classTraceVisitor, EXPAND_FRAMES)
    file.writeBytes(classWriter.toByteArray())
    Copy the code
use
Activity{ @Override protected void onCreate(Bundle savedInstanceState) { Trace.beginSection("OnCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Trace.endSection(); }}Copy the code

Wechat open source Matrix

Matrix is an APM (Application Performance Manage) developed by wechat and used daily. Currently, it is mainly running on Android platform. Matrix aims to establish a unified application performance access framework, quickly integrate various performance monitoring schemes, collect and analyze abnormal data of performance monitoring items, and output corresponding problem analysis, positioning and optimization suggestions, so as to help developers develop higher-quality applications.

Cloud.tencent.com/developer/a…

Meituan mobile terminal performance monitoring scheme Hertz

ArgusAPM 360 performance monitoring framework