Cómo mantener tu app visible en Wear

Algunas apps para Wear OS son constantemente visibles para el usuario.

Los dispositivos con Wear OS que ejecutan Android 5.1 o versiones posteriores permiten que las apps permanezcan en primer plano y, al mismo tiempo, ahorran batería. Las apps para Wear OS pueden controlar lo que se muestra en el reloj mientras que este está en el modo de bajo consumo (ambiente). Las apps para Wear que se ejecutan tanto en el modo ambiente como en el interactivo se llaman apps siempre activas.

Estas permiten que los usuarios que están trotando vean en el reloj la distancia recorrida y el tiempo transcurrido. Algunos usuarios guardan listas de compras y pueden ver rápidamente los artículos incluidos mientras compran.

Habilitar una app visible constantemente afecta la duración de batería, por lo que debes considerar especialmente ese impacto cuando agregues esta función a la tuya.

Importante: La versión 27.1.0 de la biblioteca de compatibilidad de Android proporciona una nueva forma de admitir el modo ambiente que usa la clase AmbientModeSupport, en lugar de la clase WearableActivity. Puedes elegir la nueva forma preferida para admitir el modo ambiente o, en su lugar, extender la clase WearableActivity.

Nota: La clase AmbientMode dejó de estar disponible y se reemplazó por AmbientModeSupport.

Consulta los siguientes recursos relacionados:

Cómo configurar tu proyecto

Para admitir el modo ambiente, debes actualizar tu SDK de Android y configurar tu proyecto de desarrollo. Para realizar los cambios necesarios, sigue estos pasos:

  1. Crea o actualiza tu proyecto de acuerdo con la configuración de la página Cómo crear y ejecutar una app para wearables.
  2. Agrega el permiso WAKE_LOCK al archivo de manifiesto de Android:
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    

Modo ambiente con la clase AmbientModeSupport

El uso de la clase AmbientModeSupport para admitir el modo ambiente te permite beneficiarte de lo siguiente:

Para usar la clase AmbientModeSupport, debes extender una de las subclases de FragmentActivity (o esta misma) y, luego, implementar una interfaz de proveedor, que a su vez puede usarse para detectar actualizaciones del modo ambiente.

Nota: El método AmbientModeSupport.attach(FragmentActivity) adjunta un fragmento sin interfaz gráfica a la clase o subclase FragmentActivity que proporcionas y las llamadas posteriores a FragmentManager.getFragments() muestran una referencia a este fragmento (que no debe usarse de ninguna manera).

A continuación, se describe el uso general de la clase AmbientModeSupport:

  1. Crea una subclase de una de las clases de FragmentActivity.
  2. Implementa la interfaz AmbientCallbackProvider, como se muestra en el ejemplo debajo. Reemplaza el método getAmbientCallback() para proporcionar las devoluciones de llamada necesarias de reacción a los eventos de ambiente del sistema Android. En el Paso 4, crearemos la clase de devolución de llamada personalizada.

    Kotlin

        class MainActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackProvider {
            …
            override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = MyAmbientCallback()
            …
        }
        

    Java

        public class MainActivity extends AppCompatActivity implements AmbientModeSupport.AmbientCallbackProvider {
            …
            @Override
            public AmbientModeSupport.AmbientCallback getAmbientCallback() {
                return new MyAmbientCallback();
            }
            …
        }
        
  3. En el método onCreate(), habilita el modo ambiente llamando a AmbientModeSupport.attach(FragmentActivity). Este mostrará un AmbientModeSupport.AmbientController. El controlador te permite verificar el estado de ambiente por fuera de las devoluciones de llamada; es recomendable mantener una referencia al objeto AmbientModeSupport.AmbientController:

    Kotlin

        class MainActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackProvider {
            ...
            /*
             * Declare an ambient mode controller, which will be used by
             * the activity to determine if the current mode is ambient.
             */
            private lateinit var ambientController: AmbientModeSupport.AmbientController
            ...
            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)
                ...
                ambientController = AmbientModeSupport.attach(this)
            }
            ...
        }
        

    Java

        public class MainActivity extends AppCompatActivity implements AmbientModeSupport.AmbientCallbackProvider {
            ...
            /*
             * Declare an ambient mode controller, which will be used by
             * the activity to determine if the current mode is ambient.
             */
            private AmbientModeSupport.AmbientController ambientController;
            ...
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                ...
                ambientController = AmbientModeSupport.attach(this);
            }
            ...
        }
        
  4. Crea una clase interna que extienda la clase AmbientCallback a fin de responder a los eventos de ambiente. Esta se convertirá en el objeto que muestra el método que creaste en el Paso 2:

    Kotlin

        private class MyAmbientCallback : AmbientModeSupport.AmbientCallback() {
    
            override fun onEnterAmbient(ambientDetails: Bundle?) {
              // Handle entering ambient mode
            }
    
            override fun onExitAmbient() {
              // Handle exiting ambient mode
            }
    
            override fun onUpdateAmbient() {
              // Update the content
            }
        }
        

    Java

        private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback {
            @Override
            public void onEnterAmbient(Bundle ambientDetails) {
              // Handle entering ambient mode
            }
    
            @Override
            public void onExitAmbient() {
              // Handle exiting ambient mode
             }
    
            @Override
            public void onUpdateAmbient() {
              // Update the content
            }
        }
        

