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
Activitycalls
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.xmlres/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-XXbuild-tools;X.Y.Zplatform-tools(foradb)
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.
aapt2 link
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.xmlagainstandroid.jargenerates
R.javacreates
resources.arscproduces 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
kotlinchere instead ofjavac.
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:
d8does not create directoriesoutput 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.xmlresources.arscres/classes.dex
Critical rule
classes.dexmust 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
