Jak ustawić alarm, który ma być zaplanowany dokładnie po wszystkich najnowszych ograniczeniach na Androidzie?

27

Uwaga: Wypróbowałem różne rozwiązania, o których pisano tutaj na StackOverflow (przykład tutaj ). Nie zamykaj tego, nie sprawdzając, czy twoje rozwiązanie z tego, co znalazłeś, działa przy użyciu testu, który napisałem poniżej.

tło

W aplikacji jest wymagane, aby użytkownik ustawił przypomnienie, które ma być zaplanowane o określonej godzinie, więc gdy aplikacja zostanie uruchomiona w tym czasie, robi coś małego w tle (tylko niektóre operacje zapytania DB) i pokazuje proste powiadomienie, aby opowiedzieć o przypomnieniu.

W przeszłości korzystałem z prostego kodu, aby ustawić coś do zaplanowania we względnie określonym czasie:

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

Stosowanie:

            val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
            setAlarm(this, timeToTrigger, 1)

Problem

Testowałem teraz ten kod na emulatorach na nowych wersjach Androida i na Pixelu 4 z Androidem 10 i wydaje się, że nie uruchamia się, a może uruchamia się po bardzo długim czasie od czasu, gdy go dostarczam. Zdaję sobie sprawę z okropnego zachowania, które niektórzy producenci OEM dodali do usuwania aplikacji z ostatnich zadań, ale ten dotyczy zarówno emulatorów, jak i urządzenia Pixel 4 (w magazynie).

Przeczytałem o dokumentach dotyczących ustawiania alarmu, że został on ograniczony do aplikacji, aby nie pojawiał się zbyt często, ale to nie wyjaśnia, jak ustawić alarm w określonym czasie i nie wyjaśnia jak to się dzieje, że aplikacja Google Clock osiąga sukces?

Nie tylko to, ale zgodnie z tym, co rozumiem, mówi, że ograniczenia powinny być stosowane szczególnie w przypadku stanu niskiego poboru mocy urządzenia, ale w moim przypadku nie miałem tego stanu, zarówno na urządzeniu, jak i na emulatorach. Ustawiłem uruchamianie alarmów za około minutę.

Widząc, że wiele aplikacji budzika nie działa już tak, jak kiedyś, myślę, że czegoś brakuje w dokumentacji. Przykładem takich aplikacji jest popularna aplikacja Timely, która została kupiona przez Google, ale nigdy nie otrzymała nowych aktualizacji w celu obsługi nowych ograniczeń, a teraz użytkownicy chcą ją z powrotem. . Jednak niektóre popularne aplikacje działają dobrze, takie jak ta .

Co próbowałem

Aby sprawdzić, czy rzeczywiście działa alarm, wykonuję te testy, gdy próbuję wyzwolić alarm za minutę po zainstalowaniu aplikacji po raz pierwszy, wszystko po podłączeniu urządzenia do komputera (aby wyświetlić dzienniki):

  1. Testuj, gdy aplikacja jest na pierwszym planie, widoczna dla użytkownika. - zajęło 1-2 minuty.
  2. Sprawdź, kiedy aplikacja została wysłana w tło (na przykład za pomocą przycisku Home) - zajęło to około 1 minuty
  3. Sprawdź, kiedy zadanie aplikacji zostało usunięte z ostatnich zadań. - Czekałem ponad 20 minut i nie widziałem wyzwolenia alarmu, piszącego do dzienników.
  4. Jak # 3, ale także wyłącz ekran. Prawdopodobnie byłoby gorzej ...

Próbowałem użyć następnych rzeczy, wszystko nie działa:

  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. połączenie któregokolwiek z powyższych, z:

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

  5. Próbowałem użyć usługi zamiast BroadcastReceiver. Wypróbowałem też inny proces.

  6. Próbowałem zignorować aplikację z optymalizacji baterii (nie pomogło), ale ponieważ inne aplikacje jej nie potrzebują, ja też nie powinienem jej używać.

  7. Próbowałem użyć tego:

            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
                alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
            AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
  1. Próbowałem mieć usługę, która będzie wyzwalana przez onTaskRemoved , aby ponownie ustawić tam alarm, ale to też nie pomogło (usługa działała dobrze).

Jeśli chodzi o aplikację Google Clock, nie widziałem w niej nic specjalnego poza tym, że wyświetla powiadomienie przed uruchomieniem, a także nie widzę go w sekcji „niezoptymalizowanej” ekranu ustawień optymalizacji baterii.

Widząc, że to wygląda na błąd, zgłosiłem to tutaj , w tym przykładowy projekt i wideo pokazujące problem.

Sprawdziłem wiele wersji emulatora i wygląda na to, że takie zachowanie zaczęło się od API 27 (Android 8.1 - Oreo). Patrząc na dokumenty , nie widzę wzmianki o AlarmManager, ale napisano o różnych pracach w tle.

