Skip to main content

Command Palette

Search for a command to run...

Building an Android App Without Gradle or Android Studio

Published
5 min read
Building an Android App Without Gradle or Android Studio

Understanding the Android Build Process From First Principles

Most Android developers have built hundreds of apps — yet very few have actually built an APK.

They press Run in Android Studio.
Gradle executes.
An app appears on the device.

But what actually happens in between?

In this article, I build an Android app without Gradle and without Android Studio, using only the raw Android command-line tools. Not to replace modern tooling — but to understand what it really does.


Pre-Setup: Files You Need Before Building Anything

This walkthrough assumes a minimal Android project, prepared manually.

Before running any commands, you must already have these files:

1. AndroidManifest.xml

Defines:

  • package name

  • application

  • main activity

  • intent filters

This file is mandatory.
Without it, Android does not recognize anything as an app.

<?xml version="1.0" encoding="utf-8" ?>

<manifest
 xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.helloworld"
    android:versionCode="1"
    android:versionName="1.0" >

<application  android:label="@string/appname"
    >
<activity 
 android:name=".MainActivity"
 android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

<uses-sdk
    android:minSdkVersion="24"
    android:targetSdkVersion="36"/>
</manifest>

2. MainActivity.java

A minimal Java activity, for example:

  • extends Activity

  • calls setContentView(...)

In this video and article:

  • Java is used

  • Kotlin is not used, only mentioned as an alternative

If your app uses Kotlin, kotlinc would replace javac

package com.helloworld;

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

public class MainActivity extends Activity
{
 public void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 }
}

3. Resource files

At minimum:

  • res/layout/main_activity.xml

  • res/values/values.xml

These define:

  • UI layout

  • strings and basic resources

Android is resource-driven. Code alone is not enough.

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

<TextView
    android:id="@+id/tv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Hello world"
     />
</LinearLayout>

<resources>
 <string name="appname">Hello world</string>
</resources>

4. Android SDK + JDK

Installed via:

  • Android command-line tools (sdkmanager)
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
unzip commandlinetools-linux-11076708_latest.zip
cd cmdline-tools
cd bin
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
  • JDK (for javac, keytool)

Required Android SDK components:

  • platforms;android-XX

  • build-tools;X.Y.Z

  • platform-tools (for adb)


Once these files exist, the actual build process can begin.


What an Android App Really Is

At its core, an Android app is just:

  • a ZIP file

  • containing resources

  • containing DEX bytecode

  • signed with a cryptographic key

Everything else is tooling layered on top of that fact.


Step 1: Resources and App Identity (aapt2)

Android must understand resources before code.

./aapt2 compile --dir /mnt/e/android_test/res -o /mnt/e/android_test/build/res/

aapt2 compile

This step:

  • parses XML resources

  • validates them

  • converts them into Android’s binary format

No IDs are assigned yet.
No app exists yet.


This is the most important resource step.

./aapt2 link -I $ANDROID_HOME/platforms/android-36/android.jar --manifest /mnt/e/android_test/AndroidManifest.xml --min-sdk-version 24 --target-sdk-version 36 --java /mnt/e/android_test/build /mnt/e/android_test/build/res/*.flat -o /mnt/e/android_test/build/app-unsigned.ap

aapt2 link:

  • assigns final resource IDs (0x7f…)

  • validates AndroidManifest.xml against android.jar

  • generates R.java

  • creates resources.arsc

  • produces an APK skeleton

After this step:

  • Android recognizes the app

  • Java code can reference resources

  • the app exists without code


Step 2: Compiling Java Code (javac)

Java source code is not executable.

javac -classpath $ANDROID_HOME/platforms/android-36/android.jar -d /mnt/e/android_test/build/classes /mnt/e/android_test/src/com/helloworld/MainActivity.java  /mnt/e/android_test/build/com/helloworld/R.java

javac:

.java → .class

This produces JVM bytecode, not Android bytecode.

At this stage:

  • the code is valid Java

  • Android still cannot run it

Kotlin users would use kotlinc here instead of javac.


Step 3: JVM Bytecode → Android Bytecode (d8)

Android does not run the JVM.

d8 --lib $ANDROID_HOME/platforms/android-36/android.jar --output /mnt/e/android_test/build/dex/classes.zip /mnt/e/android_test/build/classes/com/helloworld/

It runs ART, which executes DEX bytecode.

d8 converts:

.class → classes.dex

Important details:

  • d8 does not create directories

  • output must be an existing directory or a .zip / .jar

This is the moment your code becomes executable on Android.


Step 4: Assembling the APK (zip)

An APK is just a ZIP file with strict rules.

zip -u /mnt/e/android_test/build/app-unsigned.apk /mnt/e/android_test/build/dex/classes.dex

At this stage, we combine:

  • AndroidManifest.xml

  • resources.arsc

  • res/

  • classes.dex

Critical rule

classes.dex must be at the root of the APK

If it ends up inside a directory:

build/dex/classes.dex

Android will fail installation with:

INSTALL_FAILED_INVALID_APK: code is missing

This is a structural error, not a signing or runtime issue.


Step 5: Alignment (zipalign)

zipalign -p 4 /mnt/e/android_test/build/apk/app-unsigned.apk /mnt/e/android_test/build/apk/app-aligned.apk

zipalign:

  • aligns uncompressed data to 4-byte boundaries

  • enables memory-mapped access

  • reduces memory usage and startup time

This step must happen before signing.


Step 6: Signing the App (apksigner)

keytool -genkeypair -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks debug.keystore --ks-key-alias androiddebugkey --out /mnt/e/android_test/build/app.apk /mnt/e/android_test/build/app-aligned.apk

Android will never install an unsigned APK.

Signing provides:

  • integrity

  • identity

  • update safety

Android Studio signs debug apps automatically.
When building manually, you must do it yourself.

After signing, the APK becomes installable.


Installing the App (adb)

Finally:

adb install app.apk

If everything is correct:

  • the app installs

  • launches normally

  • behaves like any other Android app

Because it is one.


What This Process Teaches You

After building an Android app this way:

  • Gradle stops being magic

  • APK structure becomes obvious

  • build errors become mechanical

  • Android internals feel predictable

You stop guessing.
You start reasoning.


Final Thoughts

This process is not about replacing Android Studio.

It is about understanding it.

Once you know how an APK is built from raw tools:

  • abstractions become optional

  • debugging becomes simpler

Android development becomes clearer — not harder.

🎥 Prefer video?
I walk through this entire process step by step in the full video here:
https://youtu.be/GPyYQedY2XE