Tratamiento de la batería en Android

Publicado por Juan Manuel Rincón Carrera el

MobileAndroidBatería

Quienes trabajamos en la creación de apps tenemos claro que no consiste solamente en el desarrollo, y el mercado cada vez es más consciente de ello. Se requiere un análisis funcional específico para apps móviles, no se quiere exponer todo lo que pudiera ofrecer una web, el UX también es específico y dentro de estas dos necesidades se requerirá definir de qué manera se exponen esos datos a las plataformas móviles, posiblemente con una API específica que exponga los datos (y sólo esos datos) requeridos, así mismo que pueda ser objeto de versionado, monitorización, securización, y al pasar todo el tráfico por ella, será más fácil encontrar los posibles fallos que puedan ir surgiendo.

Después de ello entra el desarrollo con sus buenas prácticas, su arquitectura, su metodología y ese etcétera bastante mejor conocido.

¿Pero y después? Hay una parte importante y que repercute en los resultados finales, que es menos visible pero cada vez más necesaria.

Hablo de la parte de retrospectiva sobre una app desarrollada en la que se incluirán pruebas de seguridad, pruebas de performance (de procesos y de memoria), de revisión de arquitectura y cada vez con un protagonismo más creciente, las pruebas de uso de batería.

La batería, la gran olvidada:

alt

Si bien es cierto que hace años las apps eran totalmente transparentes al uso de la batería. Ni los usuarios eran muy conscientes de las repercusiones ni el sistema operativo requería que una app estuviera preparada de una manera especial. Antes, con seguir más o menos buenas prácticas y no crear un sumidero energético, bastaba.

Ya no.

Ahora el sistema operativo pone normas, reglas, categorías, en las que, dependiendo del momento y situación, la app podrá recurrir a más o menos recursos del dispositivo. Por lo que no tener en cuenta este nuevo entorno puede llevar a un mal funcionamiento y lo que es peor, tener grandes dificultades para encontrar el problema.

Un poco de historia: Android y su evolución

Comienzo por la versión 6 ya que hubo un antes y un después a partir de esta versión a nivel de batería, pero intentaré centrarme sólo en los cambios más importantes de las últimas versiones.

Android 6 (API 23)

Sin entrar mucho en detalle comentar que se introducen dos funciones de ahorro de batería:

Doze

Reduce el consumo de batería aplazando la actividad de CPU y de red cuando el dispositivo lleva un largo periodo sin usarse hasta los periodos de mantenimiento.

alt

*Cuidado con las alarmas, para que funcione en estos periodos hay que usar setAndAllowWhileIdle() y setExactAndAllowWhileIdle().

App Standby

Permite al sistema determinar cuándo una app esta inactiva. Más adelante se categorizará en buckets (depósitos). Entra en funcionamiento cuando el usuario lleva tiempo sin tocar la pantalla y:

  • No inicia la app de manera explícita
  • No hay un proceso en primer plano de la app (ni servicio ni actividad)
  • No genera la app una notificación que los usuarios pueden ver en la pantalla de bloqueada o en la bandeja de notificaciones
  • No es una app de administración de dispositivo

FCM está optimizado para trabajar con los modos de inactividad Descanso y App Standby mediante mensajes de FCM de prioridad alta.

Lista blanca

Una app que está en la lista blanca puede usar la red y conservar los bloqueos de activación parciales durante los modos Descanso y App Standby. Sin embargo, se aplican otras restricciones a la app de la lista blanca. Una app puede llamar a isIgnoringBatteryOptimizations() para verificar si está en la lista blanca de exenciones.

