Aspect Oriented Programming in Android

03 Aug 2014

Aspect-oriented programming entails breaking down program logic into "concerns" (cohesive areas of functionality). This means, that with AOP, we can add executable blocks to some source code without explicitly changing it. This programming paradigm pretends that “cross-cutting concerns” (the logic needed at many places, without a single class where to implement them) should be implemented once and injected it many times into those places.

Code injection becomes a very important part of AOP: it is useful for dealing with the mentioned "concerns" that cut across the whole application, such as logging or performance monitoring, and, using it in this way, should not be something used rarely as you might think, quite the contrary; every programmer will come into a situation where this ability of injecting code, could prevent a lot of pain and frustration.

AOP is a paradigm that has been with us for many years, and I found it very useful to apply it to Android. After some investigation I consider that we can get a lot of advantages and very useful stuff when making use of it.

Terminology (Mini glossary)

Before we get started, let's have a look at some vocabulary that we should keep in mind:

This picture summarizes a bit a few of these concepts:

Aspect Oriented Programming

So…where and when can we apply AOP?

Some examples of cross-cutting concerns are:

And in relation with "when the magic happens", the code can be injected at different points in time:

Depending on the situation you will be choosing one or the other :).

Tools and Libraries

There are a few tools and libraries out there that help us use AOP:

Why AspectJ?

For our example below I have chosen AspectJ for the following reasons:

Example

Let's say we want to measure the performance of a method (how long takes its execution). For doing this we want to mark our method with a @DebugTrace annotation and want to see the results using the logcat transparently without having to write code in each annotated method. Our approach is to use AspectJ for this purpose.

This is what is gonna happen under the hood:

I have to say here that while I was researching I found Jake Wharton's Hugo Library that it is suppose to do the same, so I refactored my code and looks similar to it, although mine is a more primitive and simpler version (I have learnt a lot by looking at its code by the way).

AspectWeaving

Project structure

We will break up our sample application into 2 modules, the first will contain our android app and the second will be an android library that will make use of AspectJ library for weaving (code injection).

You may be wondering why we are using an android library module instead of a pure java library: the reason is that for AspectJ to work on Android we have to make use of some hooks when compiling our app and this is only possible using the android-library gradle plugin. (Do not worry about this yet, cause I will be giving some more details later).

Creating our annotation

We first create our Java annotation. This annotation will be persisted in the class (RetentionPolicy.CLASS) file and we will be able to annotate any constructor or method with it (ElementType.CONSTRUCTOR and ElementType.METHOD). So our DebugTrace.java file will look like this:

@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface DebugTrace {}

Our StopWatch for performance monitoring

I have created a simple class that encapsulates time start/stop. Here is our StopWatch.java class:

/**
 * Class representing a StopWatch for measuring time.
 */
public class StopWatch {
  private long startTime;
  private long endTime;
  private long elapsedTime;

  public StopWatch() {
    //empty
  }

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

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

  public void stop() {
    if (startTime != 0) {
      endTime = System.nanoTime();
      elapsedTime = endTime - startTime;
    } else {
      reset();
    }
  }

  public long getTotalTimeMillis() {
    return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;
  }
}

DebugLog Class

I just decorated the "android.util.Log" cause my first idea was to add some more functionality to the android log. Here it is:

/**
 * Wrapper around {@link android.util.Log}
 */
public class DebugLog {

  private DebugLog() {}

  /**
   * 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);
  }
}

Our Aspect

Now it is time to create our aspect class (TraceAspect.java) that will be in charge of managing the annotation processing and source-code weaving.

/**
 * 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() {}

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

  @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();
    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("]");

    return message.toString();
  }
}

Some important points to mention here:

Making AspectJ to work with Android

Now everything should be working, but, if we compile our sample, we will see that nothing happens.

The reason is that we have to use the AspectJ compiler (ajc, an extension of the java compiler) to weave all classes that are affected by an aspect. That's why, as I mention before, we need to add some extra configuration to our gradle build task to make it work.

This is how our build.gradle looks like:

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:1.8.1'
  }
}

apply plugin: 'android-library'

repositories {
  mavenCentral()
}

dependencies {
  compile 'org.aspectj:aspectjrt: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;
      }
    }
  }
}

Our test method

Let's use our cool aspect annotation by adding it to a test method. I have created a method inside the main activity for testing purpose. Let's have a look at it:

@DebugTrace
  private void testAnnotatedMethod() {
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

Executing our application

We build and install our app on an android device/emulator by executing the gradle command:

gradlew clean build installDebug

If we open the logcat and execute our sample, we will see a debug log with:

Gintonic --> testAnnotatedMethod --> [10ms]

Our first android application using AOP worked!

You can use the Dex Dump android application (from your phone), or any any other reverse engineering tool for decompiling the apk and see the source code generated and injected.

Recap

So to recap and summarize:

Conclusion

Aspect Oriented Programming is very powerful. Using it the right way, you can avoid duplicating a lot of code when you have "cross-cutting concerns" in your Android apps, like performance monitoring, as we have seen in our example. I do encourage you to give it a try, you will find it very useful.

I hope you like the article, the purpose of it was to share what I've learnt so far, so feel free to comment and give feedback, or even better, fork the code and play a bit with it.

I'm sure we can add very interesting stuff to our AOP module in the sample app. Ideas are very welcome ;).

Source Code

You can check 2 examples here, the first one uses AspectJ and the second one uses a Dynamic Proxy approach:

Resources