Łukasz Adamczak

Łukasz Adamczak

One developer's journeys through code

Jack, Jill & building Android apps by hand

May 31, 2016
  • Android

This seems to be the overarching theme of my posts. I renounce modern IDEs, go command-line and claim it’s good for you. But this time it’s serious.

I’m scared of magic.

Mysterious background processes performing tasks I’m unaware of and don’t understand. IDEs reading my mind and generating code behind my back. “Here, try some”, they’ll say. And I will try some indeed. Heck, I will even like it. But the mystery of it all will make it hard to swallow.

One such mystery for me has been the Android build process. Even outside the IDE, turning Java and XML into APK via Gradle was just enough magic to make me uncomfortable.

Whenever possible, I like to strip away the magic and do things by hand. Even if only as an exercise.

This was one such exercise.

The goal

To build and deploy an Android app by hand, using only the SDK command-line tools.

The rules

And for extra credit:

The project

Result of our hard work

For this task, I created a simple, single-activity application. Not a paragon of Android good practices mind you, but just enough to walk us through the major steps of the build:

For demonstration purposes I’ve adopted a simple, flat project folder structure:

handbuilt-android-project/
├── AndroidManifest.xml
├── gen/
├── lib/
│   └── picasso-2.5.2.jar
├── out/
├── res/
│   ├── drawable-xhdpi/
│   │   └── icon.png
│   └── layout/
│       └── activity_main.xml
└── src/
    └── pl/
        └── czak/
            └── handbuilt/
                └── MainActivity.java

When we’re done:

The complete source code (with a slightly more advanced folder structure and simple dependency management built in) is available on GitHub. For the tl;dr version, just check out the Makefile.

If you wish to follow along, we need to do some setup beforehand.

Prerequisites

Download the latest SDK tools package (24.4.1 as of this writing), unzip it and set $ANDROID_HOME to the root folder. Then launch the SDK Manager using:

$ $ANDROID_HOME/tools/android

Before the SDK is fully usable, we need to download additional components:

I live on the edge ;) so I go with preview versions of everything:

Once everything is installed, make sure the following commands are available in your $PATH:

The Jack compiler is contained in the build tools under $ANDROID_HOME/build-tools/24.0.0-preview/jack.jar. For convenience, I use the following alias to invoke it:

$ alias jack='java -jar "$ANDROID_HOME/build-tools/24.0.0-preview/jack.jar"'

The build

The complete build consists of 7 distinct steps, described in the following subsections:

  1. Generate R.java
  2. Compile
  3. Package
  4. Sign
  5. Zipalign
  6. Upload
  7. Run

Step 1. Generate R.java

In Android Studio, this happens continuously behind the scenes and is perhaps the most magical part of the process. You add a new layout XML and a second later you can reference it in your code as R.layout.activity_main. This is because the IDE regenerates the R.java source file whenever your resources change.

In our exercise, we need to do it manually. This is the first job for aapt:

$ aapt package -f \
               -M AndroidManifest.xml \
               -I "$ANDROID_HOME/platforms/android-N/android.jar" \
               -S res/ \
               -J gen/ \
               -m

This invocation takes our AndroidManifest.xml and the resources under res/ and generates the following source file:

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package pl.czak.handbuilt;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int image=0x7f040000;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
}

Step 2. Compile

The complete source code now consists of MainActivity.java and the newly generated R.java. We can now compile it using Jack:

$ jack --classpath "$ANDROID_HOME/platforms/android-N/android.jar" \
       --import lib/picasso-2.5.2.jar \
       --output-dex out/ \
       src/ gen/

We pass in N’s android.jar for classpath and import the Picasso JAR. We also specify both folders containing our Java files.

As a result, we get out/classes.dex ready for packaging.

Step 3. Package

We’re ready to start building the APK. I split the packaging process into two steps. First, we create the initial package file with the manifest and resources:

$ aapt package -f \
               -M AndroidManifest.xml \
               -I "$ANDROID_HOME/platforms/android-N/android.jar" \
               -S res/ \
               -F out/handbuilt.apk

Once again, this is performed with the Android Asset Packaging Tool (aapt). This time however, we’re using the -F switch to create out/handbuilt.apk.

Now we add our compiled classes.dex. Note that aapt is sensitive to relative paths and classes.dex needs to reside in root of the package. Thus, we perform this step from within the out/ folder:

$ cd out/
$ aapt add handbuilt.apk classes.dex

We’ve provided all the required contents of the APK. Before we install it however, it needs to be signed.

Step 4. Sign

Every Android APK needs to be signed before it can be installed on a device or an emulator. This is true whether you’re just trying out a debug build in development or preparing your final APK for public release. When working in the IDE this is kept out of sight and there’s no mention of any keys until you want to publish your app in Google Play.

For development purposes, the SDK provides a standard debug key in ~/.android/debug.keystore. The key can be used as follows:

With this in mind, we turn to JDK’s jarsigner to perform the task:

$ jarsigner -verbose \
            -keystore ~/.android/debug.keystore \
            -storepass android \
            -keypass android \
            out/handbuilt.apk \
            androiddebugkey

The APK is now ready to upload. However, let’s perform one important optimization first.

Step 5. Zipalign

Zipaligning is a process which takes an APK and ensures that all uncompressed data (e.g. images and resources.arsc) starts with a particular alignment relative to the start of the file. This way they can be mmaped without moving any bytes around. This is an important performance optimization. Luckily, it’s very simple:

$ zipalign -f 4 out/handbuilt.apk out/handbuilt-aligned.apk

Our final APK is handbuilt-aligned.apk.

Step 6. Upload

Now launch an emulator (as an exercise for the reader – do this from the command line as well) and let’s deploy:

$ adb install -r out/handbuilt-aligned.apk

The app is ready to be launched. Sure we could just tap the icon in the drawer. But where’s the fun in that?

Step 7. Run

By now we’re commandline ninjas after all. So let’s launch the app the right way:

$ adb shell am start pl.czak.handbuilt/.MainActivity

Done.

Notes

Summary

Once again, I’m pleased to see the magic behind the Android build process is not that bad. Will I be using make instead of gradle from now on? Most probably not. I’m even starting to miss Android Studio a bit. And it’s not such a scary place anymore.