Los usuarios pueden configurar manualmente la lista blanca en Configuración > Batería > Optimización de la batería. De manera alternativa, el sistema proporciona métodos para que las apps soliciten a los usuarios que las agreguen a la lista blanca.

  • Una app puede activar la intent ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS para llevar al usuario directamente a Battery Optimization, donde puede agregar la app.
  • Una app con el permiso REQUEST_IGNORE_BATTERY_OPTIMIZATIONS puede activar un diálogo del sistema para permitir al usuario agregar la app a la lista blanca directamente, sin necesidad de acceder a la configuración. La app emite un intent ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS para activar el diálogo.
  • El usuario puede quitar apps de forma manual de la lista blanca según lo necesite.

Google advierte que no permitirá estas peticiones a menos que la función central de la app se vea afectada negativamente.

Fuente: https://developer.android.com/training/monitoring-device-state/doze-standby

Android 7 (API 24)

A su vez, Doze tiene dos estados con diferentes características:

LIGHT-DOZE
  • Pantalla Apagada
  • Batería no cargando
DEEP-DOZE
  • Pantalla Apagada
  • Batería no cargando
  • Dispositivo sin uso ~30 minutos
alt

Hay algunos cambios respecto a la versión anterior:

  • Se sincroniza periódicamente independientemente del estado de la batería del dispositivo
  • Dispara alarmas para realizar ciertas tareas en segundo plano
  • Necesita wake locks para realizar tareas
Cada uno de los modos Doze tiene varios estados:

Light: ACTIVE -> IDLE -> IDLE_MAINTENANCE -> OVERRIDE

Deep: ACTIVE -> IDLE_PENDING -> SENSING -> LOCATING -> IDLE -> IDLE_MAINTENANCE

Fuentes:
https://medium.com/@mohitgupta92/testing-your-app-on-doze-mode-4ee30ad6a3b0

https://stackoverflow.com/questions/43048010/how-to-test-doze-mode-on-android

Android 9 (API 28)

Android ha ido mejorando su gestión de batería a lo largo del tiempo, pero nos vamos a centrar en los cambios de la versión 9 a esta parte:

App Standby Buckets

Según Google:

“El sistema limita el acceso de las apps a los recursos de los dispositivos, como el CPU o la batería, según los patrones de uso del usuario.“

Y crea 5 depósitos (buckets) donde tu app se puede encontrar:

  • Activa: La app se está usando, el SO no impone restricciones.
  • Espacio de trabajo: Se usa a menudo, pero no está activa, un ejemplo pueden ser las apps de redes sociales. Se imponen restricciones leves.
  • Frecuente: Si se usa de manera regular, no tiene que ser todos los días. Las restricciones son más estrictas sobre la capacidad de ejecutar trabajos y activar alarmas, así como con los mensajes de FCM de alta prioridad. Como pudiera ser una app de entrenamiento.
  • Poco frecuente: Si no se usa a menudo, como una app de hoteles. Tendrá las mismas restricciones anteriores y se limita la capacidad de la app para conectarse a Internet.
  • Nunca ejecutada: Se ha instalado y nunca se ha ejecutado. Tiene las restricciones más severas.

Cuando la app está cargando, desaparecen estas restricciones. Los fabricantes pueden establecer sus propios criterios para la asignación de apps no activas en los depósitos.

Se puede saber en qué contenedor está la app:

    UsageStatsManager.getAppStandbyBucket().
alt
Buenas prácticas:
  • Si una app no tiene actividad de lanzamiento, nunca estará entre las activas.
  • Si una notificación te lleva a abrir la app, esta no pasará a activa.
  • Si la app está dividida en varios paquetes, éstos pueden estar en diferentes depósitos, hay que tenerlo en cuenta.
Mejoras en el modo de ahorro de batería

Cuando se activa el ahorro de batería, el sistema impone restricciones en todas las apps. Los fabricantes pueden alterar estas reglas, pero en las compilaciones AOSP encontramos:

  • El sistema pone las apps en el modo de espera de forma más agresiva, en lugar de esperar que queden inactivas.
  • Se aplican límites de ejecución en segundo plano a todas las apps, sin importar su nivel de API.
  • Los servicios de ubicación pueden inhabilitarse cuando la pantalla está apagada. Las apps en segundo plano no tienen acceso a redes.

