Aspect-Oriented Programming in Android

Update March 20, 2021: The sample project has been updated to use the latest stable Android Gradle Plugin release (4.1.3). If anyone has any issues, feel free to reach out!

Aspect-Oriented Programming (AOP) is a paradigm that allows for cross-cutting concerns to be taken care of without cluttering your files with the same redundant code everywhere. This article will try to give a broad overview of why you’d want to use AOP, how it works, and then give some concrete examples of usage.

AOP in Android: The Why

  • Analytics must be tracked every time any button is clicked
  • You need to do a root check every time an activity or fragment resumes
  • You want to log information about multiple methods in one or more classes

For all of the above cases, we could do something simple such as copying the relevant code multiple times and it might not even look too bad. For example, if we have multiple buttons and we want to log the button clicks we could just simply write the following:

button1.setOnClickListener(v -> {
String text = ((TextView) v).getText().toString();
loggingViewModel.logItem(text);
//some other actions performed when clicking Button 1
}
});

button2.setOnClickListener(v -> {
String text = ((TextView) v).getText().toString();
loggingViewModel.logItem(text);
//some other actions performed when clicking Button 2
}
});

This would work well enough. But what if we had hundreds of buttons or the method of logging was more than 2 lines? This could quickly become cumbersome but could also lead to a problem of forgetting to actually do the logging in some cases.

With Aspect-Oriented Programming, we can write the logging code once and guarantee that any buttons we currently have onClickListeners for and any we add in the future will have the logging behavior that we are aiming to achieve.

AOP in Android: The How

In order for the aspect weaver to figure out where code should be injected, we write Pointcuts - expressions specifying where code should be injected. These can have as narrow a scope as a single method or as broad a scope as any method in any class (which probably wouldn’t realistically be very useful). Here are some examples:

@Pointcut("execution(void *.onClick(..))")

The pointcut can be broken into multiple parts that can be used to figure out which methods will be affected. The execution portion specifies what type of designator we are using. Execution is usually the designator we will use as it designates that we will inject code where the specified method(s) are executed. For a list of designators and what they do see section 8.2.3 of this guide.

The stuff wrapped inside the execution designator specifies what methods to match. These have the signature of [return type] [fully qualified method name]([argument types]). We can either specify the types we want or use wildcards. In the above, we are matching void methods that are in any file (*. before the method name) that have any or no arguments (.. specifies any amount of arguments).

@Pointcut("execution(* *.activity.*.onCreate(android.os.Bundle))")

The above will match any method named onCreate with any return type as long as it has a single argument of type android.os.Bundle and and lies within a package named activity. As imagined, pointcuts can become complex but luckily, they can be chained together using && and || statements.

So if a pointcut specifies where injected code should go, an advice specifies what code is injected. An advice can be simply thought of as a method that will be called where the pointcut specifies. The advice also specifies at what point it should be called in relation to the method designated by the pointcut. For instance, you can specify that the method should be run before, after, or around the pointcut-specified method.

Taken together, a pointcut and an advice is a single aspect, which is where AOP gets its name.

Actual implemenation

After setting up the dependency, create a file for storing your aspects. You might consider have multiple files to keep things cleaner, e.g. one for logging, one for security, one for analytics, etc.

Next, write an empty method for the pointcut you want to add. For instance, if I am tracking button clicks, I might write the following:

@Pointcut("execution(void *.onClick(..))")
public void onButtonClick() {}

After you have a pointcut defined, you can write an aspect. The aspect should specify when the code should run in relation to the pointcut using the @Before, @After, or @Around annotations. Before will cause the code to run in the aspect to run before the code in the targeted method and after will cause the aspect to run after code in the targeted method. The Around annotation is quite a bit more strange and powerful. Around can allow your aspect to run both before and after the target code and even to run instead of the target code if that’s what you decide. There are a few other types that can be read about in section 8.1.1 of this guide.

Say we decide that we want to log our button clicks to the console every time any button is clicked but before the onClickListeners actually do anything. In that case we might write the following aspect.

@Before("onButtonClick()")
public void onClickAdvice() {
//todo
}

This is good and code would be weaved but we actually don’t know what button was clicked so logging might be useless. Luckily, we can specify that we want to capture the arguments to the targeted method using the args designator! We specify a name in the args designator and then use that name in conjunction with a type in the advice signature. This will then guarantee that only methods that have the specified argument type will be matched when doing injection. Thus we will get this method:

@Before("onButtonClick() && args(view)")
public void onClickAdvice(View view) {
if (view instanceof TextView) {
String text = ((TextView) view).getText().toString();
loggingViewModel.logItem(text);
}
}

We specify that we are looking for a View object as the argument to the method. In this case the name ‘view’ doesn’t matter and is only used within the aspect and would match no matter what the actual source code had named the View item.

The above code runs. If you build your project at this point, you will log every time a button is clicked (assuming you have added buttons with onClickListeners to your project). You can check this manually, or you check the build directory for a file named ajc-transform.log that will specify where code has been injected and if there were any issues. If you don’t have any issues you’ll see something like this:

Sun Jul 01 16:22:25 EDT 2018
Join point 'method-execution(void me.jdvp.androidaspectexample.activity.MainActivity$2.onClick(android.view.View))' in Type 'me.jdvp.androidaspectexample.activity.MainActivity$2' (MainActivity.java:37) advised by before advice from 'me.jdvp.androidaspectexample.aspect.AspectLogging' (AspectLogging.class(from AspectLogging.java))

Join point 'method-execution(void me.jdvp.androidaspectexample.activity.MainActivity$1.onClick(android.view.View))' in Type 'me.jdvp.androidaspectexample.activity.MainActivity$1' (MainActivity.java:30) advised by before advice from 'me.jdvp.androidaspectexample.aspect.AspectLogging' (AspectLogging.class(from AspectLogging.java))

This tells us that our onClickAdvice advice from AspectLogging.java matched two separate items in MainActivity. Nice!

At this point, you can add new onClickListeners for new buttons and they will automatically have this logging attached when the project is built. But these kinds of pointcuts are only the start. You can write more complicated pointcuts that match based on inherited interfaces rather than specific packages or method names or choose only methods that have specific annotations for instance. If you have questions about those kinds of things feel free to ask!

I hope that this article could provide some high-level insight into reasons why you might want to leverage Aspect-Oriented Programming techniques in your own projects and that the provided examples were helpful. A sample project with this implemented can be found here on my Github. Be sure to let me know if you have any suggestions or questions! Thanks!

Relevant links

Spring guide to AOP. I used this guide a lot when trying to figure out how to use pointcuts. It was especially useful for more complicated things such as targeting implementing classes of interfaces and annotation-based pointcuts since it provides a lot of samples.

This article that I found about AOP in Android specifically. Uses a different library but the ideas are the same!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store