Android App Continuous Integration with Circle CI

Mark Dappollone
4 min readApr 17, 2017

Continuous integration is an absolute necessity in any software development project that wants to enforce build and testability on an ongoing basis, without forcing developers to run time-consuming manual processes on a regular basis. The name of the game with CI is automation; allowing a machine to run tests and verify builds so that devs can focus on building features and fixing bugs. The problem lies in finding the right solution for your CI needs. Hosted options are convenient, since they alleviate the need for developers to maintain the hardware or software of the CI system itself. Today we’ll consider one popular option in the realm of hosted continuous integration solutions: CircleCI.

Circle CI is the type of solution where users pay for parallelization. That is, you get one VM for free, but if you want to run multiple builds in parallel, it starts to cost money. Another thing to consider when selecting a CI solution is that Circle CI doesn’t offer templated build configurations, like scheduled builds. To run a nightly build on Circle CI, the service offers a parameterized build API, but “Typically a server in your own infrastructure or 3rd party service would be used to trigger these builds.” What that means is, you run a cron on a server in your own environment that triggers a build on Circle CI on the schedule of that cron. In still other words, Circle CI has no schedule-able build configurations built in.

The other problem with services like Circle CI, is that the VMs they provide are updated on their schedule. So if you need a newer version of the Android SDK or Build Tools, you have to write in commands to your build project to make sure they’re there. Luckily, I’ve worked all that (and more) out for you, so you can skip to the good part.

The Build Script

Like other hosted CI solutions, Circle CI works off of a yaml file that you include in the top level of your project where you can tweak everything from how your build runs to the config of the machine it runs on. We’ll go through my script piece by piece.

The first part of my circle.yml file is my machine config:

machine:
environment:
_JAVA_OPTIONS:
"-Xms512m -Xmx1024m"
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'

I ran into increasing instances where my VM was running out of memory trying to build and test my code, so I bumped up the memory for both the JVM heap and the gradle daemon. I recommend just doing this right off the bat.

Next, I tell Circle how to build my project, and what to publish as artifacts:

compile:
override:
- ./gradlew :app:assembleDebug :app:lintDebug
post:
#publish lint artifacts
- mkdir -p $CIRCLE_ARTIFACTS/lint
- mkdir -p $CIRCLE_ARTIFACTS/apk
- mv app/build/outputs/lint* $CIRCLE_ARTIFACTS/lint
- mv app/build/outputs/apk/* $CIRCLE_ARTIFACTS/apk

By default, circle will run the “./gradlew test” command, but as you can see, I’m only having Circle build my debug flavor and run lint on it (we’ll talk about unit testing later). Next, I’ve specified the artifacts I want to publish. All the commands in the yaml script are actually just shell commands, so the easiest way to publish artifacts is to just move them into the artifact directory, which has an environment variable on the VM.

Next, let’s talk about dependencies:

## Customize dependencies
dependencies:
pre:
# Android SDK Build-tools, revision 25.0.2
- if [ ! -d "/usr/local/android-sdk-linux/build-tools/25.0.2" ]; then echo y | android update sdk --no-ui --all --filter "build-tools-25.0.2"; fi
- echo y | android update sdk --no-ui --all --filter "extra-google-m2repository"
cache_directories:
- ~/.gradle
- /usr/local/android-sdk-linux/platforms/android-25
- /usr/local/android-sdk-linux/build-tools/25.0.2
- /usr/local/android-sdk-linux/tools
- /usr/local/android-sdk-linux/extras/android/m2repository

In this block, I’m checking Circle’s VM for the existence of the build tools that my project uses. At the time of this writing, Circle has not updated their default VM’s SDK to include build tools 25.0.2, so I have to make sure to download that via the SDK’s command line updater. After that, I’ve specified some cache directories that I want to keep around across builds. This improves the speed of the build, alleviating the need to re-download everything every time.

And finally, the tests section of the script:

## Customize test commands
test:
override:
- ./gradlew :app:testDebug
post:
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
- find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;

In this section, I tell the system which (if any) tests I want to run, and then what to do with the reports when they’re finished. Circle CI provides some example yaml files for Android builds, but you’d think they would provide some web based switches for you to just enable unit test publishing without complex shell commands.

Conclusion

Circle CI certainly has some benefit — no hardware to maintain and manage, seamless integration with Github, etc., but overall I find it clunky and somewhat difficult to use. Also, it seems to be missing some key features, like scheduled builds and customized triggers, that I would expect from any CI solution. Granted, you can make a lot of those features work with a little creativity and elbow grease, but with plenty of other tools out there that provide that kind of utility out of the box, it hardly seems worth it.

--

--

Mark Dappollone

Director, Mobile Product Engineering at Anywhere Real Estate