Android Guía de la Plataforma
El backend de Android incorpora un pipeline completo que convierte un proyecto del IDE en un APK firmado
y en ejecución sobre un emulador o dispositivo real, con controles nativos
android.widget.*. El mismo código Harbour que alimenta Windows, macOS y Linux ahora también
apunta a Android — sin WebView, sin UI pintada, sin dependencias de ejecución.
| Estado | Iteración 1 — disponible ya |
|---|---|
| Lenguaje | C (JNI) + Java |
| API nativa | android.widget.* vía JNI |
| SDK mínimo | API 24 (Android 7.0 Nougat) |
| SDK objetivo | API 34 (Android 14) |
| ABI | arm64-v8a (ARM de 64 bits) |
| Toolchain | NDK r26d · SDK 34 · JDK 17 |
| Validado en | Emulador Pixel 5 / Android 14 |
UI_FormNew,
UI_LabelNew, UI_ButtonNew, UI_EditNew, UI_SetText,
UI_GetText y UI_OnClick. El resto de controles (ComboBox, ListView, CheckBox,
RadioButton, DatePicker…) llega en iteraciones siguientes. Ver Hoja de ruta.
Visión arquitectónica
Android es el 4º backend GUI nativo de HarbourBuilder. El mismo classes.prg que usa cada
plataforma despacha a android_core.c en lugar del bridge Win32/Cocoa/GTK3, y las llamadas JNI
se convierten en widgets Android reales dentro de un FrameLayout de MainActivity.
DEFINE FORM ... BUTTON ..."] --> B["classes.prg
UI_FormNew, UI_ButtonNew, ..."] B --> C["android_core.c
HB_FUNC + puente JNI"] C -->|JNI CallVoidMethod| D["MainActivity.java
Anfitrión FrameLayout"] 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
Cada backend de HarbourBuilder implementa la misma familia de HB_FUNCs UI_*; el de Android
vive en source/backends/android/android_core.c y refleja, HB_FUNC a HB_FUNC, el puente Win32
(source/cpp/hbbridge.cpp) y el puente Cocoa (source/backends/cocoa/cocoa_core.m).
Pipeline de compilación
El menú Run → Run on Android… dispara un pipeline de 8 etapas que produce un APK
firmado listo para instalar. El mismo pipeline está disponible como script independiente en
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
recursos"] E --> F["5. aapt2 link
base.apk"] F --> G["6. javac
MainActivity.class"] G --> H["7. d8
classes.dex"] H --> I["8. zipalign + apksigner
APK firmado"] style I fill:#34c759,stroke:#28a745,color:#0d1117
- harbour.exe — el compilador Harbour anfitrión traduce
.prga C portable. - clang (NDK) — compila cruzado a objetos
aarch64-linux-android24. - clang --shared — enlaza
libapp.socon todas las librerías estáticas deharbour/core(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 — compila los recursos Android (
res/). - aapt2 link — enlaza recursos + manifest en
base.apk; generaR.java. - javac — compila
MainActivity.javacontra el SDK de Android. - d8 — transforma los
.classenclasses.dex. - zipalign + apksigner — alinea y firma v2 el APK con un keystore de depuración.
API UI_* en Android
Superficie invocable desde Harbour expuesta por el backend Android. Todas las coordenadas son en píxeles del diseñador, idénticas a Win32/Cocoa/GTK3 — ver manejo de densidad.
| HB_FUNC | Signatura | Mapea a |
|---|---|---|
UI_FormNew | (cTitle, nW, nH) → hForm | MainActivity.setTitle() — la Activity es el 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) | guarda el codeblock para despacharlo desde nativeOnClick |
UI_FormRun | (hForm) | no-op (la Activity posee el bucle de eventos) |
Puente JNI
Cada HB_FUNC UI_* obtiene el JNIEnv del hilo que llama e invoca un method id cacheado sobre la
referencia global a MainActivity. En Java la creación real del widget se ejecuta en el hilo de UI con
runOnUiThread. Los clicks viajan en sentido inverso:
Button.setOnClickListener → nativeOnClick(controlId) → hb_evalBlock0.
// android_core.c — despacho de UI_ButtonNew 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 llamado desde el código nativo: 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 ); }}); }
Los handles de control son enteros pequeños (1..255) devueltos por cada
UI_*New. En Java un HashMap<Integer, View> los indexa; en C un
PHB_ITEM g_click_handlers[256] paralelo guarda los codeblocks registrados con
UI_OnClick.
Coordenadas conscientes de la densidad
Las coordenadas del diseñador son 1:1 con píxeles de Windows Desktop. En Android esos mismos valores
se multiplican por DisplayMetrics.density, de modo que un botón de 100 px se ve consistente en
pantallas mdpi (×1.0), hdpi (×1.5), xhdpi (×2.0), xxhdpi (×3.0) y xxxhdpi (×4.0).
// MainActivity.java — multiplicador de densidad 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; }
Bucle de eventos (invertido)
En Win32, Cocoa y GTK la llamada oForm:Run() bloquea en una cola de mensajes. En Android
el SO posee el bucle: UI_FormRun() es un no-op. El onCreate() de la
Activity invoca nativeInit(), que arranca la VM de Harbour y llama a Main() —
Main() crea los widgets mediante UI_*New y retorna. A partir de ahí el ciclo de vida
de la Activity dirige todo; las interacciones del usuario disparan nativeOnClick, que despacha
el codeblock almacenado.
Ejecutar desde el IDE
En Windows el IDE añade un menú Run → Run on Android…. Al pulsarlo:
- Guarda el proyecto activo (
SaveActiveFormCode). - Escribe el contenido de
Project1.prgen el directorio fuente del build Android. - Abre un diálogo de progreso e invoca
build-apk.shvía Git Bash. - En caso de fallo: muestra el
build-apk.logcompleto en un diálogo de error. - En caso de éxito: lanza
run-on-emulator.shen segundo plano — arranca el AVD si hace falta, instala el APK, lanza la Activity y guardalogcaten un fichero.
// hbbuilder_win.prg — cableado del menú 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()
Requisitos del toolchain
El target Android depende de cuatro toolchains externos. El Setup Wizard (Run → Android Setup Wizard…) los descarga e instala automáticamente en una ventana terminal propia, así que en una máquina limpia sólo tienes que hacer clic y esperar. Las rutas de abajo son las que usa el wizard (y los scripts de build).
| Componente | Versión | Ruta por defecto (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\ |
| Harbour anfitrión | 3.2.0dev | C:\harbour\bin\win\bcc\harbour.exe |
| Git Bash | cualquiera reciente | C:\Program Files\Git\bin\bash.exe |
| AVD | Pixel 5 · android-34 · google_apis · x86_64 | llamado HarbourBuilderAVD |
Ocupa ≈4 GB de disco. Las instalaciones son autocontenidas — sin entradas de registro ni cambios de
PATH: los scripts exportan PATH internamente.
Setup Wizard
Menú Run → Android Setup Wizard… abre un diálogo que reporta cada componente
como OK o MISSING. Si falta algo, un Yes lanza
source/backends/android/setup-android-toolchain.sh en una terminal nueva. El script es
idempotente — detecta lo que ya está y baja sólo lo que falta:
| Componente | Tamaño | Origen |
|---|---|---|
| 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 (incluido en el repo) |
El script redirige yes a sdkmanager --licenses para que el install no se
pare en prompts de licencia, y aplica automáticamente el tweak NDK
(ar/ranlib/strip) que los mk files de Harbour esperan. Total
~2.8 GB de descarga en una máquina limpia. Git Bash es el único prerequisito que el
wizard no puede instalar por ti.
Hola Android — ejemplo completo
El fichero source/backends/android/hello_gui.prg usa la API UI_* del backend
Android directamente:
PROCEDURE Main() LOCAL hForm, hLabel, hEdit, hBtn hForm := UI_FormNew( "Hola Android", 400, 600 ) hLabel := UI_LabelNew( hForm, "Escribe tu nombre:", 20, 20, 300, 30 ) hEdit := UI_EditNew( hForm, "", 20, 60, 300, 50 ) hBtn := UI_ButtonNew( hForm, "Saludar", 20, 130, 300, 50 ) UI_OnClick( hBtn, ; {|| UI_SetText( hLabel, "Hola, " + UI_GetText( hEdit ) + " !" ) } ) UI_FormRun( hForm ) // no-op en Android; la Activity posee el bucle RETURN
Compilar y desplegar:
# compilar el APK bash source/backends/android/build-apk-gui.sh # instalar y lanzar en el emulador adb install -r C:/HarbourAndroid/apk-gui/harbour-gui.apk adb shell am start -n com.harbour.builder/.MainActivity
Limitaciones conocidas
- Sólo arm64-v8a — los builds x86_64 / armv7 aún no están activados. Todos los dispositivos modernos y el AVD de referencia son arm64.
libharbour.sodinámica no compila en Windows: el comando de link supera el límite de 8 KB de línea de comandos de Windows (Error 87). Se usa enlace estático por aplicación (funciona en cualquier host). No bloqueante.- GT por defecto es
gtstd— el clásico? "texto"va a logcat, no a la UI. Para ver salida visible usaUI_SetText()sobre un label. - Integración con el diseñador del IDE — el menú actual envía el Project1.prg tal
cual. Un generador específico para Android que emita código basado en
UI_*desde el form visual es el entregable de la iteración 1b. - TPageControl / pestañas — el backend Android tiene como no-op
UI_TabControlNew,UI_SetCtrlOwner,UI_GetCtrlPage. El soporte completo llegará con el binding a ViewPager2.
Hoja de ruta
| Iteración | Entregables |
|---|---|
| 1 ✅ | Pipeline APK, puente JNI, Form/Label/Button/Edit, escalado por densidad, despacho de clicks, validado en emulador |
| 1b ✅ | Generador de código Android desde el diseñador (los controles visuales emiten llamadas UI_*), traducción de METHOD handlers |
| 1c ✅ | Colores de form/controles + fuentes viajan al APK (UI_SetFormColor, UI_SetCtrlColor, UI_SetCtrlFont) |
| 6 ✅ | Setup Wizard — Run → Android Setup Wizard… descarga e instala JDK/NDK/SDK/AVD y extrae las libs Harbour precompiladas |
| 2 | CheckBox, RadioButton, ComboBox (Spinner), ListView, DatePicker, TimePicker, ProgressBar, Slider (SeekBar) |
| 3 | Menú (ActionBar), Diálogo (AlertDialog), Toast, paridad MessageBox |
| 4 | Render de Imagen/Bitmap, empaquetado de recursos, sets de iconos por densidad |
| 5 | Panel adb logcat embebido en el IDE + stream de build en vivo + selector de dispositivo (AVD / USB / Wi-Fi ADB) |
| 6b | Port del Setup Wizard a macOS y Linux |
| 7 | Gestor de keystores de release + export AAB + asistente de subida a Play Console |
| 8 | Controles específicos de móvil: TSwitch, TSegmentedControl, TFloatingActionButton, TSwipeView, TQRScanner, TCamera, TMapView |
Ficheros fuente
| Archivo | Propósito |
|---|---|
source/backends/android/android_core.c | puente JNI y HB_FUNCs UI_* |
source/backends/android/java/com/harbour/builder/MainActivity.java | anfitrión FrameLayout programático, despacho de clicks |
source/backends/android/AndroidManifest.xml | metadatos del paquete, SDK mín/obj |
source/backends/android/res/values/strings.xml | paquete mínimo de recursos |
source/backends/android/build-apk-gui.sh | pipeline de 8 etapas + firma |
source/backends/android/hello_gui.prg | PRG demo que prueba el pipeline de extremo a extremo |