Como definir um alarme para ser agendado no horário exato após todas as restrições mais recentes no Android?

27

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):

  1. Teste quando o aplicativo está em primeiro plano, visível para o usuário. - levou 1-2 minutos.
  2. Teste quando o aplicativo foi enviado para segundo plano (usando o botão de início, por exemplo) - demorou cerca de 1 minuto
  3. 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.
  4. Como # 3, mas também desligue a tela. Provavelmente seria pior ...

Eu tentei usar as próximas coisas, nem tudo funciona:

  1. alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)

  2. alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  3. AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  4. combinação de qualquer dos itens acima, com:

    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)

  5. Tentei usar um serviço em vez de BroadcastReceiver. Também tentei em um processo diferente.

  6. 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.

  7. 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)
  1. 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

  1. Como definimos algo para ser acionado em um horário relativamente exato hoje em dia?

  2. 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?

  3. 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

desenvolvedor android
fonte
já faz um bom tempo que trabalho no serviço (nem mesmo um desenvolvedor profissional sugere), mas posso sugerir que você evite o alarmManager para casos como definir o alarme abaixo de 5 minutos, devido a restrições do Android após um serviço de tempo em execução no back-end é chamado após cada 5 minutos ou mais, não menos que 5 minutos. Em vez disso, usei Handler. E para executar meu serviço continua em segundo plano, consultei
Blu
Então, quais são as restrições exatas? Qual é o tempo mínimo garantido de que um gatilho funcionará em um período de tempo relativamente preciso?
desenvolvedor android
Não me lembro das restrições exatas, mas quando estava trabalhando nisso pesquisei por dias como um dia para superar o serviço em segundo plano sendo morto automaticamente. E por observação pessoal, notei um problema na Samsung, Xiaomi, etc, você não pode chamar alarmManger entre um intervalo de 5 minutos, eu tinha um serviço de upload de dados implementado usando o alarmManger que é acionado a cada 1 minuto, mas decepcionou nosso cliente que reclamou o serviço não está funcionando. Para emuladores, funciona bem.
Blu
Eu sei que você não pode iniciar a atividade em segundo plano no Android Q, mas não parece ser o seu caso.
marcinj
@ greeble31 Eu tentei agora. Qual solução você vê trabalhando? Por alguma razão, ainda não consigo fazer isso funcionar. Defino o alarme, removo o aplicativo de tarefas recentes e não vejo o alarme sendo acionado, mesmo que a tela esteja ligada e o dispositivo esteja conectado a um carregador. Isso acontece tanto em um dispositivo real (Pixel 4 com Android 10) quanto no emulador (API 27, por exemplo). Funciona para você? Você pode compartilhar o código completo? Talvez no Github?
desenvolvedor android

Respostas:

4

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.

Ibrahim Ali
fonte
Estou ciente desse problema dos OEMs chineses. Mas como eu escrevi, isso acontece mesmo no emulador e no dispositivo Pixel 4. Não é algum OEM chinês que o fez como tal. Por favor, verifique no emulador e / ou dispositivo Pixel. O problema existe lá também. Defina um alarme, remova o aplicativo de tarefas recentes e verifique se o alarme não é acionado. Vejo isso como um bug e relatado aqui (inclui um vídeo e um projeto de exemplo, se você quiser tentar): issuetracker.google.com/issues/149556385. Atualizei minha pergunta para esclarecer. A questão é como é que algum aplicativo foi bem-sucedido.
desenvolvedor android
@androiddeveloper Eu acredito que deve funcionar no emulador .. Qual emulador você tem?
Ibrahim Ali
Eu também acreditei até tentar. Experimente na API 29, por exemplo, o que o Android Studio tem a oferecer. Tenho certeza que o mesmo ocorrerá em versões um pouco mais antigas também.
desenvolvedor android
4

Encontrei uma solução alternativa estranha (exemplo aqui ) que parece funcionar para todas as versões, incluindo até o Android R:

  1. Tenha a permissão SAW permissão declarada no manifesto:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

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.

  1. Tenha um serviço que detecte quando as tarefas foram removidas e, quando o fizer, abra uma Atividade falsa que tudo o que faz é fechar-se:
class OnTaskRemovedDetectorService : Service() {
    override fun onBind(intent: Intent?) = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.e("AppLog", "onTaskRemoved")
        applicationContext.startActivity(Intent(this, FakeActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
        stopSelf()
    }

}

FakeActivity.kt

class FakeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("AppLog", "FakeActivity")
        finish()
    }
}

Você também pode tornar esta atividade quase invisível para o usuário usando este tema:

    <style name="AppTheme.Translucent" parent="@style/Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

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:

        val timeToTrigger = System.currentTimeMillis() + 10 * 1000
        val pendingShowList = PendingIntent.getActivity(this, 1, Intent(this, SomeActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        val pendingIntent = PendingIntent.getBroadcast(this, 1, Intent(this, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        manager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingShowList), pendingIntent)

Nem preciso usar "pendingShowList". Usar nulo também está ok.

desenvolvedor android
fonte
Eu só quero começar uma atividade onReceive () no AndroidQ. Existe alguma solução alternativa para isso sem a SYSTEM_ALERT_WINDOWpermissão?
doctorram 23/02
2
Por que o Google sempre faz da vida dos desenvolvedores do Android um inferno de coisas simples ?!
doctorram 23/02
@doctorram Sim, está escrito nos documentos sobre as várias exceções: developer.android.com/guide/components/activities/… . Acabei de escolher SYSTEM_ALERT_WINDOW porque é mais fácil testá-lo.
desenvolvedor android
Desde a sua última edição, você quer dizer que agora não precisamos usar nenhuma solução alternativa mencionada para manter os alarmes após remover o aplicativo da lista recente?
user3410835 21/03
Quero executar um pedaço de código todos os dias entre as 6h e as 7h em segundo plano, mesmo que o aplicativo seja removido da lista recente. Eu devo usar o WorkManager ou o AlarmManager? Tentei o seguinte código para o meu caso e não funcionou. Qual é o problema com o código abaixo? calendar.setTimeInMillis (System.currentTimeMillis ()); calendar.set (Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, pendingIntent);
user3410835 21/03
1
  1. Verifique se a intenção que você transmite é explícita e tem a Intent.FLAG_RECEIVER_FOREGROUNDbandeira.

https://developer.android.com/about/versions/oreo/background#broadcasts

Intent intent = new Intent(context, Receiver.class);
intent.setAction(action);
...
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

PendingIntent operation = PendingIntent.getBroadcast(context, 0, intent, flags);
  1. Use setExactAndAllowWhileIdle()ao segmentar a API 23+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, operation);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, operation);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, time, operation);
}
  1. Inicie seu alarme como um serviço em primeiro plano:

https://developer.android.com/about/versions/oreo/background#migration

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
} else {
    context.startService(intent);
}
  1. E não se esqueça das permissões:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Maksim Ivanov
fonte
Por que isso importa sobre o serviço, se o BroadcastReceiver em si não recebe a Intenção (ou quase o tempo todo)? Esse é o primeiro passo ... Além disso, o AlarmManagerCompat já não oferece esse mesmo código? Você já tentou isso usando os testes que escrevi, incluindo a remoção do aplicativo das tarefas recentes? Você pode mostrar o código inteiro? Talvez compartilhar no Github?
desenvolvedor android
@androiddeveloper atualizou a resposta.
Maksim Ivanov
Ainda não parece funcionar. Aqui está um exemplo de projeto: ufile.io/6qrsor7o . Experimente o Android 10 (emulador também ok), defina o alarme e remova o aplicativo das tarefas recentes. Se você não remover de tarefas recentes, ele funcionará bem e será acionado após 10 segundos.
desenvolvedor android
Também atualizei a pergunta para ter um link para um relatório de erro que inclui um projeto de amostra e vídeo, porque acho que é um erro, pois não vejo nenhum outro motivo para que isso ocorra.
desenvolvedor android
0

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.

Harsh
fonte
Se eu tenho um serviço em primeiro plano, por que precisaria disso? Eu poderia apenas usar um Handler.postDelayed ou qualquer outra solução semelhante, se desejar ...
android developer
0

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:

PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
            String packageName = "your Package name";
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Intent i = new Intent();
                if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
                    i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                    i.setData(Uri.parse("package:" + packageName));
                    startActivity(i);
user2638180
fonte
Já tentei isso, pois notei que nenhum outro aplicativo faz isso e fiquei curioso para saber se ele pode ajudar. Não funcionou. Pergunta atualizada.
desenvolvedor android
0

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:

  val pendingAlarm = Intent(ACTION_FIRED)
                .apply {
                    setClass(mContext, AlarmsReceiver::class.java)
                    putExtra(EXTRA_ID, id)
                    putExtra(EXTRA_TYPE, typeName)
                }
                .let { PendingIntent.getBroadcast(mContext, pendingAlarmRequestCode, it, PendingIntent.FLAG_UPDATE_CURRENT) }

            val pendingShowList = PendingIntent.getActivity(
                    mContext,
                    100500,
                    Intent(mContext, AlarmsListActivity::class.java),
                    PendingIntent.FLAG_UPDATE_CURRENT
            )

            am.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingShowList), pendingAlarm)

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.

Yuriy Kulikov
fonte
Infelizmente não funcionou. Foi o que eu tentei. Veja os arquivos aqui: github.com/yuriykulikov/AlarmClock/issues/…
desenvolvedor android
Fiz o check-out do seu código no GitHub. O receptor de transmissão funciona depois que o aplicativo é removido dos recentes no Moto Z2 Play. Posso experimentá-lo em um pixel, mas o código parece bom para mim. Forçar a parada do aplicativo remove o alarme agendado, mas isso acontecerá com qualquer aplicativo que seja forçado a parar.
Yuriy Kulikov
Eu já mostrei várias vezes: tudo o que faço após o agendamento é remover das tarefas recentes. E eu fiz isso no emulador e no Pixel 4.
desenvolvedor android
Verifique se o uso de pendingShowList contorna o problema e, se sim, atualizarei a resposta. Talvez seja útil para alguém.
Yuriy Kulikov