Fuente: https://developer.android.com/about/versions/pie/power

Android 10

Seguimiento del uso de batería

A partir de Android 10, SystemHealthManager restablece las estadísticas de uso de batería cada vez que el dispositivo se desconecta después de un evento de carga importante. En términos generales, un evento de carga importante puede ser que el dispositivo se cargó completamente o que pasó de tener muy poca carga a tenerla casi completa.

En versiones anteriores a Android 10, las estadísticas de uso de batería se restablecían cada vez que el dispositivo se desconectaba, independientemente del cambio en el nivel de la batería.

Android 11

No aparecen grandes cambios.

Modo de ahorro de batería

Para activarlo en Android 11 tenemos que hacerlo entrando en Ajustes > Batería y escogiendo la opción Ahorro de batería donde se nos desplegará una nueva pestaña con la posibilidad de activarlo ahora o bien programarlo. Para programar este modo de ahorro de batería nos dará dos opciones:

alt
  • Ahorro de batería según la rutina: Basándonos en la inteligencia artificial y el conocimiento de nuestro consumo diario Android 11 puede activar el ahorro automáticamente para evitar que se apague el móvil antes de cargarlo.
  • Según el porcentaje: Podremos establecer un porcentaje para que llegado ese punto se activa automáticamente el ahorro.

Con estos cambios se establece el modo oscuro, pero además nosotros vamos a poder configurar el modo oscuro completo en Android 11.

Optimizar aplicaciones en segundo plano

Las aplicaciones no se libran del ahorro de batería y es que más allá de las opciones que hemos conocido, si nos dirigimos al apartado de Ajustes > Aplicaciones y seleccionamos alguna de ellas podremos ajustarla. Por defecto Android 11 ya optimiza cada una de las aplicaciones, algo que comprobaremos en la sección de batería dentro de cada app pero además nos deja restringirla.

alt

Esta opción llamada "restringir" lo que hace es limitar los procesos de forma destacada, lo que puede hacer que no recibamos notificaciones a costa de ahorrar batería en Android 11. Solo se recomienda en juegos o aplicaciones donde las notificaciones no sean importantes, es una forma agresiva de cerrar sus procesos y forzar para que no se abran.

Se mantiene la batería inteligente

Una de las novedades en el ahorro de batería que vimos debutar en Android 10 sigue presente con Android 11, en este caso lo encontramos dentro de Ajustes > Batería y nos encontraremos con el botón de batería inteligente. El compromiso de esta función se basa en conseguir que la batería dure más al limitar procesos en segundo plano que no necesitamos continuamente.

Fuente: https://www.movilzona.es/2020/07/03/ahorro-bateria-android-11/

Android 12

Android 12 hibernará las apps que no se usan desde hace tiempo y siguen instaladas, de forma que se ahorre batería, memoria y espacio.

Para empezar, su nombre de cara al usuario será Aplicaciones sin usar o Unused apps en inglés. Google las describe como aplicaciones que no se han abierto en los últimos tres meses. En Android 11, el sistema ya hace esta distinción para revocar los permisos de aplicaciones que hace tiempo que no abres.

En Android 12, el concepto se expande y se mostrará un nuevo apartado en la información de una aplicación para eliminar permisos y liberar espacio cuando hace mucho que no usas una aplicación. También habrá un nuevo apartado en el apartado aplicaciones de los ajustes, donde podrás consultar qué apps instaladas entran en esta categoría.

Cuando una aplicación entra en esta categoría, sucederán tres cosas: se revocan sus permisos, dejan de mostrar notificaciones y se eliminan archivos temporales para liberar espacio. No está claro qué archivos exactamente se eliminan, aunque lo más probable es que la caché se encuentre entre ellos y no los datos personales o configuraciones.

Fuente: https://www.xatakandroid.com/sistema-operativo/android-12-hibernara-apps-que-no-se-usan-hace-tiempo-para-ahorrar-bateria-liberar-espacio

