Aplicações Android com seed

Este guia descreve como usar a plataforma e o framework Android atuais de seed. O suporte implementado combina uma biblioteca seed (mobile), um host Android baseado em Gradle/Kotlin/CMake e um bridge JNI que carrega a biblioteca nativa seed.

Estado atual: Android é a plataforma móvel ativa. O framework fornece uma superfície Canvas retida por comandos de frame, entrada básica, ciclo de vida, insets, métricas de tela e empacotamento multi-ABI. Ainda não é um toolkit completo de widgets.

O que é suportado

Não são declarações de suporte atual: catálogo completo de widgets, TalkBack físico, seleção/composição completa de IME, text shaping, hot reload, matriz geral de dispositivos, publicação em loja ou prontidão de produção.

Arquitetura

seed app (.sd)
    │  integrated build: .so + .a per ABI
    ▼
libseed_app.so ─► JNI bridge (preferred packaged dependency)
libseed_app.a  ─► separate static artifact
    │  C ABI
    ▼
JNI bridge + SeedSurfaceView
    │
    ▼
Activity Android / Canvas / adb / Gradle

A aplicação seed não substitui ainda a Activity. O host Android continua responsável por processo, janela, Canvas, JNI e integração com o SDK. A biblioteca mobile fornece os imports portáveis do slice implementado e deve ser usada apenas com um alvo Android.

Requisitos

É necessário ter:

O instalador oficial do projeto usa $HOME/Android/Sdk, não requer sudo e instala o baseline completo:

./seed mobile android setup
./seed mobile android doctor

Opções úteis:

./seed mobile android setup --sdk "$HOME/Android/Sdk" --start
./seed mobile android setup --skip-avd
./seed mobile android setup --dry-run

Use --api, --abi, --image-flavor, --ndk, --avd e --device para personalizar a instalação. --skip-gradle deixa a instalação do Gradle sob responsabilidade do usuário. Para usar um NDK diferente do baseline, declare explicitamente SEED_ANDROID_NDK_REVISION e confirme a compatibilidade com o projeto.

O diagnóstico deve mostrar Android 37.0, build-tools 37.0.0, Gradle 9.5.0, platform-tools, NDK e os sysroots API 21 de aarch64-linux-android e x86_64-linux-android:

./seed mobile android doctor

Tutorial 1: primeira aplicação

Crie app.sd:

fn main() -> i64 {
    0
}

pub fn seed_mobile_entry() -> i64 {
    0
}

Crie um host copiando o template versionado:

cp -R seed/compiler/llvm/mobile/android my-android

Compile as duas ABIs, faça o staging e gere o APK:

./seed mobile android build app.sd my-android \
  --package-name seed_app \
  --application-id com.example.seedapp \
  --package

O comando produz libseed_app.so e libseed_app.a para arm64-v8a e x86_64, copia os artefatos para my-android/app/src/main/jniLibs/, liga o shared object seed como dependência empacotada do bridge JNI e executa assembleDebug; o archive permanece disponível separadamente para consumidores nativos. O arquivo my-android/seed-android.properties registra o nome nativo e o application ID usados pelos comandos seguintes.

Tutorial 2: desenhar uma tela

Importe a biblioteca Android mobile no código do aplicativo. Um exemplo completo está em compiler/llvm/tests/mobile/android_framework.sd. O callback de frame recebe largura, altura e tempo em nanossegundos:

use "mobile"

pub fn seed_mobile_entry() -> i64 { 0 }

pub fn seed_mobile_on_frame(width: i32, height: i32, time_nanos: i64) {
    mobile.frame_clear()
    mobile.frame_color(4278190335 as i64) // ARGB: opaque blue
    mobile.frame_rect(1000, 1000, 2000, 2000, 4294902015 as i64)
    mobile.frame_text("Hello, Android", 1500, 4500, 2400, 4294967295 as i64)
}

Coordenadas e tamanho de fonte são milipixels. As cores são inteiros ARGB. O host limita cada frame a 256 retângulos, 32 itens de texto, 128 nós semânticos e 256 bytes UTF-8 por rótulo/item. O texto é desenhado pelo Canvas Android; escolha de fonte e shaping avançado ainda não fazem parte do contrato.

Tutorial 3: consumir toque, teclado e IME

A biblioteca mantém uma fila limitada de 128 eventos. Leia os campos antes de remover o evento:

use "mobile"

fn read_input() {
    while mobile.input_available() {
        let kind = mobile.input_kind()
        let a = mobile.input_a()
        let b = mobile.input_b()
        let c = mobile.input_c()

        // kind 1: touch; a=action, b=x in millipixels, c=y in millipixels.
        // kind 2: key; a=action, b=Android key code, c=Unicode code point.
        // kind 3: text committed by the IME.
        if kind == 3 {
            let length = mobile.input_text_length()
            // input_text_byte(index) exposes the committed text's UTF-8 bytes.
            let _ = length
        }
        mobile.input_pop()
    }
}

