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.
To build and deploy an Android app by hand, using only the SDK command-line tools.
- No IDEs
- No Gradle
And for extra credit:
For this task, I created a simple, single-activity application. When run, it will look like this:
Not a paragon of Android good practices mind you, but just enough to walk us through the major steps of the build:
- One activity
- One drawable resource for the app icon
- One layout resource with an
- One external dependency - Picasso
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:
gen/will contain the generated
out/will contain the products of our build
If you wish to follow along, we need to do some setup beforehand.
Download the latest SDK tools package (24.4.1 as of this writing), unzip it and
$ANDROID_HOME to the root folder. Then launch the SDK Manager using:
Before the SDK is fully usable, we need to download additional components:
- Platform tools
- Build tools
- Android N Preview SDK
- Android N System Image (if you want to test on an emulator)
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
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 complete build consists of 7 distinct steps, described in the following subsections:
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
In our exercise, we need to do it manually. This is the first job for
$ 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
and generates the following source file:
The complete source code now consists of
MainActivity.java and the newly
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.
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 (
This time however, we’re using the
-F switch to create
Now we add our compiled
classes.dex. Note that
aapt is sensitive to relative
classes.dex needs to reside in root of the package. Thus, we perform
this step from within the
$ 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.
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:
- Keystore password:
- Key password:
- Key alias:
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.
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
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
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?
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
- Jack simplifies the compilation process quite a bit. What previously took
multiple steps (
dx, ProGuard) is now taken care of by this single tool.
- Android Studio generates one more class behind the scenes -
BuildConfig.java. For this simple exercise I’ve omitted it.
- The Gradle build system uses a different naming scheme - the package before
zipaligning would be called
handbuilt-unaligned.apkand the final APK would be
handbuilt.apk. This is just a convention and I’m breaking it here. Sue me. (Please don’t sue me).
- A typical build process would also incorporate Jill – Jack’s helper tool for
converting libraries into Jack’s expected
jayceformat. Again, I’ve omitted it in the examples here - as of version 1.2-rc2 Jack seems to accept
picasso-2.5.2.jarjust fine. To speed up consecutive builds, you would want to “pre-jill” both of these just like you would “pre-dex” libraries with the old toolchain.
- Some Java 8 features are now available to Android
developers. If you want to use those, you need to specify
-D jack.java.source.version=8to Jack. Also note that Jack is the only option for using Java 8 as
dxwon’t accept Java 8
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