A Unixpilled Developer’s Guide to Hello World on Android

Suitable for developers with very little knowledge of the Java/Android ecosystems (like me!)


The Goal

Let’s write an Android app that displays the text “Hello World!”, but without such a massive build-time stack/dependency list.

Ok, what is an app?

For our purposes today, an “app” is an APK file. What an app is once it’s actually installed on the target device is something I do not know, and fortunately irrelevant for today.

So then what is an APK file? It’s an Android PacKage, which is a particular kind of ZIP file (like JARs, Many spreadsheet formats, etc.). The structure of an APK has some variance, and does not seem well documented, but the best resources I can find are, of course, the Wikipedia page, and this Medium article.

To simplify, for the purposes of today, the package contains classes.dex, which is the actual code, AndroidManifest.xml, which contains metadata about the application, META-INF/{MANIFEST.MF,TEST.SF,TEST.RSA}, which hold the signature data for the app, and some other noise (resources.arsc) that isn’t relevant for our test.

Already, that’s a lot of new things to process, so let’s start with what we came for…

The Code

For the easy part, open a file HelloWorld.java, and write

package com.example;

import android.widget.TextView;
import android.app.Activity;
import android.os.Bundle;

public final class HelloWorld extends Activity
{
    protected @Override void onCreate(final Bundle activityState)
    {
        super.onCreate(activityState);
        final TextView tv = new TextView(HelloWorld.this);
        tv.setText("Hello World!");
        setContentView(tv);
    }
}

Or, to be more modern with Kotlin, open HelloWorld.kt and write:

package com.example;

import android.widget.TextView;
import android.app.Activity;
import android.os.Bundle;

class HelloWorld : Activity()
{
    override fun onCreate(activityState: Bundle?)
    {
        super.onCreate(activityState);
        var tv = TextView(this);
        tv.setText("Hello World!");
        setContentView(tv);
    }
}

(Well, I can see why people are moving to Kotlin)

Now we need to take our source code, and move to the JVM world by compiling it to a “class file”. To do so, use either the Java compiler javac, or the Kotlin compiler kotlinc – depending on which source language you chose – but notice that we have those android.* dependencies. How do we satisfy those? In my case, since I use Arch (btw), I have the package android-platform installed, which provides /opt/android-sdk/platforms/android-${ANDROID_API}/android.jar, where ${ANDROID_API} is 34 for me. Putting it all together, we run:

${COMPILER} -cp ${ANDROID_SDK}/platforms/android-${ANDROID_API}/android.jar -d ./ HelloWorld.${EXT}

Where:

This will create the file com/example/HelloWorld.class (the compiler will create the parent directories as well).

Great! Now we have our executable JVM code! Except… Android doesn’t run the JVM, it runs ART, so we have to translate our Class file to a “Dalvik” executable (because Android used to use a Dalvik runtime, then switched to a different platform while keeping the same executable format, named for the previous system.)

Fine. How do we do that? First, we introduce the next dependency. The Android SDK tools (android-sdk-build-tools on Arch). This provides, among other things, the d8 program, which is a “Dexer”, and the successor to dx, which is still referenced from time to time.

Now that we have the tool, run

d8 --lib ${ANDROID_SDK}/platforms/android-${ANDROID_API}/android.jar --output ./ ./com/example/HelloWorld.class

Which will produce ./classes.dex

[APK]: Android Package [JAR]: Java Archive [JVM]: Java Virtual Machine [JDK]: Java Development Kit [ART]: Android Run-Time [SDK]: Software Development Kit (specifically for Android, in this context)