input_pop deve ser chamado uma vez por evento. Se produtores ultrapassarem a capacidade, a fila conserva os eventos mais recentes. Seleção e região de composição ainda não são expostas.

Tutorial 4: ciclo de vida e recursos

Defina apenas os hooks necessários. Eles são executados na thread da Activity e não devem bloquear, reter objetos JNI nem criar tarefas sem grupo estruturado:

pub fn seed_mobile_on_create() {}
pub fn seed_mobile_on_start() {}
pub fn seed_mobile_on_resume() {}
pub fn seed_mobile_on_pause() {}
pub fn seed_mobile_on_stop() {}
pub fn seed_mobile_on_destroy() {}

seed_mobile_entry é protegido contra execução repetida no mesmo processo; recriar a Activity não reinicializa a aplicação. Destruição cancela trabalho pendente no bridge; morte do processo é a fronteira final de limpeza.

Aplicações com recursos persistentes entre frames podem exportar seed_mobile_context_create, seed_mobile_context_on_event, seed_mobile_context_on_frame e seed_mobile_context_destroy. O host mantém no máximo um contexto por geração da Activity, deixa de encaminhar callbacks quando essa geração é invalidada e destrói o contexto exatamente uma vez. Os hooks legados continuam sendo o fallback.

Use mobile.window_focused() para pausar trabalho visual durante perda de foco e mobile.memory_pressure() para liberar caches após onTrimMemory. Não trate esses sinais como garantia de que o processo continuará vivo.

Tutorial 5: layout, safe area e armazenamento

Insets são pixels físicos: 0=left, 1=top, 2=right, 3=bottom.

use "mobile"

fn content_top() -> i64 {
    mobile.window_inset(1)
}

fn text_size() -> i64 {
    // scaled_density_milli is the font density multiplied by 1000.
    16000 * mobile.scaled_density_milli() / 1000
}

mobile.density_dpi() e mobile.scaled_density_milli() permitem adaptar layout e tipografia. mobile.app_files_* e mobile.app_cache_* retornam views limitadas dos caminhos privados fornecidos pela Activity; use-os para armazenamento interno e cache. Não há acesso automático a armazenamento externo. A permissão de rede declarada pelo template cobre o slice atual de net, mas permissões adicionais continuam sendo responsabilidade do host.

Fluxos de build

Build manual

seed --emit-shared libseed_app.so \
  --target android-arm64 --runtime-profile android \
  --sysroot "$ANDROID_NDK_SYSROOT" --soname libseed_app.so app.sd

seed --emit-shared libseed_app.so \
  --target android-x86_64 --runtime-profile android \
  --sysroot "$ANDROID_NDK_SYSROOT" --soname libseed_app.so app.sd

./seed mobile android stage my-android arm64.so x86_64.so \
  seed_app com.example.seedapp
./seed mobile android package my-android

Build integrado

./seed mobile android build app.sd my-android \
  --ndk "$ANDROID_NDK_HOME" \
  --package-name seed_app \
  --application-id com.example.seedapp

build procura o NDK em ANDROID_NDK_HOME quando --ndk não é informado e gera pares shared/static para as duas ABIs; o host prefere o shared object empacotado e conserva o archive como artefato separado. stage continua aceitando bibliotecas ELF shared, valida os artefatos e rejeita application IDs inválidos. O package name nativo não pode conter / ou ...

Se o pacote Seed que contém app.sd tiver uma pasta assets/, o build integrado também copia seus arquivos regulares e planos para o host Android. Nomes ficam limitados a letras e dígitos ASCII, ., _ e -; subpastas, arquivos ocultos e colisões com o probe do host são rejeitados. Na inicialização, o host lê o índice gerado seed-mobile-assets.list e extrai exatamente esses arquivos — no máximo 256 e 64 MiB — para a raiz privada somente-leitura já exposta ao Seed. Assets de framework/overlay que o AssetManager também enumera não entram no pacote Seed. Assim, o mesmo manifesto e os mesmos ativos de imagem, fonte, WAV e Ogg podem acompanhar desktop e Android sem lógica de cópia específica do aplicativo.

Dependências diretas de audio-vorbis e grove-game-assets fazem o build integrado incluir os backends stb correspondentes nas duas ABIs. O bridge JNI fornece o contrato de áudio compartilhado sobre OpenSL ES, com streams limitados, handles com geração, fila PCM 48 kHz estéreo, consulta de bytes pendentes, pausa/retomada, limpeza e fechamento exato.

Debug, release e execução

