Is AspectJ Still Useful for Android? Part 2

In the first part of this post, I showed some ways that AspectJ would be useful in android development when it comes to testing. This final part demonstrates one way of incorporating AspectJ, and how I manage to make the aspect weaving somewhat configurable in the build process.

Android + AspectJ

A search on the internet will show that there are various ways of integrating AspectJ into the Android build process, including doing it manually or using a gradle plugin. I have been using the android-gradle-aspectj plugin for the last few years, since it has some useful features and the author seems to keep it maintained fairly well.

A basic setup for this plugin requires adding it to the buildscript repositories and dependencies blocks. I have this in build.gradle in the project root directory.

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:x.x.x'
    classpath 'com.archinamon:android-gradle-aspectj:x.x.x'
  }
}

Then apply the plugin where required, so in the module build.gradle:

apply plugin: 'com.archinamon.aspectj'

or

plugins {
    id 'com.android.application'
    id 'com.archinamon.aspectj'
}

This should be enough to run a simple case, such as the example from the first post. Please see the plugin documentation for more advanced configurations.

To verify that the aspect has been compiled and weaved during the build process, you can check:

  • the aspectj directory in the module build directory, containing the compiled aspect class files
  • some log files generated by the plugin in the module build directory, ajc-transform.log and ajc-compile.log
  • the build output log for the AspectJ plugin tasks, compile[Build variant]Aspectj and transformClassesWithAspectjFor[Build variant].

Determining When to Use Aspects or Not

If we’re only using aspects for testing, most of the time we don’t want the aspect code to be incorporated into the build process. An easy way to determine when to weave the aspect code or not is to use a Boolean condition.
For instance, in the aspect file include a Boolean condition in the pointcut to determine whether the advice will apply or not:

pointcut myPointcut(): if (aspectjEnabled) && ... // the rest of the pointcut

Here the Boolean value ‘aspectjEnabled‘ is used as a flag that will determine whether the aspect code will apply the advice.

One way of passing this flag to the aspect class is to use the generated BuildConfig class, which can be set in the build file (build.gradle) of the app.

android {
  buildTypes {
    debug {
      // Flags for Aspect testing
      buildConfigField 'boolean', 'aspectjEnabled', true
    }
  }
}

Then in the pointcut of the aspect file, include the flag as a conditional.

pointcut myPointcut(): if (BuildConfig.aspectjEnabled) && ... // the rest of the pointcut

Of course we can go further and set that flag from a gradle project property, rather than hardcoding it in build.gradle.

Now when the build is done, pass a project property to determine whether we want the aspect flag to be set or not.

-PaspectjEnabledProperty=true

I also have this extra bit to set the aspect flag to false if the project property was not specified (which is the default when we want to do a normal build without aspects).

ext.aspectjEnabledProperty = getAspectJEnabledFlag()

def getAspectJEnabledFlag() {
  if (project.hasProperty('aspectjEnabledProperty'))
    return project.property('aspectjEnabledProperty')
  else
    return false
}

Then in the build file for the app, the value of the aspect flag to be put into the BuildConfig class will be set dynamically.

android {
  buildTypes {
    debug {
      // Flags for Aspect testing
      buildConfigField 'boolean', 'aspectjEnabled', "${aspectjEnabledProperty}"
    }
  }
}

In sumary, this means that if the property ‘aspectjEnabledProperty’ is not passed to the build or is set to false, then the AspectJ weaving does not happen. Then when required, the property can be set for testing that particular bit of code.

Of course you are not restricted to one aspect flag, you can set as many flags as you like for the different tests you want to run.

-PexceptionTestProperty=true -PcrashTestProperty=false -PservicesTestProperty=true

Testing Multiple Types of Errors

In the configuration above, I’ve used a boolean value as the flag to enable/disable aspect weaving in the pointcut. Using a boolean flag is the simplest case, and probably the most common type that used.

However what about if you want to test multiple types of errors and error handling. For instance, if you want to test having the adviced method throw different types of exceptions. Another example is if you want to simulate different status codes as the response from a network call.

Then instead of using a boolean value as the flag, use an integer (or any other simple type) for example.

-PaspectjTestingProperty=1

Then in the build file:

ext.aspectTestingFlag = getAspectTestingFlag()

def getAspectTestingFlag() {
  if (project.hasProperty('aspectjTestingProperty'))
    return project.property('aspectjTestingProperty')
  else
    // default value if property not specified
    return 0
}

android {
  buildTypes {
    debug {
      // Flags for Aspect testing
      buildConfigField 'int', 'aspectTestingFlag', "${aspectjTestingProperty}"
    }
  }
}

Then in the aspect, you can use the integer flag both as a conditional in the pointcut and as an indicator run different code in the advice.

aspect TestAspect {

  pointcut myPointcut(): if (BuildConfig.aspectjTestingFlag != 0) && ... // the rest of the pointcut

  int around(): myPointcut() {
    switch (BuildConfig.aspectjTestingFlag) {
      case 1:
      // return a particular status code, or throw a particular exception type

      case 2:
      // return a different status code, or throw a different exception type

      case 3:
      // return yet another status code, or throw a third different exception type

      default:
      return proceed();
    }
  }
}

Caveats

