← Articoli

Scrivere app Android in Ruby nel 2026: JRuby 10 e Ruboto

Ruboto permette di scrivere app Android in Ruby. Esegue JRuby sul telefono, e le vostre activity sono classi Ruby che chiamano direttamente le API Android. Il progetto si era fermato: l’ultimo commit upstream risale a maggio 2023 e punta a JRuby 9.4.

Volevo riaverlo, quindi ne ho fatto un fork: github.com/Largo/ruboto. Il fork esegue JRuby 10.1 (compatibile con Ruby 3.4) da Android 6.0 fino ad Android 16, su telefoni, tablet e smartwatch Wear OS. Non serve Android Studio. Servono un JDK, l’Android SDK e Ruby.

La prima vera app che ci ho costruito gira su una cornice digitale Frameo. Ne parlo più avanti.

Avvio rapido

Servono JDK 17 o successivo, l’Android SDK (impostate ANDROID_HOME), Gradle e Ruby.

git clone https://github.com/Largo/ruboto.git
cd ruboto && bundle install && cd ..

# Generare un'app. Scarica anche i jar JRuby più recenti in libs/.
ruby -Iruboto/lib ruboto/bin/ruboto gen app --package org.example.todo --name Todo --path todo

# Usare la lista todo di esempio come app
cp ruboto/assets/samples/todo_activity.rb todo/app/src/main/resources/todo_activity.rb

cd todo && gradle assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk

Eseguite ruboto gen app con ruby semplice, non con bundle exec. Bundler nasconde la gem jruby-jars, e allora nessun JRuby viene copiato nell’app.

L’intera app è un unico file Ruby in app/src/main/resources. Modificatelo, ricompilate, reinstallate. Questo è il ciclo di sviluppo.

Com’è il codice

Un’activity è una classe Ruby. ruboto/widget offre un piccolo DSL per il layout:

require 'ruboto/widget'
require 'ruboto/toast'

ruboto_import_widgets :Button, :EditText, :LinearLayout, :ListView

class TodoActivity
  def onCreate(bundle)
    super
    set_title 'Ruboto Todo'
    @todos = []

    self.content_view =
        linear_layout orientation: :vertical, fits_system_windows: true do
          @new_todo = edit_text hint: 'What needs doing?'
          button text: 'Add', on_click_listener: proc { add_todo }
          @list = list_view list: @todos.dup
        end
  end

  def add_todo
    @todos << @new_todo.text.to_s
    @list.reload_list(@todos.dup)
    toast 'Added'
  end
end

Potete anche fare a meno del DSL e chiamare le classi Android come farebbe Java: java_import 'android.widget.TextView', poi TextView.new(self). Ogni API Android è disponibile, non c’è nessuno strato di wrapper da aspettare.

Cosa ho dovuto sistemare

JRuby 10 è scritto per una JVM moderna. Il runtime di Android, ART, non lo è. Ruboto riscrive il jar di JRuby mentre lo installa nella vostra app, usando la libreria ASM già inclusa in JRuby. I punti principali:

Le gem con estensioni C non funzionano, e nemmeno openssl. Derivare una classe Java in Ruby (class MyList < java.util.ArrayList) richiede ancora la generazione di classi a runtime e fallisce. Per HTTPS, java.net.HttpURLConnection funziona bene da Ruby.

Ogni modifica è testata con test JVM che non richiedono l’Android SDK, e con uno smoke test sul dispositivo con 65 verifiche su funzionalità Ruby, stdlib, bundled gem, socket, net/http e integrazione Java. Passa sugli emulatori Android 13 e Android 6.

La cornice Frameo

Le cornici Frameo sono economiche cornici digitali WiFi. La mia ha uno schermo 1920×1200, un Rockchip RK312x a 32 bit con quattro core Cortex-A7, 1 GB di RAM e Android 6.0.1. Esegue l’app Frameo e nient’altro. Ma ha un interruttore nascosto: Impostazioni › Info › Programma beta, poi accesso ADB. Da lì adb install funziona.

Volevo una cornice che non avesse bisogno del cloud Frameo. Così l’ho scritta io, in Ruby, circa 1300 righe:

Il web server è di circa 160 righe di Ruby sopra java.net.ServerSocket. Serve una connessione alla volta e scrive i caricamenti direttamente su disco, perché 1 GB di RAM è condiviso con l’app Frameo. Le foto vengono decodificate con BitmapFactory di Android su un executor in background, poi passate al thread UI con un messaggio Handler:

@handler = Handler.new(Looper.getMainLooper, proc { |message| handle_message(message) })
@decoder = java.util.concurrent.Executors.newSingleThreadExecutor

def display(name)
  path = PhotoFrame.store.path(name)
  @decoder.execute(proc do
    bitmap = decode(path)
    post_ui(:fade_to, [name, bitmap]) if bitmap
  end)
end

Il firmware Frameo non permette alle app di avviarsi da sole al boot. La soluzione è registrare l’activity come app della schermata home con un intent filter android.intent.category.HOME. Android chiede allora quale app home usare, e dopo un riavvio la cornice avvia la mia app. “Chiudi cornice” invia un intent home all’app Frameo, che riprende la propria presentazione.

Una volta avviato JRuby, la dissolvenza di foto 1920×1200 e la gestione dei caricamenti sono fluide, anche su questo piccolo chip. L’avvio per ora richiede un po’ di pazienza: su questo hardware JRuby impiega circa 20 secondi dietro lo splash screen, e sotto Android 13 il sostituto di java.lang.invoke passa per la reflection. Per una cornice conta poco, visto che si avvia una volta e poi gira per settimane. Il tempo di avvio è la prossima cosa su cui sto lavorando, quindi aspettatevi miglioramenti a breve.

Questa app è il motivo per cui il fork supporta Android 6. Prima il minimo era Android 13.

Wear OS e IRB

Le stesse app girano su Wear OS. Ci ho costruito un lettore EPUB per il mio orologio, e il fork ha due esempi IRB: uno per telefoni, che può installare gem in puro Ruby dal prompt con gem install, e uno dimensionato per l’orologio, con dettatura, perché digitare Ruby su un orologio è una sofferenza.

Due cose da sapere per Wear OS. Lo swipe indietro non chiama mai il vecchio onBackPressed: impostate android:enableOnBackInvokedCallback="true" e registrate una proc con on_back_invoked_dispatcher. Inoltre i campi Java mantengono il loro nome esatto: display_metrics.widthPixels funziona, width_pixels no. JRuby aggiunge alias snake_case solo per i metodi.

Conviene usarlo?

Per uno strumento personale, un chiosco, un dispositivo di vostra proprietà o un’app da scrivere in un pomeriggio: sì. Scrivete Ruby, avete tutte le API Android e saltate Kotlin, Compose e Android Studio. JRuby su Android è interpretato, il primo avvio su hardware datato richiede ancora un momento (ci sto lavorando) e un APK di debug pesa circa 20 MB. Per un’app con un milione di utenti sceglierei ancora Kotlin.

Il fork è su github.com/Largo/ruboto. Il README contiene l’avvio rapido completo, le note sulle versioni Android più vecchie, sulle gem e sulle callback Java, e gli esempi IRB. Issue e pull request sono benvenute.