./seed mobile android package my-android
./seed mobile android package my-android --release
./seed mobile android run my-android
./seed mobile android run my-android --release
./seed mobile android run my-android --serial emulator-5554
./seed mobile android doctor --serial ZF524P7D2T

run empacota, instala, força a parada da instância anterior e executa .MainActivity via adb, exigindo Status: ok. ANDROID_SERIAL também pode selecionar o dispositivo.

Para iniciar o AVD instalado pelo setup:

emulator -avd seed-api37 -no-snapshot
adb wait-for-device
./seed mobile android run my-android

O nome real do AVD pode ser consultado com avdmanager list avd.

Matriz de AVDs

A matriz implementada mantém as system-images instaladas e cria estado de AVD efêmero por execução. Cada linha usa ANDROID_AVD_HOME temporário, inicialização sem snapshot e dados limpos; depois dos testes, encerra e exclui apenas o AVD. Imagens do SDK, NDK, Gradle e caches de compilação permanecem instalados. Staging nativo e Gradle rodam em uma cópia temporária do host; a matriz não reescreve seed-android.properties, jniLibs ou outputs no projeto original.

O smoke rápido declara phone AVDs ARM64 nas APIs 21, 29 e 37. A suite de release usa a API 37 estável com páginas de 16 KB para tablet, perfil foldable, memória limitada e oito ciclos de lifecycle. Não há tier nightly nem requisito de execução x86_64; android-x86_64 permanece coberto apenas pelos contratos de compilação/link.

O runner constrói o APK uma vez e o reutiliza. Cada linha valida boot, doctor --serial, install/cold launch, acessibilidade, touch/key/IME básico, insets/density/focus, lifecycle/rotação, Logcat e shutdown. A suite de release repete lifecycle conforme a linha e injeta RUNNING_CRITICAL no cenário de memória limitada. Falhas preservam configuração, Logcat, dumpsys, gfxinfo, meminfo, árvore de acessibilidade e screenshot em compiler/llvm/build/android-avd-matrix/.

make -C seed/compiler/llvm android-avd-matrix-check
make -C seed/compiler/llvm android-avd-smoke
make -C seed/compiler/llvm android-avd-release
make -C seed/compiler/llvm game-seed-garden-android-avd-check
SEED_ANDROID_AVD_API=37.0 \
  SEED_ANDROID_AVD_RUNNER_POLICY=required \
  make -C seed/compiler/llvm android-avd-smoke

A política auto (default) registra SKIP para ferramentas ou imagens ausentes; required falha; off desativa execução. --install-images, na chamada direta a scripts/android_avd_matrix.sh, instala imagens selecionadas. SEED_ANDROID_ADB_TIMEOUT e SEED_ANDROID_AVD_BOOT_TIMEOUT limitam chamadas adb e boot; SEED_ANDROID_AVD_VERBOSE=true preserva saída detalhada do emulador. Os três smokes e os quatro cenários de release ARM64 passam no Apple M4.

O contrato Garden usa a mesma infraestrutura efêmera. Ele valida as 64 células semânticas em portrait e landscape durante oito rotações, movimento com animação reduzida, pause/resume, remoção da sessão de áudio no teardown, startup, frames/jank e retenção de PSS. Essas medições são baseline de AVD; não substituem LeakSanitizer Android nem desempenho, temperatura ou latência em dispositivo físico.

Essa matriz amplia somente a evidência em emulator. O Motorola registrado em ANDROID_TARGET_BASELINE.md permanece todo o claim físico. A política completa e a ordem de implementação estão em roadmap-mobile.md.

Testes e validação

Valide contratos sem SDK completo com:

make -C seed/compiler/llvm android-target-test
make -C seed/compiler/llvm android-host-check
make -C seed/compiler/llvm android-package-check
make -C seed/compiler/llvm android-build-check
make -C seed/compiler/llvm android-run-check
make -C seed/compiler/llvm android-doctor-check

Para uma validação real, execute doctor, faça build, instale em AVD ou dispositivo e rode run. O baseline de evidências está em ANDROID_TARGET_BASELINE.md. doctor --serial <adb-serial> também valida modelo, API, ABI, patch de segurança e estado de autorização do dispositivo selecionado.

Diagnóstico de problemas

Limites e próximos passos

O bridge atual é uma base operacional, não uma promessa de compatibilidade com todo o Android SDK. Catálogo de widgets, layout retido, navegação, editor de texto completo, TalkBack físico, plugins estáveis, inspector, profiler, hot reload, matriz ampla de dispositivos, publicação em loja e assinatura de produção permanecem no roadmap. iOS está ativo no simulador através do compilador, runtime, host Swift e XCFramework; dispositivo físico, provisioning, assinatura e loja permanecem abertos.

Referências