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
-
When start() is called, the mLogging member variable of Looper is assigned by calling the looper.setMessagelogging () method of the main thread.
-
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 followslogging.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
-
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 then
mCurrentThread.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
- In this code, you create a StringBuilder, and then
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
- Overridden onMethodEnter, when the method enters
-
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…