Consulta el ejemplo de AlwaysOn para obtener más información y las prácticas recomendadas.

Modo ambiente con la clase WearableActivity

Tanto en proyectos nuevos como existentes, puedes agregar compatibilidad con el modo ambiente a tu app para Wear si actualizas la configuración de tu proyecto.

Cómo crear una actividad que admita el modo ambiente

Puedes usar la clase WearableActivity para habilitar el modo ambiente en tu actividad:

  1. Crea una actividad que extienda WearableActivity.
  2. En el método onCreate() de tu actividad, llama al método setAmbientEnabled().

Habilita el modo ambiente en tu actividad de la siguiente manera:

Kotlin

    class MainActivity : WearableActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            setAmbientEnabled()
        ...
        }
    }

Java

    public class MainActivity extends WearableActivity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setAmbientEnabled();
            ...
        }
    

Cómo administrar transiciones entre modos

Si el usuario no interactúa con tu app durante un tiempo mientras esta se muestra, o bien si cubre la pantalla con la palma de la mano, el sistema activará el modo ambiente. Una vez que la app active el modo ambiente, actualiza la IU de actividad a un diseño más básico para reducir el consumo de energía. Deberías usar un fondo negro con la menor cantidad de gráficos y texto posible, ambos en color blanco. Para que la transición del modo interactivo al modo ambiente no sea muy brusca para el usuario, intenta mantener los elementos de la pantalla en el mismo lugar. Si quieres obtener más información sobre cómo presentar el contenido en una pantalla ambiente, consulta la guía de diseño de Caras de reloj para Wear OS.

Ten en cuenta que cuando tu app se ejecuta en un dispositivo que no tiene un botón de hardware, al cubrir la pantalla con la palma de la mano, la app no activa el modo ambiente, sino que se cierra y se muestra la pantalla principal. Este comportamiento se creó para garantizar que los usuarios puedan salir de las apps de manera fluida. No obstante, estos dispositivos activarán el modo ambiente de todas formas cuando se agote el tiempo de espera de la pantalla.

Nota: En el modo ambiente, inhabilita los elementos interactivos en pantalla, como los botones. Para obtener más información sobre cómo diseñar interacciones del usuario para una app siempre activa, consulta la guía de diseño de Estructura de apps para Wear OS.

Cuando la actividad cambia al modo ambiente, el sistema llama al método onEnterAmbient() en tu actividad en el wearable. En el siguiente fragmento de código, se muestra cómo cambiar el color del texto a blanco y cómo inhabilitar la opción de suavizado de contorno luego de que el sistema cambie al modo ambiente:

Kotlin

    override fun onEnterAmbient(ambientDetails: Bundle?) {
        super.onEnterAmbient(ambientDetails)

        stateTextView.setTextColor(Color.WHITE)
        stateTextView.paint.isAntiAlias = false
    }
    

Java

    @Override
    public void onEnterAmbient(Bundle ambientDetails) {
        super.onEnterAmbient(ambientDetails);

        stateTextView.setTextColor(Color.WHITE);
        stateTextView.getPaint().setAntiAlias(false);
    }
    

Cuando el usuario presiona la pantalla o levanta la muñeca, la actividad cambia del modo ambiente al interactivo. El sistema llama al método onExitAmbient(). Anula este método para actualizar el diseño de la IU de manera que tu app se muestre en un estado interactivo a todo color.

En el siguiente fragmento de código, se muestra cómo cambiar el color de texto a verde y habilitar la opción de suavizado de contorno cuando el sistema cambia al modo interactivo:

Kotlin

    override fun onExitAmbient() {
        super.onExitAmbient()

        stateTextView.setTextColor(Color.GREEN)
        stateTextView.paint.isAntiAlias = true
    }
    

Java

    @Override
    public void onExitAmbient() {
        super.onExitAmbient();

        stateTextView.setTextColor(Color.GREEN);
        stateTextView.getPaint().setAntiAlias(true);
    }
    

Cómo actualizar contenido en el modo ambiente