Nuevo permiso para alarmas exactas: Las aplicaciones que requieren una precisión exacta para programar acciones tendrán ahora que solicitar al usuario un nuevo permiso especial, en el que el usuario decide si quiere que esa aplicación use una alarma inexacta, con un desfase de unos segundos y que consume menos batería o una alarma exacta que hace la aplicación activarse en el segundo exacto.

Fuente: https://www.xatakandroid.com/sistema-operativo/android-12-developer-preview-3-esta-aqui-nuevas-animaciones-cambios-interfaz-novedades

Mucha teoría, poca diversión:

Muy bien, hemos visto la evolución de Android y cómo el ahorro de batería ha ido cobrando importancia. Y yo, como persona, como individuo o peor aún, como desarrollador :P, ¿Qué puedo hacer?

Ahora sabemos cómo funciona Android, más o menos hemos respetado todas las normas… pero, no sé, son muchas cosas, no lo tengo claro. No es tu culpa, no está claro y lo que está claro lo pueden modificar los fabricantes…

La realidad no es tan cruda, Google ha tratado de que no.

Cómo controlar el nivel de la carga y el estado de carga:

¿Qué se puede hacer programáticamente?

  • Estado de carga actual
  • Supervisar los cambios en el estado de carga
  • Determinar el nivel de batería
  • Supervisar cambios importantes en el nivel de la batería

Fuente: https://developer.android.com/training/monitoring-device-state/battery-monitoring

Comprobar a que tipo de conector esta conectado el dispositivo:

Si un dispositivo está conectado, puede estarlo en cualquiera de estos cuatro tipos de conectores:

  • Auto
  • Escritorio
  • Escritorio de gama baja (analógico)
  • Escritorio de gama alta (digital)

Ten en cuenta que las últimas dos opciones se introdujeron en Android únicamente en el nivel de API 11. Es recomendable que compruebes las tres opciones solo cuando quieras saber qué tipo de conectores son y no si son digitales o analógicos.

Fuente: https://developer.android.com/training/monitoring-device-state/docking-monitoring

Cómo probar tu app en Doze y App Standby:

Explicado por Google en el link de la fuente:

Cómo probar tu app con el modo Descanso

Para probar el modo Descanso con tu app, realiza lo siguiente:

  1. Configura un dispositivo de hardware o virtual con una imagen del sistema de Android 6.0 (API nivel 23) o versiones posteriores.
  2. Conecta el dispositivo a la máquina de desarrollo y luego instala tu app.
  3. Ejecuta tu app y déjala activa.

Ejecuta el siguiente comando para forzar el modo de inactividad en el sistema:

$ adb shell dumpsys deviceidle force-idle

Cuando estés listo, ejecuta el siguiente comando para salir del modo de inactividad:

$ adb shell dumpsys deviceidle unforce

Ejecuta el siguiente comando para volver a activar el dispositivo:

$ adb shell dumpsys battery reset
  1. Observa el comportamiento de tu app después de reactivar el dispositivo. Asegúrate de que esta se recupere correctamente cuando el dispositivo salga del modo Descanso.
Cómo probar tu app con App Standby

Para probar el modo App Standby con tu app, realiza lo siguiente:

  1. Configura un dispositivo de hardware o virtual con una imagen del sistema de Android 6.0 (API nivel 23) o versiones posteriores.
  2. Conecta el dispositivo a la máquina de desarrollo y luego instala tu app.
  3. Ejecuta tu app y déjala activa.

Ejecuta los siguientes comandos para forzar el modo App Standby en la app:

$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true

Simula la activación de tu app con los siguientes comandos:

$ adb shell am set-inactive <packageName> false
$ adb shell am get-inactive <packageName>
  1. Observa el comportamiento de tu app después de activarla. Asegúrate de que se reactive correctamente cuando salga del modo App Standby. En especial, debes comprobar si las tareas en segundo plano y las notificaciones de tu app continúan funcionando de la manera esperada.

