A fin de evitar que se agote la batería, un dispositivo Android que está inactivo se suspende rápidamente. Sin embargo, hay situaciones en las que una app necesita activar la pantalla o la CPU y mantenerla encendida para completar algunas tareas.
El enfoque que elijas dependerá de las necesidades de tu app. Sin embargo, como regla general, te recomendamos que uses el enfoque más sencillo posible para tu app a fin de minimizar su impacto en los recursos del sistema. En las siguientes secciones, se describe cómo administrar los casos en los que el comportamiento de suspensión predeterminado del dispositivo no es compatible con los requisitos de tu app.
Alternativas al uso de bloqueos de activación
Antes de agregar compatibilidad con los bloqueos de activación a tu app, considera si los casos prácticos de esta admiten una de las siguientes soluciones alternativas:
- Si tu app realiza descargas de HTTP prolongadas, considera usar
DownloadManager
. - Si tu app sincroniza datos desde un servidor externo, considera crear un adaptador de sincronización.
- Si tu app se basa en servicios en segundo plano, considera usar JobScheduler o Firebase Cloud Messaging a fin de activar estos servicios en intervalos específicos.
Cómo mantener encendida la pantalla
Ciertas apps necesitan mantener la pantalla encendida, como las apps de juegos o de cine. La mejor manera de hacerlo es usar FLAG_KEEP_SCREEN_ON
en tu actividad (y únicamente en una actividad; nunca en un servicio ni en otro componente de la app). Por ejemplo:
Kotlin
class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } }
Java
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } }
La ventaja de este enfoque es que, a diferencia de los bloqueos de activación (que se explican en Cómo mantener encendida la CPU), no requiere un permiso especial y la plataforma administra correctamente el movimiento del usuario entre aplicaciones sin que tu app deba preocuparse por liberar recursos que no se usan.
Otra manera de implementarlo es en el archivo XML de diseño de tu app, por medio del atributo android:keepScreenOn
:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:keepScreenOn="true"> ... </RelativeLayout>
El uso de android:keepScreenOn="true"
equivale a usar FLAG_KEEP_SCREEN_ON
.
Puedes usar el enfoque que se adapte mejor a tu app. La ventaja de configurar la marca programáticamente en tu actividad es que te brinda la opción de borrar el marcador más adelante de manera programática, lo que permite que se apague la pantalla.
Nota: No necesitas borrar la marca FLAG_KEEP_SCREEN_ON
, a menos que ya no quieras que la pantalla se mantenga encendida en tu app en ejecución (por ejemplo, si quieres que la pantalla se suspenda luego de un período de inactividad específico). El administrador de ventanas se encarga de garantizar que las tareas se realicen correctamente cuando la app pasa a segundo plano o vuelve a primer plano. Sin embargo, si quieres borrar una marca de manera explícita y, por lo tanto, permitir que la pantalla vuelva a apagarse, usa clearFlags()
: getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
.
Cómo mantener encendida la CPU
Si necesitas mantener la CPU en ejecución a fin de completar algunas tareas antes de que se suspenda el dispositivo, puedes usar una función de servicio del sistema de PowerManager
llamada "bloqueos de activación". Los bloqueos de activación permiten a tu app controlar el estado de energía del dispositivo host.
La creación y administración de los bloqueos de activación puede afectar significativamente la duración de la batería del dispositivo host. Por lo tanto, deberías usarlos solo cuando sean estrictamente necesarios y conservarlos el menor tiempo posible. Por ejemplo, nunca deberías necesitar un bloqueo de activación en una actividad. Como se describió antes, si quieres mantener la pantalla encendida en tu actividad, usa FLAG_KEEP_SCREEN_ON
.
Un caso legítimo para implementar un bloqueo de activación puede ser un servicio en segundo plano que necesita uno para mantener la CPU en ejecución a fin de realizar una tarea mientras la pantalla está apagada. Sin embargo, esta práctica debería minimizarse debido al impacto que tiene en la duración de la batería.
Para usar un bloqueo de activación, el primer paso es agregar el permiso WAKE_LOCK
en el archivo de manifiesto de tu app:
<uses-permission android:name="android.permission.WAKE_LOCK" />
Si tu app incluye un receptor de emisión que usa un servicio a fin de realizar algunas tareas, puedes administrar el bloqueo de activación por medio de un WakefulBroadcastReceiver
, como se describe en Cómo usar un receptor de emisión que mantiene activo el dispositivo.
Este es el enfoque que se prefiere. Si tu app no sigue este patrón, aquí te mostramos cómo configurar un bloqueo de activación directamente:
Kotlin
val wakeLock: PowerManager.WakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply { acquire() } }
Java
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag"); wakeLock.acquire();
Para retirar el bloqueo de activación, llama a wakelock.release()
. De esta manera, se retira tu solicitud de uso de la CPU. Es importante retirar un bloqueo de activación tan pronto como la app termine de usarlo para evitar que se agote la batería.
Cómo usar un receptor de emisión que mantiene activo el dispositivo
El uso de un receptor de emisión junto con un servicio te permite administrar el ciclo de vida de una tarea en segundo plano.
Un WakefulBroadcastReceiver
es un tipo especial de receptor de emisión que se encarga de crear y administrar un PARTIAL_WAKE_LOCK
para tu app. Un WakefulBroadcastReceiver
pasa la tarea a un Service
(por lo general, un IntentService
) y se asegura de que el dispositivo no se suspenda durante la transición. Si no mantienes un bloqueo de activación mientras se realiza la transición de una tarea a un servicio, permitirás que el dispositivo se suspenda antes de que se complete la tarea. El resultado neto es que la app quizá no complete la tarea hasta algún punto aleatorio en el futuro, que no es lo que quieres.
El primer paso para usar un WakefulBroadcastReceiver
es agregarlo a tu manifiesto, como lo harías con cualquier otro receptor de emisión:
<receiver android:name=".MyWakefulReceiver"></receiver>
El siguiente código inicia MyIntentService
con el método startWakefulService()
.
Este método puede compararse con startService()
, salvo que WakefulBroadcastReceiver
mantiene un bloqueo de activación cuando se inicia el servicio. El intent que se pasa con startWakefulService()
mantiene un extra que identifica el bloqueo de activación:
Kotlin
class MyWakefulReceiver : WakefulBroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { // Start the service, keeping the device awake while the service is // launching. This is the Intent to deliver to the service. Intent(context, MyIntentService::class.java).also { service -> WakefulBroadcastReceiver.startWakefulService(context, service) } } }
Java
public class MyWakefulReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Start the service, keeping the device awake while the service is // launching. This is the Intent to deliver to the service. Intent service = new Intent(context, MyIntentService.class); startWakefulService(context, service); } }
Cuando finaliza el servicio, este llama a MyWakefulReceiver.completeWakefulIntent()
a fin de retirar el bloqueo de activación. El método completeWakefulIntent()
tiene como parámetro el mismo intent que se pasó desde el WakefulBroadcastReceiver
:
Kotlin
const val NOTIFICATION_ID = 1 class MyIntentService : IntentService("MyIntentService") { private val notificationManager: NotificationManager? = null internal var builder: NotificationCompat.Builder? = null override fun onHandleIntent(intent: Intent) { val extras: Bundle = intent.extras // Do the work that requires your app to keep the CPU running. // ... // Release the wake lock provided by the WakefulBroadcastReceiver. MyWakefulReceiver.completeWakefulIntent(intent) } }
Java
public class MyIntentService extends IntentService { public static final int NOTIFICATION_ID = 1; private NotificationManager notificationManager; NotificationCompat.Builder builder; public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { Bundle extras = intent.getExtras(); // Do the work that requires your app to keep the CPU running. // ... // Release the wake lock provided by the WakefulBroadcastReceiver. MyWakefulReceiver.completeWakefulIntent(intent); } }