Android Platform Guide

The Android backend ships an end-to-end pipeline that turns an IDE project into a signed APK running on a real Android emulator or device, with native android.widget.* controls. The same Harbour source that drives the Windows, macOS and Linux desktops now targets Android too — no WebView, no skinned UI, no runtime dependency.

Backend Statistics
StatusIteration 1 — available now
LanguageC (JNI) + Java
Native APIandroid.widget.* via JNI
Min SDKAPI 24 (Android 7.0 Nougat)
Target SDKAPI 34 (Android 14)
ABIarm64-v8a (64-bit ARM)
ToolchainNDK r26d · SDK 34 · JDK 17
Validated onPixel 5 emulator / Android 14
⚠️ In development. Iteration 1 wires UI_FormNew, UI_LabelNew, UI_ButtonNew, UI_EditNew, UI_SetText, UI_GetText and UI_OnClick. More controls (ComboBox, ListView, CheckBox, RadioButton, DatePicker…) land in subsequent iterations. See Roadmap below.

Architecture Overview

Android is HarbourBuilder's 4th native GUI backend. The same classes.prg that every other platform uses dispatches to android_core.c instead of the Win32 / Cocoa / GTK3 bridge, and the JNI calls turn into real Android widgets added to a FrameLayout inside MainActivity.