El modo ambiente te permite actualizar la pantalla con información nueva para el usuario, pero debes lograr un balance entre las actualizaciones de pantalla y la duración de la batería. Deberías considerar anular únicamente el método onUpdateAmbient() para actualizar la pantalla una vez por minuto en el modo ambiente. Si tu app requiere actualizaciones más frecuentes, ten en cuenta que hay una relación entre la duración de batería y la frecuencia de las actualizaciones. Para ahorrar batería, las actualizaciones no deberían tener una frecuencia mayor que una vez cada 10 segundos. Sin embargo, en la práctica, deberías actualizar tu app con menor frecuencia.

Cómo actualizar una vez por minuto

Con el fin de preservar la batería, la mayoría de las apps de Wear no deberían actualizar frecuentemente la pantalla en el modo ambiente. Te recomendamos que diseñes tu app para que actualice la pantalla una vez por minuto cuando esté en este modo. El sistema proporciona un método de devolución de llamada onUpdateAmbient() que te permite actualizar la pantalla con la frecuencia recomendada.

Para actualizar el contenido de tu app, anula el método onUpdateAmbient() en tu actividad wearable:

Kotlin

    override fun onUpdateAmbient() {
        super.onUpdateAmbient()
        // Update the content
    }
    

Java

    @Override
    public void onUpdateAmbient() {
        super.onUpdateAmbient();
        // Update the content
    }
    

Cómo actualizar con mayor frecuencia

Si bien no se recomienda, es posible actualizar una app para Wear en el modo ambiente con una frecuencia mayor que una vez por minuto. En el caso de las apps que requieren actualizaciones más frecuentes, puedes usar un objeto AlarmManager para activar el procesador y actualizar la pantalla más seguido.

Para implementar una alarma que actualice el contenido con mayor frecuencia en el modo ambiente, sigue estos pasos:

  1. Prepara el administrador de alarmas.
  2. Establece la frecuencia de las actualizaciones.
  3. Programa la próxima actualización cuando la actividad cambie al modo ambiente o si actualmente está en este modo.
  4. Cancela la alarma cuando la actividad cambie al modo interactivo o cuando esta se detenga.

Nota: Es posible que el administrador de alarmas cree nuevas instancias de tu actividad a medida que estas se activen. Para evitarlo, asegúrate de que tu actividad esté declarada con el parámetro android:launchMode="singleInstance" en el manifiesto.

En la siguientes secciones, se describen estos pasos detalladamente.

Cómo preparar el administrador de alarmas

El administrador de alarmas inicia el intent pendiente que actualiza la pantalla y programa la siguiente alarma. En el siguiente ejemplo, se muestra cómo declarar el administrador de alarmas y el intent pendiente en el método onCreate() de tu actividad:

Kotlin

    // Action for updating the display in ambient mode, per our custom refresh cycle.
    private const val AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE"
    ...
    private lateinit var ambientUpdateAlarmManager: AlarmManager
    private lateinit var ambientUpdatePendingIntent: PendingIntent
    private lateinit var ambientUpdateBroadcastReceiver: BroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setAmbientEnabled()

        ambientUpdateAlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

        ambientUpdatePendingIntent = Intent(AMBIENT_UPDATE_ACTION).let { ambientUpdateIntent ->
            PendingIntent.getBroadcast(this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        }

        ambientUpdateBroadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                refreshDisplayAndSetNextUpdate()
            }
        }
        ...
    }
    

Java

    // Action for updating the display in ambient mode, per our custom refresh cycle.
    private static final String AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE";

    private AlarmManager ambientUpdateAlarmManager;
    private PendingIntent ambientUpdatePendingIntent;
    private BroadcastReceiver ambientUpdateBroadcastReceiver;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setAmbientEnabled();

        ambientUpdateAlarmManager =
            (AlarmManager) getSystemService(Context.ALARM_SERVICE);

        Intent ambientUpdateIntent = new Intent(AMBIENT_UPDATE_ACTION);

        ambientUpdatePendingIntent = PendingIntent.getBroadcast(
            this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        ambientUpdateBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                refreshDisplayAndSetNextUpdate();
            }
        };
        ...
    }
    

A continuación, debes registrar y cancelar el registro del receptor de emisión en onResume() y onPause():

Kotlin

    override fun onResume() {
        super.onResume()
        IntentFilter(AMBIENT_UPDATE_ACTION).also { filter ->
            registerReceiver(ambientUpdateBroadcastReceiver, filter)
        }
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(ambientUpdateBroadcastReceiver)
        ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
    }
    

Java

    @Override
    public void onResume() {
        super.onResume();
        IntentFilter filter = new IntentFilter(AMBIENT_UPDATE_ACTION);
        registerReceiver(ambientUpdateBroadcastReceiver, filter);
            ...
    }

    @Override
    public void onPause() {
        super.onPause();
        unregisterReceiver(ambientUpdateBroadcastReceiver);
        ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
        ...
    }
    