Just a few things to be aware of:

  1. As already mentioned, since we can only have AspectJ compile time weaving for Android development, this means some additional build time for the AspectJ tasks.
  2. Unfortunately Android Studio doesn’t have IDE support for AspectJ, since it is based on the community edition of IntelliJ IDEA.
    While the Ultimate version of Intellij does support AspectJ, I prefer using Android Studio.

    Tip:
    Sometimes to get the pointcuts correct I will copy files with the aspect, the classes I want to advice and their lib dependencies into a dummy Eclipse project so that I can use the AspectJ Development Tools (AJDT) to fine tune the pointcut. This allows me to see if the pointcuts are being applied correctly to the joinpoints I want.

  3. Using a Gradle plugin to handle incorporating AspectJ into the build process does make things easier. However this sometimes means having to wait for the authors of the plugin to keep up to date with the latest versions of the android gradle plugin or gradle.

Is AspectJ Still Useful for Android? Part 1

For Java projects I sometimes use AspectJ for various tasks, but for Android projects I initially decided against using it.

There were various reasons for that:

  • The structure of an Android project, and its build process, was already a lot more complicated and slower than for generic Java projects.
  • For Android development, only compile time weaving is supported for AspectJ, which makes it less useful.
  • The Android Studio IDE does not have support for AspectJ.

However I eventually found some uses for aspects when it comes to testing. Another reason I decided to give AspectJ a try was the availability of Android AspectJ plugins for Gradle. They make integrating AspectJ into the Android build process easier, I use this one.

Using aspects I can weave test code into the application code when required for a particular test. Then when no longer needed, I would just do a normal build without the aspect weaving, and the test code is kept totally isolated in the aspect file.
I found this to be a good alternative to what sometimes happens during testing:

  • adding test code for a particular test, and then having to remove it or comment it out afterwards
  • adding temporary logging to find a bug, profile some code, etc

What is it Good For, Some Examples

Testing Crashlytics

without AspectJ:

When testing for Crashlytics, the Firebase documentation recommends adding a temporary button with a click handler that crashes the app.

with AspectJ:

Instead I can weave the crash inducing code into any existing app event I like, e.g a button click, menu click, etc.

Testing exception handling

without AspectJ:

Write some temporary test code to throw an exception to see if the app handles it correctly.

with AspectJ:

Have an aspect that throws the exception from any method I choose. I can also have the pointcut advice multiple methods so that the test exception can be thrown from multiple locations.

Testing interactions with external services

(For instance, checking that Google Play services are available.)

without AspectJ:

Write some test code to simulate the response from the external service that you want to test for. For example, when checking if Google Play services are available then you might want to test that your app correctly handles an unsuccessful result code such as SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED, etc.

with AspectJ:

Write an aspect that advices where you call the external service, and have it return the result that you want to test for. It’s up to you whether you want to actually make the call to the external service in the advice using proceed(..) or just have the advice return the result you want.

The Usual Stuff, like Tracing

One of the most common uses of AspectJ is for method tracing, this can be useful for profiling the app or when trying to track down that hard-to-find bug.

without AspectJ:

Add temporary trace logging to the app code that you think might need profiling or be causing a bug.

with AspectJ:

Enable tracing of method calls with logging for any or all method calls that I want by adjusting the pointcut for of a tracing aspect. It’s quick and easy to write a pointcut to advice all methods in a class, for example.

There are plenty of examples of AspectJ tracing aspects around, or you can use android tools to generate trace logs by instrumentation.

An AspectJ Example

Let’s start with a common scenario, a service class with a method that makes a network call (presumably to get some data and apply business logic to it). This service method would be called by an activity or fragment in the app.
Note that this example assumes that you are already familiar with how to program in AspectJ native syntax.

public class BusinessService {

  public Result serviceNetworkCall() {
    return ... // return some data from a network call
  }
}

I want to test that the activity/fragment correctly handles the situation where there is an error when the service method is called. Here is an aspect that can be used to simulate an exception being thrown on the method call.

aspect TestExceptionInjector {

  pointcut injectTestException(): target(BusinessService) && execution(public Result *(..));

  Result around(): injectTestException() {
    // can allow the adviced method to run first before the exception is thrown by calling <em>proceed()</em> first, in case we want some side effects to occur

    // simulate an exception being thrown from the method(s) adviced
    throw new RuntimeException("TEST");
  }
}

In this aspect the pointcut will advice the service method, and throws an exception instead its normal execution. Then we can run the app to check that the Activity / Fragment that calls the service method does handle this error properly.

I can also do this where a reactive library, such as RxJava, is being used in the service class.

public class BusinessServiceRx {

  public Observable<Result> serviceNetworkCallRx() {
    return ... // return some data from a network call
  }

  public Observable<Result> anotherServiceNetworkCallRx() {
    return ... // return some data from a network call
  }
}

privileged aspect TestExceptionInjectorRx {

  pointcut injectTestExceptionRx(): target(BusinessServiceRx) && execution(public Observable<Result> *(..));

  Observable<Result> around(): injectTestExceptionRx() {
    // simulate an exception being thrown downstream from the method(s) adviced
    return Observable.error(new RuntimeException("TEST"));
  }
}

This example also shows that the pointcut in the aspect can advice multiple service methods.

Determining When to Build with Aspects (or Not)

Next, we need to weave the aspect in the example into the app code, and then find some way to determine when to do a build with aspects for testing, and when to just do a normal build. In the concluding part of this post, I will show the workflow I use as one way of doing this.

For Testing Only?

As I have mentioned, when using aspects the test/logging code resides in an aspect file that is isolated from the app code and should never get into a release build. However it is always available when I want to run the tests again, and there is no need to worry about forgetting to remove temporary test code after the testing is done.

The use of AOP (Aspect Oriented Programming) for testing only is purely my personal preference. Of course you can just as easily use AspectJ as part of your development process and include aspects in your app code.