Fuente: https://developer.android.com/training/monitoring-device-state/doze-standby

Comprobar StandBy Buckets y ahorro de batería:

Con la anterior comprobación deberíamos de tener suficiente control de cómo se comporta nuestra app. No obstante, si se quiere hilar más fino, Google nos da las herramientas:

App Standby Buckets

Te recomendamos usar ADB para asignar manualmente tu app a un App Standby Bucket. Para modificar el depósito de una app, utiliza el siguiente comando:

    $ adb shell am set-standby-bucket packagename active|working_set|frequent|rare

También puedes usar ese comando para configurar varios paquetes a la vez:

    $ adb shell am set-standby-bucket package1 bucket1 package2 bucket2...

Para verificar el depósito en el que se encuentra una app, ejecuta

    $ adb shell am get-standby-bucket [packagename]

Si no pasas un parámetro packagename, el comando lista los depósitos para todas las apps. Una app también puede identificar su depósito en el tiempo de ejecución llamando al nuevo método UsageStatsManager.getAppStandbyBucket().

Ahorro de batería

Existen varios comandos para probar la manera en que se comporta tu app en condiciones de bajo nivel de batería.

Nota: También puedes recurrir a la pantalla Settings > Battery saver del dispositivo para activar su modo de ahorro de batería.

Para simular la desconexión del dispositivo de la corriente, usa el comando

    $ adb shell dumpsys battery unplug

Para probar la manera en que se comporta el dispositivo en condiciones de bajo nivel de batería, usa este comando:

    $ adb shell settings put global low_power 1

Una vez que hayan concluido tus pruebas, puedes deshacer tus ajustes manuales para el dispositivo con este comando:

    $ adb shell dumpsys battery reset

Fuente: https://developer.android.com/about/versions/pie/power#battery-saver

Herramientas de análisis de la batería del dispositivo: Batterystats y Battery Historian

Definición de Google:

Batterystats es una herramienta del framework de Android que recopila datos sobre la batería en tu dispositivo. Puedes usar adb para volcar los datos sobre la batería recopilados en tu máquina de desarrollo y crear un informe que puedas analizar con Battery Historian. Esta herramienta convierte el informe de Batterystats en una visualización HTML que puedes ver en tu navegador.

Con lo cual tenemos la opción de poder monitorizar el uso de batería de nuestra app a lo largo de tiempo, por un lado con Batterystats, que nos proveerá el archivo en "crudo" y con Battery Historian que nos permitirá visualizarlo de una manera más o menos amigable:

alt

Creo que está muy bien explicado por Google, es bastante sencillo:

Para instalar Battery Historian y recopilar los datos con Batterystats: https://developer.android.com/topic/performance/power/setup-battery-historian

Análisis

Tenemos dos puntos de vista para analizar el consumo de batería de nuestra app:

Análisis general del dispositivo

Tenemos la perspectiva de todo el consumo de batería del dispositivo, esto incluye todas las apps y el propio sistema. Incluso se puede ver el ranking de las apps que más consumen batería.

Análisis de una app en concreto

Por otro lado, podemos seleccionar una app en concreto y nos aparecerán las siguientes categorías:

  • SyncManager
  • Proceso en primer plano
  • Bloqueo de activación del espacio del usuario
  • App principal
  • JobScheduler
  • Procesos del administrador de actividades

A partir de aquí podemos ir analizando paso a paso los detalles del consumo de nuestra app.

Para entrar un poco más en detalle en cómo analizar desde cada uno de los puntos de vista: https://developer.android.com/topic/performance/power/battery-historian

Bajémoslo a tierra:

Ok, ahora sé la teoría, sé que puedo hacer… pero ¿cómo ataco? ¿dónde y cuándo meto mano a esto?