Pytania

  1. Jak ustawić coś, co ma być teraz uruchamiane we względnie dokładnym czasie?

  2. Dlaczego powyższe rozwiązania już nie działają? Czy coś mi brakuje? Pozwolenie? Może powinienem zamiast tego użyć Robotnika? Ale czy to nie znaczy, że może wcale nie zadziałać na czas?

  3. W jaki sposób aplikacja Google „Zegar” przezwycięża to wszystko i zawsze uruchamia się dokładnie o określonej godzinie, nawet jeśli została uruchomiona jeszcze minutę temu? Czy tylko dlatego, że jest to aplikacja systemowa? Co się stanie, jeśli zostanie zainstalowany jako aplikacja użytkownika na urządzeniu, które nie ma wbudowanej aplikacji?

Jeśli powiesz, że to dlatego, że jest to aplikacja systemowa, Znalazłem inną aplikację, która może wywołać alarm dwukrotnie w ciągu 2 minut, tutaj , choć myślę, że może skorzystać z usługi pierwszoplanowy czasami.

EDYCJA: utworzyłem małe repozytorium Github do przetestowania pomysłów tutaj .


EDYCJA: w końcu znalazłem próbkę, która jest zarówno otwarta, jak i nie ma tego problemu. Niestety jest to bardzo skomplikowane i wciąż próbuję dowiedzieć się, co czyni go tak różnym (i jaki jest minimalny kod, który powinienem dodać do mojego POC), który pozwala, aby jego alarmy pozostały zaplanowane po usunięciu aplikacji z ostatnich zadań

programista Androida
źródło
dawno pracowałem nad usługą (nie jestem nawet pro programistą, aby to zasugerować), ale mogę zasugerować, aby uniknąć alarmuManager w przypadkach takich jak ustawienie alarmu poniżej 5 minut, ponieważ z powodu ograniczeń Androida po usłudze czasu działającej w backend jest wywoływany co 5 minut lub dłużej, nie mniej niż 5 minut. Zamiast tego użyłem Handlera. Aby uruchomić moją usługę, działam
Blu
Jakie są dokładne ograniczenia? Jaki jest minimalny czas, który gwarantuje, że spust zadziała w stosunkowo precyzyjnym czasie?
programista Androida
Nie pamiętam dokładnych ograniczeń, ale kiedy nad tym pracowałem, przeglądałem go przez kilka dni, aby automatycznie zabić serwis działający w tle. I z osobistych obserwacji zauważyłem problem na Samsungu, Xiaomi itp., Nie można wywołać alarmManger w odstępie 5 minut, miałem usługę przesyłania danych wdrożoną za pomocą alarmManger, która jest uruchamiana co 1 minutę, ale rozczarował naszego klienta, który narzekał na usługę wcale nie działa. W przypadku emulatorów działa dobrze.
Blu
Wiem, że w Androidzie Q nie można rozpocząć aktywności od tła, ale nie wygląda to tak, jak w twoim przypadku.
marcinj
@ greeble31 Próbowałem teraz. Które rozwiązanie tam działa? Z jakiegoś powodu nadal nie działam. Ustawiam alarm, usuwam aplikację z ostatnich zadań i nie widzę wyzwalanego alarmu, nawet jeśli ekran jest włączony, a urządzenie jest podłączone do ładowarki. Zdarza się to zarówno na prawdziwym urządzeniu (Pixel 4 z Androidem 10), jak i na emulatorze (na przykład API 27). Czy Ci to pasuje? Czy możesz podać pełny kod? Może w Github?
programista Androida

Odpowiedzi:

4

Nie mamy nic do roboty.

Gdy aplikacja nie znajduje się na białej liście, zawsze zostanie zabita po usunięciu z najnowszych aplikacji.

Ponieważ producent oryginalnego sprzętu (OME) stale narusza zgodność z Androidem .

Więc jeśli twoja aplikacja nie znajduje się na białej liście od urządzenia Wyprodukuj, nie uruchomi żadnej pracy w tle, nawet alarmów - w przypadku, gdy Twoja aplikacja zostanie usunięta z najnowszych aplikacji.

Możesz znaleźć listę urządzeń o takim zachowaniu tutaj TAKŻE możesz znaleźć rozwiązanie poboczne, jednak to nie zadziała dobrze.

