preface

Recently, I encountered the need to collect user behaviors through burying points in a project. Since the project was running on a LAN and had some very detailed requirements, I chose burying points through AspectJ after comparing several technical solutions. This article presents my summary of learning and using AspectJ.

What is the AspectJ

As object-oriented programming is the same as the modular of common problems, tangent programming is the same problem of horizontal modular, such as in a package of all classes in a certain kind of method need to solve a similar problem, can be through the AOP programming approach to this modular packaging, unified solution. For a detailed explanation of AOP, see Wikipedia. AspectJ is a concrete implementation of section-oriented programming in Java.

AspectJ introduces a new concept to Java, join Point, which includes several new constructs: Pointcuts, Advice, Inter-Type declarations, and Aspects.

A join point is a point defined in the program flow. The pointcut picks up specific join points and values at those points. Advice is the code that is executed when the join point is reached.

AspectJ also has inter-type declarations of different types that allow programmers to modify the static structure of a program, that is, the relationships between its classes and the members of its classes.

A few terms in AspectJ explained

  • Cross-cutting concerns: Even though most classes perform a single, specific function in object-oriented programming, they sometimes need to share common helper functions. For example, we want to add log output to the data layer and UI layer when a thread enters and exits a method. Although the main functions of each class are different, the auxiliary functions they need to perform are similar.

  • Advice: code that needs to be injected into the. Class bytecode file. There are usually three: before, after, and around, respectively, before, after, and in place of target code execution. In addition to injecting code into a method, you can make other modifications, such as adding member variables and interfaces to a class.

  • Join point: the point in a program at which code insertion is performed, such as at method invocation or method execution.

  • Pointcut: An expression that tells the code injection tool where to inject specific code (that is, which Joint points need to apply specific Advice). It can select one such point (for example, the execution of a single method) or many similar points (for example, all methods marked by the custom annotation @debugtrace).

  • Aspect: Aspect ties pointcut and advice together. For example, we implement the aspect of adding a print log function to our program by defining a pointcut and giving an accurate advice.

  • Weaving: The process of injecting advice to a target join point.

The relation between the above nouns is shown as follows:

Specific usage scenarios for AOP programming

  • logging
  • persistence
  • Behavior monitoring
  • Data validation
  • The cache…

Timing of code injection

  • Runtime: Your code is clear about the need for enhancements, such as the need to use dynamic proxies (which is arguably not true code injection).

  • Load time: Changes are performed when the target class is loaded by Dalvik or ART. This is an injection of Java bytecode files or Android dex files.

  • Compile time: The compiled classes are modified by adding additional steps to the compile process before packaging the distribution.

Which way to use depends on the situation.

Several commonly used tools and class libraries

  • AspectJ: Extension tool for faceted programming that seamlessly integrates with the Java language (available for Android).

  • Javassist for Android: A well-known Java library for manipulating bytecodes ported to the Android platform.

  • DexMaker: A Java language-based API for generating code at compile time or runtime for the Dalvik VM.

  • ASMDEX: A bytecode manipulation library (ASM), but it handles Android executables (DEX bytecode).

Why AspectJ

  • A very powerful

  • Easy to use

  • Support code injection at compile time and load time

Take a chestnut

There is a requirement that we need to calculate the runtime of a method, and we want to do this by adding our own custom annotation @debugtrace to the method, rather than inserting the runtime code into the business code. Here we can do this through AspectJ.

Here are two things we need to know:

  • Annotations will be handled in a new step in our compilation process.

  • The necessary template code will be generated and injected into the annotated method.

This process can be understood in the following diagram:

In this example, we will split up two modules, one for business code and one for code injection using AspectJ. (For the record, AspectJ is itself a Java Library, and for AspectJ to work properly on Android, we use the Android Library because we have to use some hooks when compiling the application, You can only use the Android-Library Gradle plugin.

Create annotation
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface DebugTrace {}Copy the code
Create classes that control listening
/** * Class representing a StopWatch for measuring time. */
public class StopWatch {
  private long startTime;
  private long endTime;
  private long elapsedTime;

  public StopWatch(a) {
    //empty
  }

  private void reset(a) {
    startTime = 0;
    endTime = 0;
    elapsedTime = 0;
  }

  public void start(a) {
    reset();
    startTime = System.nanoTime();
  }

  public void stop(a) {
    if(startTime ! =0) {
      endTime = System.nanoTime();
      elapsedTime = endTime - startTime;
    } else{ reset(); }}public long getTotalTimeMillis(a) {
    return(elapsedTime ! =0)? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) :0; }}Copy the code
