App sempre attive e modalità Ambient di sistema

Questa guida spiega come rendere la tua app sempre attiva, come reagire alle transizioni dello stato di alimentazione e come gestire il comportamento dell'applicazione per offrire una buona esperienza utente, risparmiando batteria.

Rendere un'app costantemente visibile influisce in modo significativo sulla durata della batteria, quindi valuta l'impatto sul consumo energetico quando aggiungi questa funzionalità.

Concetti principali

Quando un'app Wear OS viene visualizzata a schermo intero, si trova in uno dei due stati di alimentazione:

  • Interattiva: uno stato di elevato consumo energetico in cui lo schermo è alla massima luminosità, consentendo la piena interazione dell'utente.
  • Ambientale: uno stato di basso consumo energetico in cui la luminosità del display si riduce per risparmiare energia. In questo stato, la UI della tua app occupa ancora l'intero schermo, ma il sistema potrebbe alterarne l'aspetto sfocandolo o sovrapponendo contenuti come l'ora. Questa modalità è nota anche come modalità Ambient.

Il sistema operativo controlla la transizione tra questi stati.

Un'app sempre attiva è un'applicazione che mostra contenuti sia nello stato interattivo che in quello ambient.

Quando un'app sempre attiva continua a visualizzare la propria UI mentre il dispositivo è in modalità Ambient a basso consumo energetico, viene descritta come in modalità ambiactive.

Transizioni di sistema e comportamento predefinito

Quando un'app è in primo piano, il sistema gestisce le transizioni dello stato di alimentazione in base a due timeout attivati dall'inattività dell'utente.

  • Timeout 1: stato interattivo a stato Ambient:dopo un periodo di inattività dell'utente, il dispositivo entra nello stato Ambient.
  • Timeout n. 2: ritorno al quadrante. Dopo un ulteriore periodo di inattività, il sistema potrebbe nascondere l'app corrente e mostrare il quadrante.

Subito dopo il primo passaggio del sistema allo stato Ambient, il comportamento predefinito dipende dalla versione di Wear OS e dalla configurazione dell'app:

  • Su Wear OS 5 e versioni precedenti, il sistema mostra uno screenshot sfocato dell'applicazione in pausa, con l'ora sovrapposta.
  • Su Wear OS 6 e versioni successive, se un'app ha come target SDK 36 o versioni successive, viene considerata sempre attiva. Il display è oscurato, ma l'applicazione continua a essere eseguita e rimane visibile. Gli aggiornamenti possono essere eseguiti anche una volta al minuto.

Personalizzare il comportamento per lo stato Ambient

Indipendentemente dal comportamento predefinito del sistema, in tutte le versioni di Wear OS puoi personalizzare l'aspetto o il comportamento della tua app nello stato Ambient utilizzando AmbientLifecycleObserver per ascoltare i callback sulle transizioni di stato.

Utilizzare AmbientLifecycleObserver

Per reagire agli eventi della modalità Ambient, utilizza la classe AmbientLifecycleObserver:

  1. Implementa l'interfaccia AmbientLifecycleObserver.AmbientLifecycleCallback. Utilizza il metodo onEnterAmbient() per regolare l'interfaccia utente per lo stato di risparmio energetico e onExitAmbient() per ripristinarla nella visualizzazione interattiva completa.

    val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
        override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
            // ... Called when moving from interactive mode into ambient mode.
            // Adjust UI for low-power state: dim colors, hide non-essential elements.
        }
    
        override fun onExitAmbient() {
            // ... Called when leaving ambient mode, back into interactive mode.
            // Restore full UI.
        }
    
        override fun onUpdateAmbient() {
            // ... Called by the system periodically (typically once per minute)
            // to allow the app to update its display while in ambient mode.
        }
    }
    
  2. Crea un AmbientLifecycleObserver e registralo con il ciclo di vita della tua attività o del tuo componente componibile.

    private val ambientObserver = AmbientLifecycleObserver(activity, ambientCallback)
    
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(ambientObserver)
    
        // ...
    }
    
  3. Chiama removeObserver() per rimuovere l'osservatore in onDestroy().

Per gli sviluppatori che utilizzano Jetpack Compose, la libreria Horologist fornisce un'utile utilità, il componibile AmbientAware, che semplifica l'implementazione di questo pattern.

TimeText sensibile all'ambiente

Come eccezione alla necessità di un osservatore personalizzato, su Wear OS 6 il widget TimeText è compatibile con la modalità Ambient. Si aggiorna automaticamente una volta al minuto quando il dispositivo è in modalità Ambient senza codice aggiuntivo.

Controllare la durata di accensione dello schermo

Le sezioni seguenti descrivono come gestire la durata di visualizzazione dell'app sullo schermo.

Impedire il ritorno al quadrante con un'attività in corso

Dopo un periodo di tempo nello stato Ambientale (Timeout n. 2), il sistema tornerà in genere al quadrante. L'utente può configurare la durata del timeout nelle impostazioni di sistema. Per alcuni casi d'uso, ad esempio quando un utente monitora un allenamento, un'app potrebbe dover rimanere visibile più a lungo.

