Tuesday, April 19, 2011

android and aspect oriented programming (aop)

When OOP appeared in the software world it was pioneering. Encapsulation! Abstraction! OOP became the next big thing and it's still huge. OOP is great; you can define an ontology and build out what the grammar of whatever your layer/application is. It's fantastic. But there are simply some things that don't really belong to an object. We'll call these cross-cutting concerns. These concerns are pervasive throughout an application (i.e. transaction registration, CRM auditing) and typically functional. Enter AOP (aspect oriented programming) brought to us some years ago by the guys at Xerox PARC. Some will say it's made things a lot easier, some would say I have no clue what is going on. AOP compliments OOP very well. Some will say that you can do the same with well defined interfaces or abstracts BUT that truly does require dead accuracy basically the first time, can cause drastic issues if refactoring is needed and simply can clutter your code. AOP is a new(er) strategy for modularization. Note: another good one is the decorator pattern.

Getting back to it. I've used many AOP technologies and am currently using AspectJ rather frequently in lower level framework development. Making some concerns declarative via an annotation, or simply flat out applied to all methods (i.e. tracing, execution auditing), for some reason makes it a lot easier to understand. Hide the magic.

Recently I've begun doing a significant amount of Android development across many products and have thought to myself Can I get AOP to work on the Dalvik VM?. Well, the answer is yes. Largely I've had a desire to do this b/c I lead a group of developers and unifying a development process always comes from the bottom up. This guarantees adherence to pattern and convention above and beyond coding standards and promotes a massive amount of re-use. This very important for making sure certain concerns are addressed in the same fashion (i.e. exception handling).

Granted AOP has taken off in certain areas and has not in others the challenge of introducing advice into Android code was a challenging one up front but not insurmountable. The reason being for this is that the first building block of Android is a Java development environment and the Java class byte code format. The next stage, after development, is the packaging of these java class files into the Dalvik Executable format (DEX). Unfortunately advice cannot be applied directly to DEX files b/c currently the Android runtime lacks a byte code generation scheme where Java supports several forms of advice introduction:

  1. compile time -- byte code manipulation.
  2. load time -- byte code manipulation. Has a slower startup time.
  3. run time -- auto-proxies of various forms (an is-a relationship) or jdk interceptors (Spring for example can work in both of these paradigms but it is fundamentally the slowest).

With that said applying advice to an Android app, due to the lack byte code generation schemes, is option 1. With this we can write java code and compile aspects directly into it and finally convert it to the DEX format to run on android devices.

One thing to note about DEX is that it unpacks all jar files and packages all dependent classes into a single zip archive, classes.dex. This could be because of several historical reasons:

  1. J2ME has proven to have issues in the past with class loaders and and a large number of classes.
  2. not having the byte code in the java format mitigates the need for the OEM to have to pay Sun / J2ME licensing fees.

A bit of history....

Let's get back to it.

I'm a firm believer in logical architecture where your libraries represent modularized concerns. With that said I also like to create aspects that sit at a lower level or at that of an API. This can be done generally via abstract pointcut definitions to allow the application to define the fully qualified pointcut definition but the same advice is applied; or, by proceeding in a declarative fashion. This is great b/c it engages the developer and removes the need for them to understand pointcut definitions.

So, considering this, let's create a Java project in maven with an aspect to be applied to another module. We'll call it android.aop. The following is a boiled down maven build script for the android.aop project:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>some.group</groupId>
 <artifactId>android.aop</artifactId>
 <version>1.0.0</version>
 <packaging>jar</packaging>

 <dependencies>
  <dependency>
   <groupId>com.google.android</groupId>
   <artifactId>android</artifactId>
   <version>2.2.1</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>1.6.10</version>
   <scope>provided</scope>
   <optional>false</optional>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.6.10</version>
   <scope>provided</scope>
   <optional>false</optional>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjtools</artifactId>
   <version>1.6.10</version>
   <scope>provided</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.3.1</version>
    <configuration>
     <source>1.5</source>
     <complianceLevel>1.5</complianceLevel>
     <showWeaveInfo>true</showWeaveInfo>
     <verbose>true</verbose>
    </configuration>
    <executions>
     <execution>
      <goals>
       <goal>compile</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.5</source>
     <target>1.5</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>


The maven-aspectj-plugin will look for aspects beneath src/main/aspect.

An android apk maven build script (boiled down of course):


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>some.other.group</groupId>
 <artifactId>android.apk</artifactId>
 <version>1.0.0</version>
 <packaging>apk</packaging> 

 <dependencies>
  <dependency>
   <groupId>com.google.android</groupId>
   <artifactId>android</artifactId>
   <version>2.2.1</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>some.group</groupId>
   <artifactId>android.aop</artifactId>
   <version>1.0.0</version>
   <type>jar</type>
   <scope>compile</scope>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>1.6.9</version>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjtools</artifactId>
   <version>1.6.10</version>
   <scope>provided</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>com.jayway.maven.plugins.android.generation2</groupId>
    <artifactId>maven-android-plugin</artifactId>
    <version>2.8.4</version>
    <configuration>
     <sdk>
      <platform>2.1</platform>
     </sdk>
     <extractDuplicates>true</extractDuplicates>
     <undeployBeforeDeploy>true</undeployBeforeDeploy>
    </configuration>
    <extensions>true</extensions>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.3.1</version>
    <configuration>
     <argumentFileName>trace.lst</argumentFileName>
     <aspectLibraries>
      <aspectLibrary>
       <groupId>some.group</groupId>
       <artifactId>android.aop</artifactId>
      </aspectLibrary>
     </aspectLibraries>
     <source>1.5</source>
     <complianceLevel>1.5</complianceLevel>
     <showWeaveInfo>true</showWeaveInfo>
     <verbose>true</verbose>
    </configuration>
    <executions>
     <execution>
      <goals>
       <goal>compile</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.5</source>
     <target>1.5</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

Now that the build rigmarole is out of the way you can focus on aspect development. For instance, a declarative approach to audit use case execution perhaps.

/**
 * An annotation to be applied to methods of which we would like to track their execution
 * (i.e) createAccount
 */
public @interface Tracked {

 // add in whatever you need to track 

}

The example skeleton aspect that handles the hitting of the method:

@Aspect
public class TrackingAspect {

 @AfterReturning(value = "execution(* *(..)) && @annotation(tracked)", argNames = "joinPoint, tracked")
 public void track(JoinPoint joinPoint, Tracked tracked) {
  // whatever your tracking logic may be. 
  // The JoinPoint injection is only added with an execution pointcut is defined.
  // the tracked is passed in as an arg on the join point
 }
}

These two classes would end up in your android.aop jar and would allow for some bit of functionality to occur within your application whenever a method decorated with the @Tracked annotation appears. These annotations don't necessarily need to have a Runtime retention policy b/c we are actually applying the advice at compile time. If the Dalvik VM supported load time weaving (had a byte code generation scheme) you could leverage a Runtime retention policy.

Another that I've found useful with various declarative advice applications is leveraging JEXL (Java expression language) to pass runtime metadata to the interwoven advice via the call stack ... but we'll leave that for another post.

At any rate, that's the high and low of it. Much thanks to Nikhil Walvekar ( published an example of aspectj method tracing in Android ) for his assistance with helping me figure out a modular build in maven.

If you have questions don't hesitate to email me.

dan

No comments: