Nota: Tentei várias soluções descritas aqui no StackOverflow (exemplo aqui ). Não feche isso sem verificar se sua solução do que você encontrou funciona usando o teste que escrevi abaixo.
fundo
Há um requisito no aplicativo, que o usuário defina um lembrete para ser agendado em um horário específico; portanto, quando o aplicativo é acionado nesse momento, ele faz algo minúsculo em segundo plano (apenas algumas operações de consulta ao banco de dados) e mostra um notificação simples, para contar sobre o lembrete.
No passado, usei um código simples para definir algo a ser agendado em um horário relativamente específico:
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
when {
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> alarmManager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
//do something in the real app
}
}
Uso:
val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
setAlarm(this, timeToTrigger, 1)
O problema
Agora testei esse código em emuladores nas novas versões do Android e no Pixel 4 com Android 10, e ele não parece ser acionado, ou talvez seja acionado após muito tempo desde o que eu forneço. Estou ciente do péssimo comportamento que alguns OEMs adicionaram para remover aplicativos das tarefas recentes, mas este é usado para emuladores e dispositivos Pixel 4 (padrão).
Eu li os documentos sobre a configuração de um alarme, que ficou restrito a aplicativos para que não ocorra com muita frequência, mas isso não explica como definir um alarme em um horário específico e não explica como é que o aplicativo Clock do Google consegue fazer isso?
Não apenas isso, mas de acordo com o que entendi, ele diz que as restrições devem ser aplicadas especialmente para o estado de baixa energia do dispositivo, mas no meu caso, eu não tinha esse estado, tanto no dispositivo como nos emuladores. Eu configurei os alarmes para serem acionados daqui a um minuto.
Como muitos aplicativos de despertador não funcionam mais como costumavam, acho que falta algo nos documentos. Exemplo desses aplicativos é o popular aplicativo Timely, que foi comprado pelo Google, mas nunca recebeu novas atualizações para lidar com as novas restrições, e agora os usuários o desejam de volta. . No entanto, alguns aplicativos populares funcionam bem, como este .
O que eu tentei
Para testar se o alarme funciona, eu realizo esses testes ao tentar disparar o alarme dentro de um minuto, depois de instalar o aplicativo pela primeira vez, enquanto o dispositivo estiver conectado ao PC (para ver os registros):
- Teste quando o aplicativo está em primeiro plano, visível para o usuário. - levou 1-2 minutos.
- Teste quando o aplicativo foi enviado para segundo plano (usando o botão de início, por exemplo) - demorou cerca de 1 minuto
- Teste quando a tarefa do aplicativo foi removida das tarefas recentes. - Esperei mais de 20 minutos e não vi o alarme sendo disparado, escrevendo nos logs.
- Como # 3, mas também desligue a tela. Provavelmente seria pior ...
Eu tentei usar as próximas coisas, nem tudo funciona:
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
combinação de qualquer dos itens acima, com:
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)
Tentei usar um serviço em vez de BroadcastReceiver. Também tentei em um processo diferente.
Tentei fazer com que o aplicativo fosse ignorado pela otimização da bateria (não ajudou), mas como outros aplicativos não precisam, também não devo usá-lo.
Tentei usar isso:
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
- Tentei ter um serviço que terá um gatilho de onTaskRemoved , para remarcar o alarme lá, mas isso também não ajudou (o serviço funcionou bem).
Quanto ao aplicativo Clock do Google, não vi nada de especial nele, exceto que ele mostra uma notificação antes de ser acionada e também não a vejo na seção "não otimizado" da tela de configurações de otimização da bateria.
Vendo que isso parece um bug, relatei sobre isso aqui , incluindo um projeto de amostra e um vídeo para mostrar o problema.
Eu verifiquei várias versões do emulador e parece que esse comportamento começou na API 27 (Android 8.1 - Oreo). Observando os documentos , não vejo o AlarmManager sendo mencionado, mas foi escrito sobre vários trabalhos em segundo plano.
As questões
Como definimos algo para ser acionado em um horário relativamente exato hoje em dia?
Como as soluções acima não funcionam mais? Estou faltando alguma coisa? Permissão? Talvez eu deva usar um trabalhador em vez disso? Mas então, isso não significaria que talvez não fosse acionado a tempo?
Como o aplicativo "Clock" do Google supera tudo isso e é acionado de qualquer maneira na hora exata, sempre, mesmo que tenha sido acionado há apenas um minuto? É apenas porque é um aplicativo do sistema? E se ele for instalado como um aplicativo de usuário, em um dispositivo que não o tenha incorporado?
Se você diz que é por ser um aplicativo do sistema, encontrei outro aplicativo que pode disparar um alarme duas vezes em 2 minutos, aqui , embora eu ache que às vezes possa usar um serviço em primeiro plano.
EDIT: criou um pequeno repositório Github para experimentar idéias, aqui .
EDIT: finalmente encontrei uma amostra que é de código aberto e não tem esse problema. Infelizmente, é muito complexo e ainda tento descobrir o que o torna tão diferente (e qual é o código mínimo que devo adicionar ao meu POC) que permite que seus alarmes permaneçam agendados após a remoção do aplicativo das tarefas recentes
fonte
Respostas:
Não temos nada para fazer.
Quando seu aplicativo não estiver na lista de permissões, ele sempre será eliminado depois de removido dos aplicativos recentes.
Porque o fabricante original do equipamento (OMEs) viola constantemente a conformidade com o Android .
Portanto, se o seu aplicativo não estiver na lista de permissões do Manufacture do dispositivo, ele não acionará nenhum trabalho em segundo plano, nem alarmes - caso seu aplicativo seja removido dos aplicativos recentes.
Você pode encontrar uma lista de dispositivos com esse comportamento aqui. TAMBÉM pode encontrar uma solução secundária. No entanto, não funcionará bem.
fonte
Encontrei uma solução alternativa estranha (exemplo aqui ) que parece funcionar para todas as versões, incluindo até o Android R:
No Android R, você também terá que conceder. Antes, parece que não precisa ser concedido, apenas declarado. Não sei por que isso mudou no R, mas posso dizer que o SAW pode ser necessário como uma possível solução para iniciar as coisas em segundo plano, conforme escrito aqui para o Android 10.
FakeActivity.kt
Você também pode tornar esta atividade quase invisível para o usuário usando este tema:
Infelizmente, esta é uma solução alternativa estranha. Espero encontrar uma solução melhor para isso.
A restrição fala sobre o início do Activity, então minha ideia atual é que, talvez, se eu iniciar um serviço em primeiro plano por uma fração de segundo, ele também ajudará, e por isso nem precisarei da permissão SAW.
EDIT: OK, tentei com um serviço de primeiro plano (exemplo aqui ), e não funcionou. Não faço ideia por que uma Atividade está funcionando, mas não um serviço. Eu até tentei remarcar o alarme lá e tentei deixar o serviço ficar um pouco, mesmo depois de re-agendar. Também tentei um serviço normal, mas é claro que foi fechado imediatamente, pois a tarefa foi removida e não funcionou (mesmo que eu tenha criado um thread para executar em segundo plano).
Outra solução possível que eu não tentei é ter um serviço em primeiro plano para sempre, ou pelo menos até a tarefa ser removida, mas isso é um pouco estranho e não vejo os aplicativos que mencionei.
EDIT: tentou ter um serviço em primeiro plano em execução antes da remoção da tarefa do aplicativo e, um pouco depois, e o alarme ainda funcionava. Também tentou que esse serviço fosse o responsável pelo evento removido pela tarefa e se fechasse imediatamente quando ocorresse, e ainda funcionou (exemplo aqui ). A vantagem desta solução alternativa é que você não precisa ter a permissão SAW. A desvantagem é que você tem um serviço com uma notificação enquanto o aplicativo já está visível para o usuário. Gostaria de saber se é possível ocultar a notificação enquanto o aplicativo já está em primeiro plano por meio da Atividade.
EDIT: Parece que é um bug no Android Studio (relatado aqui , incluindo vídeos comparando versões). Quando você inicia o aplicativo a partir da versão problemática que eu tentei, isso poderia fazer com que os alarmes fossem limpos.
Se você iniciar o aplicativo a partir do iniciador, ele funcionará bem.
Este é o código atual para definir o alarme:
Nem preciso usar "pendingShowList". Usar nulo também está ok.
fonte
SYSTEM_ALERT_WINDOW
permissão?Intent.FLAG_RECEIVER_FOREGROUND
bandeira.https://developer.android.com/about/versions/oreo/background#broadcasts
setExactAndAllowWhileIdle()
ao segmentar a API 23+.https://developer.android.com/about/versions/oreo/background#migration
fonte
Sei que isso não é eficiente, mas pode ser mais consistente com uma precisão de 60 segundos.
https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK
se esse receptor de transmissão for usado dentro de um serviço em primeiro plano, você poderá verificar a hora a cada minuto e tomar uma decisão sobre a ação.
fonte
Acho que você pode pedir ao usuário que defina a permissão para desativar o modo de economia de energia e avisar ao usuário que, se ele não usá-lo, os horários exatos não serão alcançados.
Aqui está o código para solicitá-lo:
fonte
Eu sou o autor do projeto de código aberto que você mencionou na sua pergunta ( despertador simples) .
Estou surpreso que o uso de AlarmManager.setAlarmClock não funcionou para você, porque meu aplicativo faz exatamente isso. O código está no arquivo AlarmSetter.kt. Aqui está um trecho:
Basicamente, não é nada de especial, apenas certifique-se de que a intenção tenha uma ação e uma classe de destino, que é um receptor de transmissão no meu caso.
fonte