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.
| Status | Iteration 1 — available now |
|---|---|
| Language | C (JNI) + Java |
| Native API | android.widget.* via JNI |
| Min SDK | API 24 (Android 7.0 Nougat) |
| Target SDK | API 34 (Android 14) |
| ABI | arm64-v8a (64-bit ARM) |
| Toolchain | NDK r26d · SDK 34 · JDK 17 |
| Validated on | Pixel 5 emulator / Android 14 |
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.
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.
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
- harbour.exe — host Harbour compiler transpiles
.prgto portable C. - clang (NDK) — cross-compiles C to
aarch64-linux-android24object files. - clang --shared — links
libapp.sowith the fullharbour/corestatic 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). - aapt2 compile — compiles Android resources (
res/). - aapt2 link — links resources + manifest into
base.apk; emitsR.java. - javac — compiles
MainActivity.javaagainst the Android SDK. - d8 — dex-compiles
.classfiles toclasses.dex. - 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_FUNC | Signature | Maps to |
|---|---|---|
UI_FormNew | (cTitle, nW, nH) → hForm | MainActivity.setTitle() — the Activity is the form |
UI_LabelNew | (hForm, cText, x, y, w, h) → hCtrl | android.widget.TextView |
UI_ButtonNew | (hForm, cText, x, y, w, h) → hCtrl | android.widget.Button |
UI_EditNew | (hForm, cText, x, y, w, h) → hCtrl | android.widget.EditText |
UI_SetText | (hCtrl, cText) | TextView.setText() |
UI_GetText | (hCtrl) → cText | TextView.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:
- Saves the active project (
SaveActiveFormCode). - Writes
Project1.prgcontent to the Android build source directory. - Opens a progress dialog and invokes
build-apk.shvia Git Bash. - On failure: shows the full
build-apk.login a build-error dialog. - On success: launches
run-on-emulator.shin the background — boots the AVD if needed, installs the APK, launches the activity, tailslogcatto 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.
| Component | Version | Default path (Windows) |
|---|---|---|
| Android NDK | r26d | C:\Android\android-ndk-r26d\ |
| Android SDK | platform 34 · build-tools 34.0.0 | C:\Android\Sdk\ |
| JDK | 17 (Temurin, portable) | C:\JDK17\jdk-17.0.13+11\ |
| Host Harbour | 3.2.0dev | C:\harbour\bin\win\bcc\harbour.exe |
| Git Bash | any recent | C:\Program Files\Git\bin\bash.exe |
| AVD | Pixel 5 · android-34 · google_apis · x86_64 | named 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:
| Component | Size | Source |
|---|---|---|
| JDK 17 Temurin | ~175 MB | Adoptium GitHub Releases |
| Android NDK r26d | ~1.5 GB | dl.google.com |
| SDK cmdline-tools | ~100 MB | dl.google.com |
| platforms;android-34, build-tools;34.0.0, platform-tools, emulator, system-image x86_64 | ~1.2 GB | via sdkmanager |
AVD HarbourBuilderAVD | — | via avdmanager |
| Harbour-for-Android libs | 3.6 MB | releases/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
- arm64-v8a only — x86_64 / armv7 builds not enabled yet. All modern devices and the reference AVD are arm64.
- Dynamic
libharbour.sodoes not build on Windows: the link command exceeds the Windows 8 KB command-line limit (Error 87). Per-app static linking is used instead (works on every host). Non-blocking. - Default GT is
gtstd— legacy? "text"prints to logcat, not to the UI. For visible output useUI_SetText()on a label. - IDE form-designer integration — the current menu item sends the raw Project1.prg as-is.
A dedicated Android code generator that emits
UI_*-based code from the visual form is the iteration 1b deliverable. - TPageControl / tabs — Android backend stubs
UI_TabControlNew,UI_SetCtrlOwner,UI_GetCtrlPageas no-ops. Full page support lands with the ViewPager2 binding.
Roadmap
| Iteration | Deliverables |
|---|---|
| 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 |
| 2 | CheckBox, RadioButton, ComboBox (Spinner), ListView, DatePicker, TimePicker, ProgressBar, Slider (SeekBar) |
| 3 | Menu (ActionBar), Dialog (AlertDialog), Toast, MessageBox parity |
| 4 | Image / Bitmap rendering, resource packaging, per-density icon sets |
| 5 | Embedded adb logcat panel in the IDE + live build stream + device picker (AVD / USB / Wi-Fi ADB) |
| 6b | Setup Wizard port to macOS and Linux hosts |
| 7 | Release keystore manager + AAB export + Play Console upload helper |
| 8 | Mobile-specific controls: TSwitch, TSegmentedControl, TFloatingActionButton, TSwipeView, TQRScanner, TCamera, TMapView |
Source Files
| File | Purpose |
|---|---|
source/backends/android/android_core.c | JNI bridge and UI_* HB_FUNCs |
source/backends/android/java/com/harbour/builder/MainActivity.java | programmatic FrameLayout host, click dispatch |
source/backends/android/AndroidManifest.xml | package metadata, min/target SDK |
source/backends/android/res/values/strings.xml | minimal resource bundle |
source/backends/android/build-apk-gui.sh | 8-stage build + sign pipeline |
source/backends/android/hello_gui.prg | demo PRG proving the pipeline end-to-end |