DEV Community

Murillo Alves da Silva
Murillo Alves da Silva

Posted on

Garantindo a execução de alarmes no modo soneca do Android

Introdução
A medida que novas versões do android vão surgindo, são criadas diferentes formas de trabalho e acesso aos dados do ecossistema. Diante disso, desde a versão do android 6, a Google vem tratando a forma que os serviços são executados, visando uma boa experiência do usuário e diminuindo riscos em potenciais.
A Google também avançou em diversas ferramentas para trabalhos em segundo plano e agendamentos de tarefas, no qual podemos citar: AlarmManager, WorkManager(oficial) e Firebase JobDispatcher. A medida que crescia a popularidade dessas ferramentas, a preocupação com o consumo de bateria também aumentava.
Em suma, o consumo de bateria diminui quando estamos executando alguma tarefa em segundo plano, visto que a execução dela pode ser feita de diferentes formatos, desde uma vez a cada hora e até de maneira recorrente com poucos intervalos de 10 segundos.
Com isso, a partir do android 6, foi introduzido o modo Soneca e amplamente apoiado pela comunidade.

O que é o modo soneca?
O modo soneca no android é uma forma de economizar bateria quando o dispositivo está muito tempo sem interação, ou seja, o celular só entra neste modo quando o usuário não utiliza. Sendo assim, o modo soneca entende que o dispositivo não está sendo utilizado e barra qualquer alarme, notificação, acesso a dados remotos e até tarefas que estão sendo executados em segundo plano, mas com uma condição: Essas tarefas, alarmes, notificações e etc são executados, mas em um formato de janela e varia com o tempo.

O que são as janelas de execução?
As janelas de execução são feitas para poder executar qualquer tarefa quando o dispositivo estiver no modo soneca. A partir da janela, o sistema operacional aciona algumas tarefas e coloca em uma janela. Com isso, todas as tarefas são executadas naquele período. O problema é que a medida que uma janela é colocada, a próxima executa em um tempo maior que a outra, sendo assim, a tarefa que era para ser executada a cada 30 segundos, naquela janela ela será executada daqui a 30 minutos e prejudicando o seu aplicativo.

AlarmManager
Não vamos nos aprofundar nesse momento com o alarmManager, mas é necessário termos um breve conhecimento.
Segundo a documentação do AlarmManager, ele é uma ferramenta para realizar operações baseadas em tempo fora da vida útil do seu app. Em qualquer projeto tem seu caso de uso, mas podemos imaginar uma o conceito de despertador, mas imaginemos o seguinte: o despertador só sabe que precisa tocar todos os dias as 16:30 horas porque foi programado através do alarmManager para ele realizar aquela operação.

Problemática
Temos um seguinte problema e precisamos resolver ele urgente: Um grupo de desenvolvedores criaram um aplicativo que gerencia o tempo de estudos dos usuários. Logo após o desenvolvimento, eles notaram que aqueles estudantes que passavam um bom tempo sem o celular começaram a reclamar que o dispositivo não estava tocando no horário programado e ocasionando em tempos indevidos de estudos.
Basicamente, é isso que vamos resolver aqui. Será criado uma solução para este problema e fazer com que o dispositivo toque mesmo no modo soneca.

Implementação
Criando um projeto android, iremos criar a classe IdleAlarmManager, onde fica responsável por configurar toda ação de alarme do aplicativo.

  • IdleAlarmManager
class IdleAlarmManager(context: Context) {
    private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    private val alarmIntent =
        PendingIntent.getBroadcast(context, 0, Intent(context, AlarmReceiver::class.java), 0)
    private val calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, SUA_HORA)
        set(Calendar.MINUTE, SEU_MINUTO)
    }

    fun setAlarmManager() = alarmManager.setExactAndAllowWhileIdle (
        AlarmManager.RTC_WAKEUP,
        calendar.timeInMillis,
        alarmIntent
    )
}
Enter fullscreen mode Exit fullscreen mode

