In this series of blogposts, we shall attempt to talk you through how we use Maven and do Behaviour and Test Driven Development (including a description of mocking) on the Android platform*, while staying agile all the way. This, the first of the series, will focus on using Maven with Android.
Maven is a build automation and software comprehension tool. While primarily used for Java programming, it can also be used to build and manage projects written in C#, Ruby, Scala, and other languages. Maven serves a similar purpose to the Apache Ant tool, but it is based on different concepts and works in a profoundly different manner. Android does not support Maven out of the box, we need to use the Android Maven Plugin.
Maven is a build automation and software comprehension tool. While primarily used for Java programming, it can also be used to build and manage projects written in C#, Ruby, Scala, and other languages. Maven serves a similar purpose to the Apache Ant tool, but it is based on different concepts and works in a profoundly different manner. Android does not support Maven out of the box, we need to use the Android Maven Plugin.
- Create an Android project (you can do this using Eclipse or the command line; take a look at http://developer.android.com for more). The application we are building will be for Android platform 10 (v2.3.3).
- Ensure that a folder called src/ exists in the main project directory. If you’re using Eclipse, you’ll want the src folder to be a source folder.
- Since we will be attempting to practice TDD in subsequent blogposts, create a folder called tests. Add a subfolder called tests/src, and ensure Eclipse sees this as a source folder.
- Add an AndroidManifest.xml file to the tests/ folder. The AndroidManifest.xml file should use a different package name (I used com.cloudreach.tests). However, I prefer to place my tests for a particular class in the same package as the class under test itself.
- In the main project directory, create a file called pom.xml and add the following lines in:
<?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>
</project>The Application pom.xml
The first section on the pom file of interest to us is this:
Here we specify the group ID (com.cloudreach). This should ideally be the same as the package name you specified in the Android manifest. Specify the version ID of the application as appropriate. We use Semantic Versioning at Cloudreach (hence the version number). Android applications are packaged as apks, and the name tag specifies the human readable name of the application. <groupId>com.cloudreach</groupId>
<artifactId>AwesomeApp</artifactId>
<version>0.1.0</version>
<packaging>apk</packaging>
<name>The Awesome Application</name>
We then need to specify our dependencies. Currently, the only dependency we have is the Android framework itself (the android.jar file). Add that in.
<dependencies>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>install</defaultGoal>
<finalName>${project.artifactId}-${project.version}</finalName>
<sourceDirectory>${project.basedir}/src</sourceDirectory>
<!-- there’s more coming in this space, read on -->
</build>
<build>
<!-- the stuff from up above there -->
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>maven-android-plugin</artifactId>
<configuration>
<androidManifestFile>
${project.basedir}/AndroidManifest.xml
</androidManifestFile>
<assetsDirectory>${project.basedir}/assets</assetsDirectory>
<resourceDirectory>${project.basedir}/res</resourceDirectory>
<nativeLibrariesDirectory>${project.basedir}/src/main/native</nativeLibrariesDirectory>
<sdk>
<platform>10</platform>
</sdk>
<deleteConflictingFiles>true</deleteConflictingFiles>
<undeployBeforeDeploy>true</undeployBeforeDeploy>
</configuration>
<extensions>true</extensions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
There’s lots of other funky stuff you can write into your pom.xml files, but I’m not going to go into them. The complete pom.xml file can be found here.
Building using Maven
Before you build the application, you need to add the path to your installation of the Android SDK into the ANDROID_HOME environment variable. You may then want to save it into your .bash_profile or .bashrc files.
To build the application,
To build the application,
- Open up a terminal, navigate to the root directory where the application is located.
- Type
mvn install(or mvn, if you’ve used my pom file as a template; install is the default target). - You can then watch maven magically download stuff into your local repository from the Maven central repository (and any other repositories you may have added) and build stuff for you, as if by magic.
- Type mvn android:deploy to deploy the application to an attached Android device.
Signing, Verifying, and Aligning your APK
In order to release an Android application, we should sign it using our private key. The way we do it using Maven is slightly sub-optimal for reasons mentioned below. To sign our Android application, we need to undertake the following steps:
- Create a private key using keytool (see the aforelinked document for details):
- We should now modify the pom.xml to add a new build profile called release, which is activated when the property release is set to true (i.e., mvn -Drelease=true)
<profiles> <profile> <id>release</id> <activation> <activeByDefault>false</activeByDefault> <property> <name>release</name> <value>true</value> </property> </activation> </profile> </profiles> - Copy the default <build> section of your pom.xml file into the new profile, and add a new section within the <configuration> subsection.
<sign> <debug>false</debug> </sign> - We shall now sign the APK. We shall use the maven-jarsigner-plugin for this. To do so, we need to first add information about our keystore into a file called keystore.properties (in the same directory as the pom.xml file). Unfortunately, you will have to enter your password in plaintext. However, this is marginally better than entering it (and the path to your keystore) directly into the pom.xml file. The keystore.properties file should contain the following information:
keystore.alias=Cloudreach keystore.location=/Users/siddhu.warrier/.ssh/Cloudreach.keystore keystore.password=[I’M NOT TELLING YOU THIS, DEAR READER] - Now, in the <plugins> subsection of the <build> section that you copied over from the default profile, add two new plugins; the maven-jarsigner-plugin to sign the APK and the properties-maven-plugin to read data from the properties from the keystore.properties file.
Now, the most interesting part of the above code are the<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <version>1.0-alpha-2</version> <executions> <execution> <phase>initialize</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>${basedir}/keystore.properties</file> </files> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jarsigner-plugin</artifactId> <version>1.2</version> <executions> <execution> <id>signing</id> <goals> <goal>sign</goal> </goals> <phase>package</phase> <inherited>true</inherited> <configuration> <includes> <include> ${project.build.directory}/target/${project.artifactId}-${project.version}.apk </include> </includes> <keystore>${keystore.location}</keystore> <storepass>${keystore.password}</storepass> <alias>${keystore.alias}</alias> <verbose>true</verbose> </configuration> </execution> </executions> </plugin>, , and the sections, where we refer to the properties we defined in keystore.properties and read using the properties-maven-plugin. - Zipalign is an archive alignment tool that provides important optimization to Android application (.apk) files, thereby reducing the amount of RAM consumed when running the application. To zipalign the application, we need to add two blocks to the maven-android-plugin block in the pom.xml file.
and<executions> <execution> <id>zipalign</id> <phase>verify</phase> <goals> <goal>zipalign</goal> </goals> </execution> </executions>
<executions> <execution> <id>zipalign</id> <phase>verify</phase> <goals> <goal>zipalign</goal> </goals> </execution> </executions> - To now create a signed and aligned APK, we should run the following command:
mvn install -Drelease=true
[siddhu.warrier@realMac:~ ]$keytool -genkey -v -keystore ~/.ssh/Cloudreach.keystore -alias Cloudreach -keysize 2048 -keyalg RSA -validity 10000
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: Siddhu Warrier
What is the name of your organizational unit?
[Unknown]: Cloudreach Dev Team
What is the name of your organization?
[Unknown]: Cloudreach Limited
What is the name of your City or Locality?
[Unknown]: London
What is the name of your State or Province?
[Unknown]: Greater London
What is the two-letter country code for this unit?
[Unknown]: GB
Is CN=Siddhu Warrier, OU=Cloudreach Dev Team, O=Cloudreach Limited, L=London, ST=Greater London, C=GB correct?
[no]: yes
Generating 2,048 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 10,000 days
for: CN=Siddhu Warrier, OU=Cloudreach Dev Team, O=Cloudreach Limited, L=London, ST=Greater London, C=GB
Enter key password for <Cloudreach>
(RETURN if same as keystore password):
[Storing /Users/siddhu.warrier/.ssh/Cloudreach.keystore]
The Test pom.xml
As in the previous application pom.xml file, the first section that is of interest to us is this:
<groupId>com.cloudreach</groupId>
<artifactId>AwesomeApp-tests</artifactId>
<version>0.1.0</version>
<packaging>apk</packaging>
<name>The Awesome App - Tests</name>Make sure that the artifact ID is different to the artifact ID used for the main pom.xml file.
Now, we get to the dependencies section. Please note that we could have a single pom.xml file that is the parent of both the application and test pom.xml files, but given how we typically structure our Android projects, this does not make sense.
Now, we get to the dependencies section. Please note that we could have a single pom.xml file that is the parent of both the application and test pom.xml files, but given how we typically structure our Android projects, this does not make sense.
In addition to the dependency on the android JAR file which the test project shares with the main project, we need to add the following additional dependencies:
- The android test instrumentation test runner, which the lads at the Android Maven Plugin project have added to the Maven central repository,
- JUnit, and
- the application under test.
<dependency> <groupId>com.google.android</groupId> <artifactId>android-test</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> </dependency> <dependency> <groupId>com.cloudreach</groupId> <artifactId>AwesomeApp</artifactId> <type>apk</type> <version>0.1.0</version> </dependency> <dependency> <groupId>com.cloudreach</groupId> <artifactId>AwesomeApp</artifactId> <type>jar</type> <version>0.1.0</version> </dependency>
The rest of the pom.xml file is the same as the main pom.xml file. When you execute the install target, it installs the application to be tested and the test application, and will automatically run all of the tests in the package. If you do not want this to happen and merely want the tests to be installed, then execute maven using the maven.test.skip argument set to true, i.e.:
You can then run your tests using the adb shell am instrument command (see here for details).
Our Github repository contains the entire project.
mvn install -Dmaven.test.skip=trueYou can then run your tests using the adb shell am instrument command (see here for details).
Our Github repository contains the entire project.