Ibrahim Ali
źródło
Jestem w pełni świadomy tego problemu chińskich producentów OEM. Ale jak napisałem, dzieje się to nawet na emulatorze i urządzeniu Pixel 4. Nie jest to jakiś chiński producent OEM. Sprawdź emulator i / lub urządzenie Pixel. Problem też tam istnieje. Ustaw alarm, usuń aplikację z ostatnich zadań i sprawdź, czy alarm nie został uruchomiony. Widzę to jako błąd i zgłosiłem tutaj (zawiera film i przykładowy projekt, jeśli chcesz spróbować): Issuetracker.google.com/issues/149556385. Zaktualizowałem moje pytanie, aby było jasne. Pytanie, w jaki sposób niektóre aplikacje się powiodły.
programista Androida
@androiddeveloper Uważam, że powinien działać na emulatorze. Który emulator posiadasz?
Ibrahim Ali
Wierzyłem też, dopóki nie spróbowałem. Wystarczy wypróbować na przykład API 29 tego, co ma do zaoferowania Android Studio. Jestem pewien, że to samo wydarzy się również w nieco starszych wersjach.
programista Androida
4

Znaleziono dziwne obejście (przykład tutaj ), które wydaje się działać we wszystkich wersjach, w tym nawet na Androida R:

  1. Czy pozwolenie SAW zostało zadeklarowane w manifeście:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

W Androidzie R trzeba będzie również to przyznać. Wcześniej nie wydaje się, że należy to przyznać, po prostu zadeklarować. Nie jestem pewien, dlaczego zmieniło się to na R, ale mogę powiedzieć, że SAW może być wymagane jako możliwe rozwiązanie do uruchamiania w tle, jak napisano tutaj dla Androida 10.

  1. Mieć usługę, która wykryje, kiedy zadania zostały usunięte, a kiedy to zrobi, otwórz fałszywe działanie, które wystarczy, aby się zamknąć:
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()
    }
}

Możesz również uczynić tę aktywność prawie niewidoczną dla użytkownika za pomocą tego motywu:

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

Niestety jest to dziwne obejście. Mam nadzieję znaleźć lepsze rozwiązanie tego problemu.

Ograniczenie mówi o rozpoczęciu działania, więc moim obecnym pomysłem jest to, że jeśli uruchomię usługę pierwszoplanową na ułamek sekundy, to również pomoże, a do tego nie potrzebuję nawet zgody SAW.

EDYCJA: OK Próbowałem z usługą pierwszego planu (próbka tutaj ) i to nie działało. Nie mam pojęcia, dlaczego działanie działa, ale nie usługa. Próbowałem nawet zmienić harmonogram alarmu i starałem się, aby usługa pozostała przez jakiś czas, nawet po ponownym harmonogramie. Próbowałem także normalnej usługi, ale oczywiście została ona natychmiast zamknięta, ponieważ zadanie zostało usunięte i w ogóle nie działało (nawet jeśli utworzyłem wątek do uruchomienia w tle).

Innym możliwym rozwiązaniem, którego nie próbowałem, jest posiadanie usługi pierwszoplanowej na zawsze, a przynajmniej do momentu usunięcia zadania, ale jest to trochę dziwne i nie widzę aplikacji, o których wspominałem.

EDYCJA: próbowałem uruchomić usługę pierwszoplanową przed usunięciem zadania aplikacji i jeszcze chwilę później, a alarm nadal działał. Próbowałem także, aby ta usługa była odpowiedzialna za zdarzenie usuwające zadanie i aby zamknęła się od razu, gdy wystąpi, i nadal działało (przykład tutaj ). Zaletą tego obejścia jest to, że wcale nie musisz mieć uprawnienia SAW. Wadą jest to, że masz usługę z powiadomieniem, gdy aplikacja jest już widoczna dla użytkownika. Zastanawiam się, czy można ukryć powiadomienie, gdy aplikacja jest już na pierwszym planie za pośrednictwem działania.


EDYCJA: Wydaje się, że jest to błąd w Android Studio (zgłaszany tutaj , w tym filmy porównujące wersje). Po uruchomieniu aplikacji z problematycznej wersji, którą próbowałem, może to spowodować skasowanie alarmów.

Jeśli uruchomisz aplikację z poziomu programu uruchamiającego, działa dobrze.

To jest aktualny kod do ustawienia alarmu:

        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)

Nie muszę nawet używać „pendingShowList”. Używanie wartości null jest również w porządku.