Cómo actualizar la pantalla y programar actualizaciones de datos

En esta actividad de ejemplo, el administrador de alarmas se activa cada 20 segundos en el modo ambiente. Cuando el temporizador marca, la alarma activa el intent para actualizar la pantalla y, luego, establece el retraso de la próxima actualización.

En el siguiente ejemplo, se muestra cómo actualizar la información de la pantalla y cómo establecer la alarma para la siguiente actualización:

Kotlin

    // Milliseconds between waking processor/screen for updates
    private val AMBIENT_INTERVAL_MS: Long = TimeUnit.SECONDS.toMillis(20)
    ...
    private fun refreshDisplayAndSetNextUpdate() {
        if (isAmbient) {
            // Implement data retrieval and update the screen for ambient mode
        } else {
            // Implement data retrieval and update the screen for interactive mode
        }
        val timeMs: Long = System.currentTimeMillis()
        // Schedule a new alarm
        if (isAmbient) {
            // Calculate the next trigger time
            val delayMs: Long = AMBIENT_INTERVAL_MS - timeMs % AMBIENT_INTERVAL_MS
            val triggerTimeMs: Long = timeMs + delayMs
            ambientUpdateAlarmManager.setExact(
                    AlarmManager.RTC_WAKEUP,
                    triggerTimeMs,
                    ambientUpdatePendingIntent)
        } else {
            // Calculate the next trigger time for interactive mode
        }
    }
    

Java

    // Milliseconds between waking processor/screen for updates
    private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
    private void refreshDisplayAndSetNextUpdate() {
        if (isAmbient()) {
            // Implement data retrieval and update the screen for ambient mode
        } else {
            // Implement data retrieval and update the screen for interactive mode
        }
        long timeMs = System.currentTimeMillis();
        // Schedule a new alarm
        if (isAmbient()) {
            // Calculate the next trigger time
            long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
            long triggerTimeMs = timeMs + delayMs;
            ambientUpdateAlarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                triggerTimeMs,
                ambientUpdatePendingIntent);
        } else {
            // Calculate the next trigger time for interactive mode
        }
    }
    
Cómo programar la siguiente alarma

Anula los métodos onEnterAmbient() y onUpdateAmbient() a fin de programar la alarma para que actualice la pantalla cuando la actividad cambia al modo ambiente o cuando ya está en este modo.

Kotlin

    override fun onEnterAmbient(ambientDetails: Bundle?) {
        super.onEnterAmbient(ambientDetails)

        refreshDisplayAndSetNextUpdate()
    }

    override fun onUpdateAmbient() {
        super.onUpdateAmbient()
        refreshDisplayAndSetNextUpdate()
    }
    

Java

    @Override
    public void onEnterAmbient(Bundle ambientDetails) {
        super.onEnterAmbient(ambientDetails);
        refreshDisplayAndSetNextUpdate();
    }

    @Override
    public void onUpdateAmbient() {
        super.onUpdateAmbient();
        refreshDisplayAndSetNextUpdate();
    }
    

Nota: En este ejemplo, se llama al método refreshDisplayAndSetNextUpdate() cada vez que la pantalla necesita actualizarse. Para obtener más ejemplos sobre cuándo llamar a este método, consulta el ejemplo de AwaysOn.

Cómo cancelar la alarma

Cuando el dispositivo cambie al modo interactivo, cancela la alarma en el método onExitAmbient():

Kotlin

    override fun onExitAmbient() {
        super.onExitAmbient()

        ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
    }
    

Java

    @Override
    public void onExitAmbient() {
        super.onExitAmbient();
        ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
    }
    

Cuando el usuario cierre o detenga tu actividad, cancela la alarma en el método onDestroy() de tu actividad:

Kotlin

    override fun onDestroy() {
        ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
        super.onDestroy()
    }
    

Java

    @Override
    public void onDestroy() {
        ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
        super.onDestroy();
    }
    

Cómo mantener la compatibilidad con versiones anteriores

Las actividades que admiten el modo ambiente automáticamente recurren a las actividades normales en dispositivos Wear que ejecutan versiones de Android anteriores a 5.1 (nivel de API 22). No se requiere ningún código de app especial para admitir dispositivos en estas versiones de Android. Cuando el dispositivo cambia al modo ambiente, este vuelve a la pantalla principal y cierra tu actividad.

Si tu app no debe instalarse o actualizarse en dispositivos con versiones de Android anteriores a 5.1, actualiza tu manifiesto con lo siguiente:

    <uses-library android:name="com.google.android.wearable" android:required="true" />