graph TB A["Harbour .prg
DEFINE FORM ... BUTTON ..."] --> B["classes.prg
UI_FormNew, UI_ButtonNew, ..."] B --> C["android_core.c
HB_FUNC + JNI bridge"] C -->|JNI CallVoidMethod| D["MainActivity.java
FrameLayout host"] D --> E["android.widget.TextView"] D --> F["android.widget.Button"] D --> G["android.widget.EditText"] F -->|onClick| H["nativeOnClick(id)"] H -->|hb_evalBlock0| B style A fill:#8b5cf6,stroke:#7c3aed,color:#fff style B fill:#f59e0b,stroke:#d97706,color:#0d1117 style C fill:#34c759,stroke:#28a745,color:#0d1117 style D fill:#34c759,stroke:#28a745,color:#0d1117 style E fill:#3ddc84,stroke:#1a8d4d,color:#0d1117 style F fill:#3ddc84,stroke:#1a8d4d,color:#0d1117 style G fill:#3ddc84,stroke:#1a8d4d,color:#0d1117 style H fill:#34c759,stroke:#28a745,color:#0d1117

Every HarbourBuilder backend implements the same family of UI_* HB_FUNCs; the Android one lives in source/backends/android/android_core.c and mirrors, HB_FUNC-for-HB_FUNC, the Win32 bridge (source/cpp/hbbridge.cpp) and the Cocoa bridge (source/backends/cocoa/cocoa_core.m).

Build Pipeline

The Run → Run on Android… menu item drives an 8-stage pipeline that produces a signed APK ready to install. The same pipeline is available as a standalone script at source/backends/android/build-apk-gui.sh.

graph LR A[".prg source"] --> B["1. harbour.exe
prg → .c"] B --> C["2. clang
C → .o"] C --> D["3. clang --shared
libapp.so"] D --> E["4. aapt2 compile
resources"] E --> F["5. aapt2 link
base.apk"] F --> G["6. javac
MainActivity.class"] G --> H["7. d8
classes.dex"] H --> I["8. zipalign + apksigner
signed APK"] style I fill:#34c759,stroke:#28a745,color:#0d1117
  1. harbour.exe — host Harbour compiler transpiles .prg to portable C.
  2. clang (NDK) — cross-compiles C to aarch64-linux-android24 object files.
  3. clang --shared — links libapp.so with the full harbour/core static library set (hbvm, hbrtl, hblang, hbcpage, hbrdd, hbmacro, hbpp, hbcommon, hbpcre, hbzlib, hbnulrdd, hbdebug, hbcplr, rddntx/cdx/fpt/nsx, gtstd/trm/cgi/pca, hbsix, hbhsx).
  4. aapt2 compile — compiles Android resources (res/).
  5. aapt2 link — links resources + manifest into base.apk; emits R.java.
  6. javac — compiles MainActivity.java against the Android SDK.
  7. d8 — dex-compiles .class files to classes.dex.
  8. zipalign + apksigner — aligns and v2-signs the APK with a debug keystore.

UI_* API on Android

The Harbour-callable surface exposed by the Android backend. All coordinates are in form-designer pixels, identical to Win32/Cocoa/GTK3 — see density handling.

HB_FUNCSignatureMaps to
UI_FormNew(cTitle, nW, nH) → hFormMainActivity.setTitle() — the Activity is the form
UI_LabelNew(hForm, cText, x, y, w, h) → hCtrlandroid.widget.TextView
UI_ButtonNew(hForm, cText, x, y, w, h) → hCtrlandroid.widget.Button
UI_EditNew(hForm, cText, x, y, w, h) → hCtrlandroid.widget.EditText
UI_SetText(hCtrl, cText)TextView.setText()
UI_GetText(hCtrl) → cTextTextView.getText()
UI_OnClick(hCtrl, bBlock)stores the codeblock for dispatch from nativeOnClick
UI_FormRun(hForm)no-op (Android Activity owns the event loop)

JNI Bridge

Each UI_* HB_FUNC obtains the JNIEnv of the calling thread and invokes a cached method id on the MainActivity global reference. The Java side runs the actual widget creation on the UI thread with runOnUiThread. Clicks travel the opposite direction: Button.setOnClickListener → nativeOnClick(controlId) → hb_evalBlock0.

// android_core.c — UI_ButtonNew dispatch
HB_FUNC( UI_BUTTONNEW )
{
    int id = create_widget( m_createButton,
                             HB_ISCHAR(2) ? hb_parc(2) : "",
                             hb_parni(3), hb_parni(4),
                             hb_parni(5), hb_parni(6) );
    hb_retni( id );
}

// Java called back from native:
public void createButton( final int id, final String text,
                          final int x, final int y, final int w, final int h ) {
    runOnUiThread( new Runnable() { public void run() {
        Button b = new Button( MainActivity.this );
        b.setText( text );
        b.setOnClickListener( v -> nativeOnClick( id ) );
        root.addView( b, lp( x, y, w, h ) );
        ctrls.put( id, b );
    }});
}

Control handles are small integers (1..255) returned by each UI_*New. The Java side keys a HashMap<Integer, View>; the C side keeps a parallel PHB_ITEM g_click_handlers[256] holding the Harbour codeblocks attached via UI_OnClick.

Density-aware Coordinates

Form-designer coordinates are 1:1 with Windows desktop pixels. On Android the same pixel values scale with DisplayMetrics.density so a 100-px button looks consistent on mdpi (×1.0), hdpi (×1.5), xhdpi (×2.0), xxhdpi (×3.0) and xxxhdpi (×4.0) screens.

// MainActivity.java — density multiplier
private int dp( int formPx ) { return Math.round( formPx * density ); }

private FrameLayout.LayoutParams lp( int x, int y, int w, int h ) {
    FrameLayout.LayoutParams p = new FrameLayout.LayoutParams( dp(w), dp(h) );
    p.leftMargin = dp(x);
    p.topMargin  = dp(y);
    return p;
}

Event Loop (inverted)

On Win32, Cocoa and GTK the Harbour oForm:Run() call blocks on a message pump. On Android the OS owns the event loop: UI_FormRun() is a no-op. The Activity's onCreate() calls nativeInit(), which starts the Harbour VM and invokes Main()Main() creates the widgets via UI_*New and returns. From then on the Activity lifecycle drives everything; user interactions fire nativeOnClick, which dispatches the stored codeblock.

Running from the IDE

On Windows the IDE adds a Run → Run on Android… menu item. Clicking it:

  1. Saves the active project (SaveActiveFormCode).
  2. Writes Project1.prg content to the Android build source directory.
  3. Opens a progress dialog and invokes build-apk.sh via Git Bash.
  4. On failure: shows the full build-apk.log in a build-error dialog.
  5. On success: launches run-on-emulator.sh in the background — boots the AVD if needed, installs the APK, launches the activity, tails logcat to a log file.
// hbbuilder_win.prg — menu wiring
DEFINE POPUP oRun PROMPT "&Run" OF oIDE
   MENUITEM "&Run"              OF oRun ACTION TBRun()
   MENUITEM "&Debug"            OF oRun ACTION TBDebugRun()
   MENUSEPARATOR OF oRun
   MENUITEM "Run on &Android..." OF oRun ACTION TBRunAndroid()

Toolchain Requirements

The Android target depends on four external toolchains. The Setup Wizard (Run → Android Setup Wizard…) downloads and installs them automatically in a dedicated terminal window, so on a fresh machine you just click and wait. Paths below are the ones the wizard (and the build scripts) use.

ComponentVersionDefault path (Windows)
Android NDKr26dC:\Android\android-ndk-r26d\
Android SDKplatform 34 · build-tools 34.0.0C:\Android\Sdk\
JDK17 (Temurin, portable)C:\JDK17\jdk-17.0.13+11\
Host Harbour3.2.0devC:\harbour\bin\win\bcc\harbour.exe
Git Bashany recentC:\Program Files\Git\bin\bash.exe
AVDPixel 5 · android-34 · google_apis · x86_64named HarbourBuilderAVD

Total disk footprint of the toolchain is ≈4 GB. Installs are self-contained — no registry entries, no PATH changes: the build scripts export PATH internally.

Setup Wizard

Menu Run → Android Setup Wizard… opens a dialog that reports every toolchain component as OK or MISSING. If anything is missing, a single Yes launches source/backends/android/setup-android-toolchain.sh in a new terminal window. The script is idempotent — it detects what is already in place and downloads only the gaps:

ComponentSizeSource
JDK 17 Temurin~175 MBAdoptium GitHub Releases
Android NDK r26d~1.5 GBdl.google.com
SDK cmdline-tools~100 MBdl.google.com
platforms;android-34, build-tools;34.0.0, platform-tools, emulator, system-image x86_64 ~1.2 GBvia sdkmanager
AVD HarbourBuilderAVDvia avdmanager
Harbour-for-Android libs3.6 MBreleases/harbour-android-arm64-v8a.zip (shipped in repo)

The script pipes yes into sdkmanager --licenses so the install is uninterrupted, and auto-applies the NDK ar/ranlib/strip tweak that Harbour's mk files require. Total ~2.8 GB download on a blank machine. Git Bash is the only prerequisite the wizard cannot install for you.

Hello Android — full example

The source source/backends/android/hello_gui.prg uses the Android-backed UI_* API directly:

PROCEDURE Main()

   LOCAL hForm, hLabel, hEdit, hBtn

   hForm  := UI_FormNew( "Hello Android", 400, 600 )
   hLabel := UI_LabelNew(  hForm, "Type your name:", 20, 20,  300, 30 )
   hEdit  := UI_EditNew(   hForm, "",                20, 60,  300, 50 )
   hBtn   := UI_ButtonNew( hForm, "Greet",           20, 130, 300, 50 )

   UI_OnClick( hBtn, ;
      {|| UI_SetText( hLabel, "Hello, " + UI_GetText( hEdit ) + " !" ) } )

   UI_FormRun( hForm )   // no-op on Android; Activity owns the loop

RETURN

Build and deploy:

# build the APK
bash source/backends/android/build-apk-gui.sh

# install + launch on the emulator
adb install -r C:/HarbourAndroid/apk-gui/harbour-gui.apk
adb shell am start -n com.harbour.builder/.MainActivity

Known Limitations

Roadmap

IterationDeliverables
1 ✅APK pipeline, JNI bridge, Form/Label/Button/Edit, density scaling, click dispatch, validated on emulator
1b ✅IDE form-designer → Android code generator (visual controls emit UI_* calls), METHOD handler translation
1c ✅Form/control colors + fonts carry over (UI_SetFormColor, UI_SetCtrlColor, UI_SetCtrlFont)
6 ✅Setup Wizard — Run → Android Setup Wizard… downloads and installs JDK/NDK/SDK/AVD and extracts the shipped Harbour libs
2CheckBox, RadioButton, ComboBox (Spinner), ListView, DatePicker, TimePicker, ProgressBar, Slider (SeekBar)
3Menu (ActionBar), Dialog (AlertDialog), Toast, MessageBox parity
4Image / Bitmap rendering, resource packaging, per-density icon sets
5Embedded adb logcat panel in the IDE + live build stream + device picker (AVD / USB / Wi-Fi ADB)
6bSetup Wizard port to macOS and Linux hosts
7Release keystore manager + AAB export + Play Console upload helper
8Mobile-specific controls: TSwitch, TSegmentedControl, TFloatingActionButton, TSwipeView, TQRScanner, TCamera, TMapView

Source Files

FilePurpose
source/backends/android/android_core.cJNI bridge and UI_* HB_FUNCs
source/backends/android/java/com/harbour/builder/MainActivity.javaprogrammatic FrameLayout host, click dispatch
source/backends/android/AndroidManifest.xmlpackage metadata, min/target SDK
source/backends/android/res/values/strings.xmlminimal resource bundle
source/backends/android/build-apk-gui.sh8-stage build + sign pipeline
source/backends/android/hello_gui.prgdemo PRG proving the pipeline end-to-end

On This Page

Architecture Overview Build Pipeline UI_* API on Android JNI Bridge Density-aware Coordinates Event Loop (inverted) Running from the IDE Toolchain Requirements Hello Android — example Known Limitations Roadmap Source Files