programista Androida
źródło
Chcę tylko rozpocząć działanie onReceive () na AndroidQ. Czy istnieją jakieś obejścia tego bez SYSTEM_ALERT_WINDOWzgody?
doctorram
2
Dlaczego Google zawsze sprawia, że ​​życie deweloperów Androida jest prostym piekłem ?!
doctorram
@doctorram Tak, jest napisane w dokumentach dotyczących różnych wyjątków: developer.android.com/guide/components/activities/… . Właśnie wybrałem SYSTEM_ALERT_WINDOW, ponieważ najłatwiej to przetestować.
programista Androida
Czy od czasu ostatniej edycji masz na myśli, że teraz nie musimy używać wspomnianych obejść, aby wspomnieć o utrzymywaniu alarmów po usunięciu aplikacji z ostatniej listy?
Pradeepkumar Reddy
Chcę uruchamiać fragment kodu codziennie między 6 rano a 7 rano w tle, nawet jeśli aplikacja zostanie usunięta z ostatniej listy. Powinienem użyć WorkManagera lub AlarmManagera? Próbowałem następującego kodu dla mojej skrzynki użytkownika i nie zadziałało. Na czym polega problem z poniższym kodem? calendar.setTimeInMillis (System.currentTimeMillis ()); zestaw kalendarza (Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, pendingIntent);
Pradeepkumar Reddy
1
  1. Upewnij się, że zamiar, który transmitujesz, jest wyraźny i ma Intent.FLAG_RECEIVER_FOREGROUNDflagę.

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. Służy do setExactAndAllowWhileIdle()kierowania na interfejs 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. Uruchom alarm jako usługę pierwszego planu:

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. I nie zapomnij uprawnień:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Maksim Iwanow
źródło
Dlaczego ma to znaczenie dla usługi, jeśli sam BroadcastReceiver w ogóle nie ma zamiaru (lub w najbliższym czasie)? To pierwszy krok ... Czy AlarmManagerCompat nie oferuje już tego samego kodu? Czy próbowałeś tego za pomocą testów, które napisałem, w tym usunięcia aplikacji z ostatnich zadań? Czy możesz pokazać cały kod? Może udostępnisz na Github?
programista Androida
@androiddeveloper zaktualizował odpowiedź.
Maksim Iwanow
Nadal nie działa. Oto przykładowy projekt: ufile.io/6qrsor7o . Spróbuj na Androidzie 10 (emulator również działa), ustaw alarm i usuń aplikację z ostatnich zadań. Jeśli nie usuniesz z ostatnich zadań, działa dobrze i uruchamia się po 10 sekundach.
programista Androida
Zaktualizowałem również pytanie, aby mieć link do raportu o błędzie, który zawiera przykładowy projekt i film, ponieważ myślę, że to błąd, ponieważ nie widzę żadnego innego powodu.
programista Androida
0

Wiem, że to nie jest wydajne, ale może być bardziej spójne z dokładnością 60 sekund.

https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK

jeśli ten odbiornik rozgłoszeniowy jest używany w ramach usługi pierwszego planu, możesz sprawdzać godzinę co minutę i podejmować decyzję o podjęciu działania.

Szorstki
źródło
Jeśli mam usługę pierwszego planu, dlaczego miałbym jej potrzebować? Mógłbym po prostu użyć Handler.postDelayed lub innego podobnego rozwiązania, jeśli chcę ...
programista Androida
0

Myślę, że możesz poprosić użytkownika o ustawienie uprawnienia, aby wyłączało tryb oszczędzania energii i ostrzec użytkownika, że ​​jeśli go nie użyje, dokładne czasy nie zostaną osiągnięte.

Oto kod, aby o to poprosić:

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);
użytkownik2638180
źródło
Próbowałem już tego, ponieważ zauważyłem, że żadna inna aplikacja tego nie robi i byłem ciekawy, czy może pomóc. Nie działało Zaktualizowane pytanie.
programista Androida
0

Jestem autorem projektu open source, o którym wspomniałeś w swoim pytaniu ( prosty budzik) .

Dziwi mnie, że użycie AlarmManager.setAlarmClock nie zadziałało, ponieważ moja aplikacja właśnie to robi. Kod znajduje się w pliku AlarmSetter.kt. Oto fragment:

  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)

Zasadniczo nie jest to nic specjalnego, tylko upewnij się, że zamiar ma akcję i klasę docelową, która w moim przypadku jest odbiornikiem transmisji.

Jurij Kulikow
źródło
Niestety nie zadziałało. Właśnie tego próbowałem. Zobacz pliki tutaj: github.com/yuriykulikov/AlarmClock/issues/…
programista Androida
Sprawdziłem Twój kod na GitHub. Odbiornik rozgłoszeniowy działa po usunięciu aplikacji z ostatnich w Moto Z2 Play. Mogę wypróbować go na Pixelu, ale kod wydaje mi się odpowiedni. Wymuszone zatrzymanie aplikacji usuwa zaplanowany alarm, ale stanie się tak w przypadku każdej aplikacji, która zostanie zatrzymana wymuszona.
Jurij Kulikow
Pokazałem już wiele razy: wszystko, co robię po zaplanowaniu, to usunięcie z ostatnich zadań. Zrobiłem to zarówno na emulatorze, jak i na Pixelu 4.
programista Androida
Sprawdź, czy korzystanie z PendingShowList powoduje obejście problemu, a jeśli tak, zaktualizuję odpowiedź. Może przyda się komuś.
Jurij Kulikow