Encapsulate theandroid.util.Log
/**
 * Wrapper around {@link android.util.Log}
 */
public class DebugLog {

  private DebugLog(a) {}

  /**
   * Send a debug log message
   *
   * @param tag Source of a log message.
   * @param message The message you would like logged.
   */
  public static void log(String tag, String message) { Log.d(tag, message); }}Copy the code
Implementation of key Aspect classes
/** * Aspect representing the cross cutting-concern: Method and Constructor Tracing. */
@Aspect
public class TraceAspect {

  private static final String POINTCUT_METHOD =
      "execution(@org.android10.gintonic.annotation.DebugTrace * *(..) )";

  private static final String POINTCUT_CONSTRUCTOR =
      "execution(@org.android10.gintonic.annotation.DebugTrace *.new(..) )";

  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotatedWithDebugTrace(a) {}

  @Pointcut(POINTCUT_CONSTRUCTOR)
  public void constructorAnnotatedDebugTrace(a) {}

  @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();

    final StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    The annotated method is executed in this line of code
    Object result = joinPoint.proceed();
    stopWatch.stop();

    DebugLog.log(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));

    return result;
  }

  /**
   * Create a log message.
   *
   * @param methodName A string with the method name.
   * @param methodDuration Duration of the method in milliseconds.
   * @return A string representing message.
   */
  private static String buildLogMessage(String methodName, long methodDuration) {
    StringBuilder message = new StringBuilder();
    message.append("Gintonic --> ");
    message.append(methodName);
    message.append("-- >");
    message.append("[");
    message.append(methodDuration);
    message.append("ms");
    message.append("]");

    returnmessage.toString(); }}Copy the code

Two points about the above code:

  • We declare the two common methods and pointcut used to filter all the “org. Android10. Gintonic. The annotation. DebugTrace” tag method and the constructor.

  • The “weaveJointPoint(ProceedingJoinPoint joinPoint)” method we define is annotated with the “@around” annotation, which means that our code injection will take place before and after the method marked by the “@debugtrace” annotation.

The following diagram will help you understand the composition of the Pointcut:

Some necessary configurations in the build.gradle file

For AspectJ to run correctly on Android, you also need to do some necessary configuration in the build.gradle file, as follows:

import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com. Android. Tools. Build: gradle: 0.12 +'
    classpath 'org. Aspectj: aspectjtools:, version 1.8.1'
  }
}

apply plugin: 'android-library'

repositories {
  mavenCentral()
}

dependencies {
  compile 'org. Aspectj: aspectjrt:, version 1.8.1'
}

android {
  compileSdkVersion 19
  buildToolsVersion '19.1.0'

  lintOptions {
    abortOnError false} } android.libraryVariants.all { variant -> LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin) JavaCompile  javaCompile = variant.javaCompile javaCompile.doLast { String[] args = ["-showWeaveInfo"."1.5"."-inpath", javaCompile.destinationDir.toString(),
                     "-aspectpath", javaCompile.classpath.asPath,
                     "-d", javaCompile.destinationDir.toString(),
                     "-classpath", javaCompile.classpath.asPath,
                     "-bootclasspath", plugin.project.android.bootClasspath.join(
        File.pathSeparator)]

    MessageHandler handler = new MessageHandler(true);
    new Main().run(args, handler)

    def log = project.logger
    for (IMessage message : handler.getMessages(null.true)) {
      switch (message.getKind()) {
        case IMessage.ABORT:
        case IMessage.ERROR:
        case IMessage.FAIL:
          log.error message.message, message.thrown
          break;
        case IMessage.WARNING:
        case IMessage.INFO:
          log.info message.message, message.thrown
          break;
        case IMessage.DEBUG:
          log.debug message.message, message.thrown
          break; }}}}Copy the code
The test method
@DebugTrace
  private void testAnnotatedMethod(a) {
    try {
      Thread.sleep(10);
    } catch(InterruptedException e) { e.printStackTrace(); }}Copy the code

Running results:

Gintonic --> testAnnotatedMethod --> [10ms]Copy the code

We can see the injected code by decompiling the APK file.

conclusion

AOP programming in user behavior statistics is a very reliable solution, avoiding the direct buried point in the business code, and AOP programming application is not only this, it in performance monitoring, data acquisition and other aspects also have a wide range of applications, the follow-up will continue to study, and collate and release. AspectJ is a powerful library for AOP programming. The key to using AspectJ is to master its pointcut syntax. Here is a link to AspectJ’s official doc. Need to be summarized in the actual use process.