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?