background

When optimizing the startup speed of an Application, it is necessary to know the time-consuming tasks in the startup phase, analyze the key methods of the Application, such as attachBaseContext and onCreate, and count the time of other methods called by them.

The analysis is combined with the Systrace tool, because you need to know not only the wall time of the method, but also the CPU time, so you can know if it’s a CPU-intensive task and then adjust or schedule it for the task type.

The requirements are clear: insert tracecompat.beginSection () before the method call to be counted and tracecompat.endSection () after. The requirements are simple enough that we can quickly use aspect, Javassist, or ASM implementations.

However, this time it is method peg + Systrace, need to develop a plug-in for this; Next time it’s method piling + time statistics, you’ll have to develop another plugin. Why do plugins have to be tied to peg logic?

Why is there not a plug-in that only provides the ability of method piling and does not write the logic of dead piling, but allows users to customize the logic of piling freely?

Based on this pain point, we develop a plug-in that can customize the piling logic freely.

Choice of AOP scheme

The first is the choice of AOP technical solution, aspect, Javassist or ASM? After thinking about it for a second, I chose ASM for a simple reason: high performance and high performance.

Gradle and ASM native API, from scratch? NO, some wheels can’t be built. We want to ride in a wagon!

So I ended up developing in ByteX.

ByteX is similar to Jetpack StartUp.

Startup Provides a ContentProvider that centrally initializes all dependencies of libraries. ByteX provides a host plugin, transform, that centrally handles all the transforms.

ByteX encapsulates Transform and ASM apis, greatly saving the workload of plug-in development. We don’t need to deal with class/ JAR IO operations, just focus on the hook logic we want to do.

Therefore, in terms of performance and development cost, it is a good choice to develop meaningful plug-ins based on ByteX.

The trace – plugins plugin

At present, trace-plugin has been developed and released, see: github.com/yhaolpz/Byt…

Using postures is simple, with @traceclass and @tracemethod annotations:

TraceClass is a class annotation that can be configured:

// Specify the method peg implementation class
Class methodTrace(a) default TimeTrace.class;
// Whether to trace all methods in this class
boolean traceAllMethod(a) default false;
// Whether to trace the method called inside the method
boolean traceInnerMethod(a) default false;
Copy the code

@tracemethod is a method annotation, which can be configured:

// Whether to trace this method
boolean trace(a) default true;
// Whether to trace the method called inside the method
boolean traceInnerMethod(a) default false;
Copy the code

For example

There are three methods m1(), m2() and m3() in the Test class:

public class Test{
    public static void m1(a) {
        m2();
        OtherClass.m4();
    }
    public static void m2(a) {}public static void m3(a) {}}Copy the code

Time spent tracking M1 () :

@TraceClass
public class Test{
    @TraceMethod
    public static void m1(a) {...
Copy the code

Time spent tracking all methods in a class:

@TraceClass(traceAllMethod = true)
public class Test{...
Copy the code

It takes time to track all methods in a class, except m1() :

@TraceClass(traceAllMethod = true)
public class Test{
    @TraceMethod(trace = false)
    public static void m1(a) {...
Copy the code

Trace the time of the method called inside m1(), namely m2() and otherclass.m4 () :

@TraceClass
public class Test{
    @TraceMethod(trace = false,traceInnerMethod = true)
    public static void m1(a) { m2(); OtherClass.m4(); }...Copy the code

Custom tracking pile insertion processing:

// Implement your own peg handling from IMethodTrace, such as systrace:
public class CustomSysTrace implements IMethodTrace {
    @Override
    public void onMethodEnter(String className, String methodName, String methodDesc, String outerMethod) {
        TraceCompat.beginSection(className + "#" + methodName);
    }
    @Override
    public void onMethodEnd(String className, String methodName, String methodDesc, String outerMethod) { TraceCompat.endSection(); }}// Specify it in the class annotation
@TraceClass(methodTrace = CustomSysTrace.class)
public class Test{... }Copy the code

Implementation principle of self-defined pile insertion

In fact, it is very simple, the plug-in will call the TraceRecord class for all methods that need to be inserted:

public class TraceRecord {
    // The pile insertion method will be adjusted here before implementation
    public static void onMethodEnter(String traceImplClass, String className, String methodName, String methodDesc, String outerMethod) {
        getMethodTrace(traceImplClass).onMethodEnter(className,
                                methodName, methodDesc, outerMethod);
    }
    // After the piling method is implemented, it will be unified here
    public static void onMethodEnd(String traceImplClass, String className, String methodName, String methodDesc, String outerMethod) { getMethodTrace(traceImplClass).onMethodEnd(className, methodName, methodDesc, outerMethod); }}Copy the code

TraceImplClass is the custom piling logic implementation class we specified in the class annotation, such as CustomSysTrace, and instantiated in getMethodTrace() :

private static IMethodTrace getMethodTrace(String traceImplClass) {
    IMethodTrace methodTrace = sMethodTraceMap.get(traceImplClass);
    if (methodTrace == null) {
        try {
            methodTrace = (IMethodTrace) Class.forName(traceImplClass).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    sMethodTraceMap.put(traceImplClass, methodTrace);
    return methodTrace;
}
Copy the code

The last

With the simple, elegant, easy-to-use, and powerful trace-plugin, you can plug any method you want.

Github, you can view the plug-in usage posture and access mode.