Using AspectJ in Android with Eclipse

AOP tries to solve the problem of modularizing specific features (called concerns) that are needed throughout all the layers of a software product. Examples of such concerns can be logging and tracing, security or even database transactions management. Instead of polluting all application layers, AOP tries to extract those concerns into separate components and “weave” them into the code at compile-time or run-time. This post examines how AspectJ can be utilized in an Android project using Eclipse.

Installing AspectJ

To make Eclipse aware of AspectJ the “AspectJ Development Tools” plugin has to be installed. The Eclipse version that is distributed with the ADT is missing the Eclipse Marketplace feature. To install a plugin go to “Help” -> “Install New Software” and paste the URL for your Eclipse version into the site field.

Select the “AspectJ Development Tools” feature from the list of available features and click “Finish”.

An AspectJ-ready Android project

Add AspectJ nature

The first step is to make the Android project AspectJ aware. This is done by opening the context menu for the Android project -> “Configure” -> “Convert to AspectJ Project”. This adds the AspectJ Builder to the compile phase of the project. The current list of builders can be seen in the project properties -> “Builders” tab.

alt

Export Runtime

Next step is to export the AspectJ runtime library so that the Android toolchain picks it up and compiles it into the apk. In the project properties -> “Java Build Path” -> “Order and Export” the “AspectJ Runtime Library” has to be checked.

alt

This is everything that has to be done to add AspectJ to an Android project. Now everything is set up and ready to be used.

Logging as a cross-cutting concern

As an example we take a look at a small sample application that logs all Activity and Fragment lifecycle methods using an Aspect.

The Aspect

The Aspect below consists of two Pointcuts and one Advice. While the Advice defines what the cross-cutting functionality is, the two Pointcuts define where it should be weaved in.

public aspect LogAspect {

    // Pointcuts for Activity and Fragment onSomething() methods
    pointcut activityMethods() : execution(* Activity+.on*(..)) ;
    pointcut fragmentMethods() : execution(* Fragment+.on*(..)) ;

    // Advice that gets executed "around" the Pointcuts
    Object around() : (activityMethods() || fragmentMethods()) {
        String method = thisJoinPoint.getSignature().toShortString();
        Log.d("Lifecycle", "Enter " + method);
        Object result = proceed();
        Log.d("Lifecycle", "Exit  " + method);
        return result;
    }
}

During the build phase, and after the Java compiler compiled the Java code, the AspectJ builder steps in. For each Aspect and its respective Pointcuts, it finds all the locations in the compiled code matching those Pointcuts. At each location AspectJ modifies the existing code with the code defined in the Advices.

The Method Trace

Starting the application and opening the Activity results in the following logs entries:

Enter MainActivity.onCreate(..)
Exit  MainActivity.onCreate(..)
Enter MainActivity.PlaceholderFragment.onAttach(..)
Exit  MainActivity.PlaceholderFragment.onAttach(..)
Enter MainActivity.PlaceholderFragment.onCreate(..)
Exit  MainActivity.PlaceholderFragment.onCreate(..)
Enter MainActivity.PlaceholderFragment.onCreateView(..)
Exit  MainActivity.PlaceholderFragment.onCreateView(..)
Enter MainActivity.PlaceholderFragment.onActivityCreated(..)
Exit  MainActivity.PlaceholderFragment.onActivityCreated(..)
Enter MainActivity.onStart()
Exit  MainActivity.onStart()
Enter MainActivity.PlaceholderFragment.onStart()
Exit  MainActivity.PlaceholderFragment.onStart()
Enter MainActivity.onResume()
Exit  MainActivity.onResume()
Enter MainActivity.PlaceholderFragment.onResume()
Exit  MainActivity.PlaceholderFragment.onResume()

The Compiled Code

The best way to understand how AspectJ adds itself at compile time is by looking at the compiled code that gets packaged in the apk. Lets examine this by looking at the code for the onCreate() method. Note that the code has been cleaned up to improve readability.

public class MainActivity extends Activity {

    private static final JoinPoint.StaticPart joinPoint0;
    // Further join points ...

    static {
        MainActivity.aspectjInit();
    }

    private static void aspectjInit() {
        Factory factory = new Factory("MainActivity.java", (Class)MainActivity.class);

        Signature signature0 = (Signature)factory.makeMethodSig("4", "onCreate",
            "org.bitbrothers.example.aspectjsample.MainActivity",
            "android.os.Bundle", "savedInstanceState", "", "void");

        MainActivity.joinPoint0 = factory.makeSJP("method-execution", signature0, 13);

        // Further join points initialization ...
    }

    // ... continues below ...

When the Activity class gets loaded aspectjInit() is called to initialize the JoinPoint.StaticPart for each inserted Advice. These “static parts” contain the method signature information of their respective insertion points.

    protected void onCreate(Bundle bundle) {
        MainActivity.onCreate_aroundBody1_advice(this, bundle,
            LogAspect.aspectOf(), null, MainActivity.joinPoint0);
    }

    // ... continues below ...

Since the onCreate() method matches one of the join points, it got replaced with a wrapper method that adds the necessary Aspect information to it. The wrapper function calls the Advice method.

    private static final Object onCreate_aroundBody1_advice(MainActivity activity, Bundle bundle,
            LogAspect logAspect2, AroundClosure logAspect2, JoinPoint.StaticPart staticPart) {
        String string = staticPart.getSignature().toShortString();
        Log.d("Lifecycle", "Enter " + string);
        MainActivity.onCreate_aroundBody0(activity, bundle);
        Log.d("Lifecycle", "Exit  " + string);
        return null;
    }

    // ... continues below ...

The Advice method contains the same code that has been defined in the around() Advice. The proceed() method call has been replaced with a call to another method that contains the actual onCreate() code.

    private static final void onCreate_aroundBody0(MainActivity activity, Bundle bundle) {
        activity.onCreate(bundle);
        activity.setContentView(2130903040);
        if (bundle == null) {
            activity.getFragmentManager().beginTransaction().add(2131165184,
                (Fragment)new PlaceholderFragment()).commit();
        }
    }

    // Further wrapper and lifecycle functions for this Activity ...
}

And that is it. AspectJ added one static variable and two methods to insert an Advice at a specific point in the code.

The sample code is hosted at Github.

More information

The example above only scratches the surface of what is possible. More information can be found here:

comments powered by Disqus