Manter o dispositivo ativado

Para evitar o descarregamento da bateria, um dispositivo Android entra em estado de suspensão rapidamente quando fica ocioso. No entanto, há momentos em que um app precisa ativar a tela ou a CPU e mantê-la ativa para concluir algum trabalho.

O método escolhido depende das necessidades do app. No entanto, uma regra geral é usar a abordagem mais leve possível para o app, a fim de minimizar o impacto nos recursos do sistema. As seções a seguir descrevem como lidar com os casos em que o comportamento de suspensão padrão do dispositivo é incompatível com os requisitos do app.

Alternativas ao uso de wake locks

Antes de oferecer o recurso de wake lock, considere se os casos de uso do seu app são compatíveis com uma das seguintes soluções alternativas:

Manter a tela ativada

Alguns apps precisam manter a tela ativada, por exemplo, os apps de jogos ou filmes. A melhor maneira de fazer isso é usar FLAG_KEEP_SCREEN_ON na sua atividade e apenas em uma atividade, nunca em um serviço ou outro componente de app. Exemplo:

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);
      }
    }
    

A vantagem dessa abordagem é que, ao contrário dos wake locks (discutidos em Manter a CPU ligada), ela não requer permissão especial, e a plataforma gerencia corretamente o usuário entre apps, sem que o app precise se preocupar em liberar recursos não utilizados.

Outra maneira de implementar isso é no arquivo XML de layout do app, usando o 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>

O uso de android:keepScreenOn="true" é equivalente a usar FLAG_KEEP_SCREEN_ON. Você pode usar a abordagem que for melhor para o app. A vantagem de definir a sinalização programaticamente na sua atividade é que ela oferece a opção de limpar a sinalização programaticamente mais tarde e, assim, permitir que a tela seja desativada.

Observação: você só precisa apagar a sinalização FLAG_KEEP_SCREEN_ON se não quiser mais que a tela permaneça no app em execução, por exemplo, se você quiser que a tela atinja determinado período de inatividade. O gerenciador de janelas se encarrega de garantir que as coisas certas aconteçam quando o app entra em segundo plano ou retorna ao primeiro plano. Mas se você quiser limpar explicitamente a sinalização e permitir que a tela seja desativada novamente, use clearFlags(): getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON).

Manter a CPU ligada

Se você precisa manter a CPU ligada para concluir algum trabalho antes que o dispositivo entre suspensão, pode usar um recurso de serviço do sistema PowerManager chamado "wake locks". Os wake locks permitem que o app controle o estado de atividade do dispositivo host.

Criar e manter wake locks pode ter um impacto significativo na duração da bateria do dispositivo host. Portanto, use wake locks apenas quando estritamente necessário e os mantenha pelo menor tempo possível. Por exemplo, o ideal seria nunca precisar usar um wake lock em uma atividade. Conforme descrito acima, se você quiser manter a tela ativada na sua atividade, use FLAG_KEEP_SCREEN_ON.

Um caso legítimo de uso de um wake lock pode ser um serviço em segundo plano que precisa pegar um wake lock para manter a CPU funcionando enquanto a tela está desligada. Novamente, essa prática precisa ser minimizada por causa do impacto sobre a duração da bateria.

Para usar um wake lock, a primeira etapa é adicionar a permissão WAKE_LOCK ao arquivo de manifesto do seu app:

<uses-permission android:name="android.permission.WAKE_LOCK" />

Se o app inclui um broadcast receiver que usa um serviço para fazer algum trabalho, você pode gerenciar seu wake lock por meio de um WakefulBroadcastReceiver, conforme descrito em Usar um broadcast receiver que mantenha o dispositivo ativado. Essa é a abordagem preferida. Se o app não seguir esse padrão, veja como definir um wake lock diretamente:

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 liberar o wake lock, chame wakelock.release(). Isso libera sua reivindicação para a CPU. É importante liberar um wake lock assim que o app terminar de usá-lo para evitar o esgotamento da bateria.

Usar um broadcast receiver que mantenha o dispositivo ativado

O uso de um broadcast receiver com um serviço permite que você gerencie o ciclo de vida de uma tarefa em segundo plano.

Um WakefulBroadcastReceiver é um tipo especial de broadcast receiver que se encarrega de criar e gerenciar um PARTIAL_WAKE_LOCK para o app. Um WakefulBroadcastReceiver passa o trabalho para um Service, normalmente um IntentService, garantindo que o dispositivo não volte a entrar em suspensão na transição. Se você não mantiver um wake lock ao fazer a transição do trabalho para um serviço, estará efetivamente permitindo que o dispositivo entre novamente em suspensão antes que o trabalho seja concluído. O resultado é que o app pode não concluir o trabalho até algum momento arbitrário no futuro, que não é o que você quer.

A primeira etapa para usar um WakefulBroadcastReceiver é adicioná-lo ao seu manifesto, como acontece com qualquer outro broadcast receiver:

<receiver android:name=".MyWakefulReceiver"></receiver>

O código a seguir inicia MyIntentService com o método startWakefulService(). Esse método é comparável a startService(), exceto pelo fato de que WakefulBroadcastReceiver está mantendo um wake lock quando o serviço é iniciado. O intent que é transmitido com startWakefulService() contém um extra que identifica o wake lock:

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);
        }
    }
    

Quando o serviço é concluído, ele chama MyWakefulReceiver.completeWakefulIntent() para liberar o wake lock. O método completeWakefulIntent() tem como parâmetro o mesmo intent transmitido de 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);
        }
    }