¿Qué tenemos?

  • ¿Tengo necesidad de trabajos en segundo plano o conexión de mi app en los diferentes modos de optimización de batería?
  • ¿Tengo una funcionalidad un tanto especial?
  • Tengo que hacer un trabajo periódico, debería de contestar mi app al instante, mi app tiene alguna parte compleja de algoritmia o de conexión de red…
  • Mi app es una app con millones de descargas, me lo exige el guin.
  • O simplemente quiero estar seguro de que mi app no tiene por dónde toserle, es perfecta, es óptima, posiblemente podría considerarse como una obra de arte de la tecnología (Eso es que llevas poco trabajando en IT, mucho ánimo y no pierdas la esperanza).

¿Qué sabemos?

Asegurar la funcionalidad de la app

Como ya hemos visto, el SO puede someter a la app a diversos estados en las que pudiera fallar alguna funcionalidad. Es importante, con lo que ya sabemos, comprobar que nuestra app no se comporta de una manera inesperada en puntos críticos de la funcionalidad (procesos en segundo plano, guardados de info. lentos en persistencia, llamadas pesadas a servidor...).

La idea sería identificar esta funcionalidad e inducir los diferentes estados en la app.

Optimizar la funcionalidad de la app

Esta ver vamos a usar las herramientas Batterystats y Battery Historian para analizar el uso de batería de nuestra app en los siguientes casos:

  • Bajo un comportamiento normal: En un ciclo normal de pruebas por parte de QA analizar el consumo de batería para poder evaluar de una manera global cómo se comporta nuestra app a nivel de eficiencia energética y así poder hacer una valoración global.
  • Comportamiento en funciones específicas: En funciones en las que sospechemos que hay un gran uso de la batería, analizar y ver cómo se pueden optimizar.
  • Inducir estados de optimización de batería del SO: Induciendo los estados anteriormente comentados en el post, comprobar qué consumo de batería requiere la app tanto con un comportamiento normal como en funciones específicas. (Lo mismo que las dos anteriores induciendo estados).

Bonus: Mi problema es precisamente la optimización de batería (La otra cara de la moneda)

Puede que nuestra app se encuentre con que debe de hacer ciertas cosas que debido a la serie de mecanismos comentados anteriormente para la optimización de batería, de una manera u otra, no le permite. Android nos provee un par de alternativas para "saltarnos" los mecanismos de optimización de batería:

Mantener la pantalla encendida

Pensado para apps que requieren que la pantalla se mantenga activa, como apps de juegos o de videos. No requiere de permisos especiales.

Mantener la CPU encendida

Pensado para mantener la CPU en ejecución para poder completar algunas tareas que sean requeridas antes de que se suspenda el dispositivo. Esto se hace a través de los llamados "bloqueos de activación".

Explicado en más detalle: https://developer.android.com/training/scheduling/wakelock

Conclusión (o Google aprieta pero no ahoga):

Cada vez hay más restricciones a las apps a la hora del acceso a recursos en pos de conservar el máximo posible de batería. Esto es un hecho. No obstante, también es cierto, y por el mismo motivo, que al proteger la experiencia del usuario final (si tu app está en primer plano, en uso y activa), nada la parará. Si tu app requiere hacer algo en background, hay nuevos métodos que provee Google para llevarlo a cabo (ej. WorkManager) y lo que sí que tenemos que tener en cuenta es que ya no será en un momento preciso y a nuestro gusto, si no que el sistema operativo puede modificarlo (y el usuario a través del sistema operativo).

Si tenemos alguna funcionalidad un tanto especial a la hora de ejecutar algún proceso en background, deberemos desarrollarla pensando en que puede fallar o retrasarse. Si es prioritaria y central para la app, ver qué "facilidades" se le pueden otorgar a través del sistema operativo. Y una vez desarrollada, probar con las herramientas que hemos visto que comportamiento tiene.

¡Espero que este artículo haya sido de utilidad y toda aportación será de agradecer!

¡Síguenos en Twitter para estar al día de próximas entregas!