Su Wear OS 5 e versioni successive, puoi impedirlo implementando un'attività in corso. Se la tua app mostra informazioni su un'attività utente in corso, come una sessione di allenamento, puoi utilizzare l'API Ongoing Activity per mantenere la tua app visibile fino al termine dell'attività. Se un utente torna manualmente al quadrante, l'indicatore dell'attività in corso gli consente di tornare alla tua app con un solo tocco

Per implementare questa funzionalità, l'intent tocco della notifica continua deve puntare all'attività sempre attiva, come mostrato nel seguente snippet di codice:

private fun createNotification(): Notification {
    val activityIntent =
        Intent(this, AlwaysOnActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        }

    val pendingIntent =
        PendingIntent.getActivity(
            this,
            0,
            activityIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )

    val notificationBuilder =
        NotificationCompat.Builder(this, CHANNEL_ID)
            // ...
            // ...
            .setOngoing(true)

    // ...

    val ongoingActivity =
        OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
            // ...
            // ...
            .setTouchIntent(pendingIntent)
            .build()

    ongoingActivity.apply(applicationContext)

    return notificationBuilder.build()
}

Mantenere lo schermo attivo ed evitare lo stato Ambient

In rari casi, potrebbe essere necessario impedire completamente al dispositivo di entrare nello stato Ambient. ovvero per evitare il timeout n. 1. Per farlo, puoi utilizzare il flag della finestra FLAG_KEEP_SCREEN_ON. Funziona come un wake lock, mantenendo il dispositivo nello stato Interattivo. Utilizza questa impostazione con estrema cautela in quanto influisce notevolmente sulla durata della batteria.

Suggerimenti per la modalità Ambient

Per offrire la migliore esperienza utente e risparmiare energia in modalità Ambient, segui queste linee guida di progettazione. Questi consigli danno la priorità a un'esperienza utente chiara, evitando informazioni fuorvianti e riducendo il disordine visivo, ottimizzando al contempo il consumo energetico del display.

  • Ridurre il disordine visivo e la potenza del display. Un'interfaccia utente pulita e minimalista indica all'utente che l'app è in stato di basso consumo energetico e consente di risparmiare molta batteria limitando i pixel luminosi.
    • Mantieni almeno l'85% dello schermo nero.
    • Mostra solo le informazioni più importanti, spostando i dettagli secondari sul display interattivo.
    • Utilizza contorni per icone o pulsanti di grandi dimensioni anziché riempimenti pieni.
    • Evita grandi blocchi di colore uniforme e branding o immagini di sfondo non funzionali.
  • Gestire i dati dinamici obsoleti
    • Il callback onUpdateAmbient() viene richiamato solo periodicamente, in genere una volta al minuto, per risparmiare energia. A causa di questa limitazione, tutti i dati che cambiano di frequente, come un cronometro, la frequenza cardiaca o la distanza dell'allenamento, diventano obsoleti tra gli aggiornamenti. Per evitare di mostrare informazioni fuorvianti e errate, ascolta il callback onEnterAmbient e sostituisci questi valori live con contenuti segnaposto statici, ad esempio --.
  • Mantenere un layout coerente
    • Mantieni gli elementi nella stessa posizione nelle modalità Interattiva e Ambientale per creare una transizione fluida.
    • Mostra sempre l'ora.
  • Essere sensibile al contesto
    • Se l'utente si trovava in una schermata di impostazioni o configurazione quando il dispositivo entra in modalità Ambient, valuta la possibilità di mostrare una schermata più pertinente della tua app anziché la visualizzazione delle impostazioni.
  • Gestire i requisiti specifici per i dispositivi
    • Nell'oggetto AmbientDetails passato a onEnterAmbient():
      • Se deviceHasLowBitAmbient è true, disattiva l'anti-aliasing, se possibile.
      • Se burnInProtectionRequired è true, sposta periodicamente gli elementi dell'interfaccia utente leggermente ed evita le aree bianche piene per evitare il burn-in dello schermo.

Debug e test

Questi comandi adb possono essere utili durante lo sviluppo o il test del comportamento dell'app quando il dispositivo è in modalità Ambient:

# put device in ambient mode if the always on display is enabled in settings
# (and not disabled by other settings, such as theatre mode)
$ adb shell input keyevent KEYCODE_SLEEP

# put device in interactive mode
$ adb shell input keyevent KEYCODE_WAKEUP

Esempio: app di allenamento

Prendi in considerazione un'app di allenamento che deve mostrare le metriche all'utente per l'intera durata della sessione di allenamento. L'app deve rimanere visibile durante le transizioni di stato Ambient ed evitare di essere sostituita dal quadrante orologio.

A questo scopo, lo sviluppatore deve:

  1. Implementa un AmbientLifecycleObserver per gestire le modifiche dell'interfaccia utente tra gli stati Interattivo e Ambientale, ad esempio oscurando lo schermo e rimuovendo i dati non essenziali.
  2. Crea un nuovo layout a basso consumo energetico per lo stato Ambient che segue le best practice.
  3. Utilizza l'API Ongoing Activity per tutta la durata dell'allenamento per impedire al sistema di tornare al quadrante.

Per un'implementazione completa, consulta l'esempio di esercizio basato su Compose su GitHub. Questo esempio mostra anche l'utilizzo del composable AmbientAware della libreria Horologist per semplificare la gestione della modalità Ambient in Compose.