Explicação do código acima:

  • É criado uma pendingIntent e passando qual broadcast receiver deve ser chamado quando o alarme for disparado.
  • O objeto calendar é utilizado para definir o horário que o alarme será disparado.
  • A parte mais importante do código: O metódo setAlarmManager() faz a configuração do alarme e a sua característica, no nosso caso, um alarme que dispara mesmo no modo soneca.
    • o metódo setExactAndAllowWhileIdle() garante que o alarme será chamado mesmo quando estiver no modo soneca, diferente do setAndAllowWhileIdle(), no qual não tem total garantia. Este método exato ou o outro deve ser chamado para que o alarme seja tocado. Se for usado qualquer outro método, só será disparado quando o dispositivo sair do modo soneca. Lembrando que ambos podem haver atrasos em disparar esse alarme.

Depois, para mostrar a notificação após o alarme ser executado, será necessário criar um broadcast receiver e registrar no manifest. O broadcast receiver permite que um aplicativo receba mensagens ou "broadcasts" do sistema operacional ou de outros aplicativos, mesmo quando o aplicativo não está em execução. O metódo onReceive() é chamado toda vez que recebe uma mensagem do sistema.

  • Android Manifest
        <receiver android:name="com.github.tumusx.idle.alarmManager.AlarmReceiver"/>
Enter fullscreen mode Exit fullscreen mode
  • AlarmReceiver
class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
            val builder = NotificationCompat.Builder(context, SEU_CHANNEL_ID)
                .setSmallIcon(R.drawable.notification)
                .setContentTitle(SEU_TITULO)
                .setContentText(SUA_MENSAGEM)
                .setStyle(
                    NotificationCompat.BigTextStyle()
                        .bigText(SUA_MENSAGEM)
                )
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            val name = SEU_NOME
            val description = SUA_DESCRICAO
            val channelNotification = NotificationChannel(
                SEU_CHANNEL_ID,
                name,
                NotificationManager.IMPORTANCE_HIGH,
            ).also {
                it.description = description
            }
            val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channelNotification)
            notificationManager.notify(SEU_CHANNEL_ID, builder.build())
}

Enter fullscreen mode Exit fullscreen mode

Não irei entrar em detalhes sobre o código acima pois não é o objetivo tratar notificações, mas em resumo, toda vez que o alarme for disparado, será construído e mostrado uma notificação para o usuário.

NÃO É POSSÍVEL ALTERAR PROGRAMATICAMENTE O MODO SONECA NO ANDROID

Para testar se realmente o alarme está sendo despertado no modo soneca, é necessário executar alguns comandos para forçar o dispositivo a entrar no modo soneca:
Com o seguinte caminho, abra o cmd:

C:\Users\SEU_USER\AppData\Local\Android\Sdk\platform-tools>

  1. adb shell dumpsys deviceidle enable: Força a entrar em modo econômico;
  2. adb shell dumpsys deviceidle force-idle: Força a entrar em modo soneca;
  3. adb shell dumpsys deviceidle get deep: Checa o estado atual do dispositivo. Se retornar a mensagem IDLE, ele está em modo soneca;
  4. adb shell dumpsys deviceidle unforce: Remove o dispositivo do modo soneca.

Caso queira consultar o repositório, se encontra no github.

Considerações finais
Como foi mostrado, é possível sim executar alguns processos mesmo quando o dispositivo estiver no modo soneca. Lembrando que o uso dessa implementação, o modo soneca não é desativado. Apenas garante que o alarme seja disparado quando estiver neste modo.
Existe outra solução para o problema, mas é utilizado o Firebase Cloud Messaging e a notificação tem que ser definida como prioridade alta.

Contate-me
Linkedin

Top comments (2)

Collapse
 
elielmoreno1987 profile image
elielmoreno1987

Bom dia
sou iniciante, estou fazendo um alarme com notificação e estou com um problema
quando minimizo o app ou desligo a tela por muito tempo ele não desperta,
como e um despertador para tomar remédio, eu preciso que desperte na hora exata,
já fiz de vários jeitos diferente, você poderia me ajudar?

Collapse
 
tumusx profile image
Murillo Alves da Silva

Opa. boa noite
Como você está implementando o seu alarmManager? Verificou se está com modo economia de bateria ligado? Ou até melhor, está usando o setExactAndAllowWhileIdle, assim como no tutorial?