Próbuję napisać aplikację, która robi coś konkretnego, gdy po pewnym czasie zostanie przywrócona na pierwszy plan. Czy istnieje sposób na wykrycie, kiedy aplikacja zostanie wysłana w tło lub przeniesiona na pierwszy plan?
Próbuję napisać aplikację, która robi coś konkretnego, gdy po pewnym czasie zostanie przywrócona na pierwszy plan. Czy istnieje sposób na wykrycie, kiedy aplikacja zostanie wysłana w tło lub przeniesiona na pierwszy plan?
Odpowiedzi:
onPause()
I onResume()
metody są wywoływane, gdy aplikacja jest prawidłowe tle i na pierwszym planie ponownie. Są one jednak wywoływane również przy pierwszym uruchomieniu aplikacji i przed jej zabiciem. Więcej możesz przeczytać w Aktywności .
Nie ma bezpośredniego podejścia do uzyskania statusu aplikacji w tle lub na pierwszym planie, ale nawet ja napotkałem ten problem i znalazłem rozwiązanie za pomocą onWindowFocusChanged
i onStop
.
Aby uzyskać więcej informacji, sprawdź tutaj Android: Rozwiązanie pozwalające wykryć, kiedy aplikacja na Androida przechodzi w tło i wraca na pierwszy plan bez getRunningTasks lub getRunningAppProcesses .
AKTUALIZACJA z marca 2018 r . : Istnieje teraz lepsze rozwiązanie. Zobacz ProcessLifecycleOwner . Będziesz musiał korzystać z nowych komponentów architektury 1.1.0 (najnowsza w tej chwili), ale jest to konkretnie zaprojektowane do tego celu.
W tej odpowiedzi podano prosty przykład, ale napisałem przykładową aplikację i post na blogu o nim.
Odkąd napisałem to w 2014 roku, pojawiły się różne rozwiązania. Niektórzy pracowali, niektóre były uważane pracować , ale miał wady (w tym moje!), A my, jako wspólnota (Android) nauczył się żyć z konsekwencjami i napisał obejścia szczególnych przypadkach.
Nigdy nie zakładaj, że pojedynczy fragment kodu jest rozwiązaniem, którego szukasz, jest mało prawdopodobne; jeszcze lepiej, spróbuj zrozumieć, co robi i dlaczego to robi.
MemoryBoss
Klasa nigdy nie był rzeczywiście używany przeze mnie napisane, że to tylko kawałek pseudo kod, co wydarzyło się do pracy.
O ile nie ma uzasadnionego powodu, abyś nie korzystał z nowych komponentów architektury (a są takie, szczególnie jeśli celujesz w super stare api), to idź dalej i używaj ich. Są dalekie od ideału, ale żadne z nich nie było ComponentCallbacks2
.
AKTUALIZACJA / UWAGI (listopad 2015) : Ludzie piszą dwa komentarze, po pierwsze, >=
należy ich użyć zamiast, ==
ponieważ dokumentacja stwierdza, że nie należy sprawdzać dokładnych wartości . W większości przypadków jest to w porządku, ale pamiętaj, że jeśli zależy Ci tylko na zrobieniu czegoś, gdy aplikacja przeszła w tło, będziesz musiał użyć == i również połączyć to z innym rozwiązaniem (np. Wywołania zwrotne cyklu życia) lub może nie uzyskać pożądanego efektu. Przykład (i to mi się przydarzyło) jest taki, że jeśli chcesz zablokowaćTwoja aplikacja z ekranem z hasłem, gdy przechodzi w tło (np. 1 Hasło, jeśli ją znasz), możesz przypadkowo zablokować aplikację, jeśli zabraknie pamięci i nagle zaczniesz testować >= TRIM_MEMORY
, ponieważ Android uruchomi LOW MEMORY
połączenie, a to wyższy niż twój. Więc bądź ostrożny jak / co testujesz.
Ponadto niektóre osoby pytały o sposób wykrywania po powrocie.
Najprostszy sposób, jaki mogę wymyślić, wyjaśniono poniżej, ale ponieważ niektórzy ludzie nie są z tym zaznajomieni, dodam tutaj pseudo kod. Zakładając, że masz YourApplication
i MemoryBoss
klasy, w swoim class BaseActivity extends Activity
(musisz je utworzyć, jeśli go nie masz).
@Override
protected void onStart() {
super.onStart();
if (mApplication.wasInBackground()) {
// HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
mApplication.setWasInBackground(false);
}
}
Polecam onStart, ponieważ okna dialogowe mogą wstrzymywać aktywność, więc założę się, że nie chcesz, aby twoja aplikacja myślała „poszła do tła”, jeśli wszystko co zrobiłeś, to wyświetlało okno dialogowe na pełnym ekranie, ale przebieg może się różnić.
I to wszystko. Kod w bloku if zostanie wykonany tylko raz , nawet jeśli przejdziesz do innego działania, nowe (to także extends BaseActivity
) zgłosi wasInBackground
to, false
aby nie wykonało kodu, dopóki nie onMemoryTrimmed
zostanie wywołane, a flaga ponownie ustawiona na true .
Mam nadzieję, że to pomaga.
AKTUALIZACJA / UWAGI (kwiecień 2015 r.) : Zanim przejdziesz do wszystkich funkcji Kopiuj i Wklej ten kod, zauważ, że znalazłem kilka przypadków, w których może on nie być w 100% wiarygodny i musi być połączony z innymi metodami, aby uzyskać najlepsze wyniki. W szczególności istnieją dwa znane przypadki, w których onTrimMemory
nie można zagwarantować, że oddzwonienie zostanie wykonane:
Jeśli telefon blokuje ekran, gdy aplikacja jest widoczna (powiedzmy, że urządzenie blokuje się po nn minutach), to wywołanie zwrotne nie jest wywoływane (lub nie zawsze), ponieważ ekran blokady jest na samej górze, ale aplikacja jest nadal „uruchomiona”, chociaż jest zakryta.
Jeśli twoje urządzenie ma stosunkowo mało pamięci (i jest pod obciążeniem pamięci), system operacyjny wydaje się ignorować to połączenie i przechodzi bezpośrednio do bardziej krytycznych poziomów.
Teraz, w zależności od tego, jak ważne jest, aby wiedzieć, kiedy aplikacja przeszła do tła, może być konieczne rozszerzenie tego rozwiązania wraz ze śledzeniem cyklu życia aktywności i tym podobne.
Pamiętaj o tym i miej dobry zespół kontroli jakości;)
KONIEC AKTUALIZACJI
Może być późno, ale istnieje niezawodna metoda w Ice Cream Sandwich (API 14) i wyżej .
Okazuje się, że gdy aplikacja nie ma już widocznego interfejsu użytkownika, wywoływane jest oddzwanianie. Oddzwonienie, które można zaimplementować w klasie niestandardowej, nazywa się ComponentCallbacks2 (tak, z dwójką). To wywołanie zwrotne jest dostępne tylko na poziomie API 14 (Ice Cream Sandwich) i wyższym.
Zasadniczo otrzymujesz wywołanie metody:
public abstract void onTrimMemory (int level)
Poziom wynosi 20 lub więcej
public static final int TRIM_MEMORY_UI_HIDDEN
Testowałem to i zawsze działa, ponieważ poziom 20 to tylko „sugestia”, że możesz chcieć zwolnić część zasobów, ponieważ Twoja aplikacja nie jest już widoczna.
Cytując oficjalne dokumenty:
Poziom dla onTrimMemory (int): proces pokazywał interfejs użytkownika i już tego nie robi. W tym momencie należy zwolnić duże alokacje z interfejsem użytkownika, aby umożliwić lepsze zarządzanie pamięcią.
Oczywiście, powinieneś to zaimplementować, aby faktycznie zrobić to, co mówi (oczyść pamięć, która nie była używana przez pewien czas, wyczyść niektóre zbiory, które nie były używane itp. Możliwości są nieograniczone (zobacz oficjalne dokumenty, aby uzyskać więcej możliwych poziomy krytyczne ).
Ale interesujące jest to, że system operacyjny mówi: HEJ, twoja aplikacja poszła w tło!
Właśnie to przede wszystkim chciałeś wiedzieć.
Jak ustalić, kiedy wróciłeś?
Cóż, to proste, jestem pewien, że masz „BaseActivity”, dzięki czemu możesz użyć funkcji onResume () do oznaczenia faktu powrotu. Ponieważ jedyny raz, kiedy powiesz, że nie wróciłeś, to faktyczne otrzymanie połączenia z powyższą onTrimMemory
metodą.
To działa. Nie otrzymujesz fałszywych trafień. Jeśli aktywność zostanie wznowiona, wrócisz 100% razy. Jeśli użytkownik ponownie przejdzie do tyłu, otrzymasz kolejne onTrimMemory()
połączenie.
Musisz opisać swoje działania (lub jeszcze lepiej niestandardową klasę).
Najłatwiejszym sposobem na zagwarantowanie, że zawsze to otrzymasz, jest utworzenie prostej klasy takiej jak ta:
public class MemoryBoss implements ComponentCallbacks2 {
@Override
public void onConfigurationChanged(final Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We're in the Background
}
// you might as well implement some memory cleanup here and be a nice Android dev.
}
}
Aby tego użyć, we wdrożeniu aplikacji ( masz RIGHT? ), Wykonaj coś takiego:
MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMemoryBoss = new MemoryBoss();
registerComponentCallbacks(mMemoryBoss);
}
}
Jeśli utworzysz, Interface
możesz dodać else
do niego if
i zaimplementować ComponentCallbacks
(bez 2) używane w czymkolwiek poniżej API 14. To wywołanie zwrotne ma tylko onLowMemory()
metodę i nie jest wywoływane, gdy przejdziesz do tła , ale powinieneś użyć go do przycięcia pamięci .
Teraz uruchom aplikację i naciśnij przycisk home. Twoja onTrimMemory(final int level)
metoda powinna zostać wywołana (wskazówka: dodaj rejestrowanie).
Ostatnim krokiem jest wyrejestrowanie się z oddzwaniania. Prawdopodobnie najlepszym miejscem jest onTerminate()
metoda Twojej aplikacji, ale ta metoda nie jest wywoływana na prawdziwym urządzeniu:
/** * This method is for use in emulated process environments. It will * never be called on a production Android device, where processes are * removed by simply killing them; no user code (including this callback) * is executed when doing so. */
Tak więc, chyba że naprawdę masz sytuację, w której nie chcesz już być rejestrowanym, możesz bezpiecznie zignorować to, ponieważ proces i tak umiera na poziomie systemu operacyjnego.
Jeśli w pewnym momencie zdecydujesz się wyrejestrować (jeśli na przykład zapewnisz mechanizm zamykania aplikacji, aby wyczyścić i umrzeć), możesz:
unregisterComponentCallbacks(mMemoryBoss);
I to wszystko.
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
co pozwala uniknąć problemu w twojej aktualizacji, punkt 2. Jeśli chodzi o punkt 1, nie jest to dla mnie problemem, ponieważ aplikacja tak naprawdę nie poszła w tło, więc tak powinna działać.
Oto jak udało mi się to rozwiązać. Działa przy założeniu, że użycie odniesienia czasowego między przejściami aktywności najprawdopodobniej dostarczy wystarczających dowodów na to, że aplikacja została „uruchomiona w tle” lub nie.
Po pierwsze, użyłem instancji android.app.Application (nazwijmy ją MyApplication), która ma Timer, TimerTask, stałą reprezentującą maksymalną liczbę milisekund, jaką przejście z jednej czynności do drugiej może rozsądnie zająć (poszedłem o wartości 2s) i wartość logiczną wskazującą, czy aplikacja była „w tle”:
public class MyApplication extends Application {
private Timer mActivityTransitionTimer;
private TimerTask mActivityTransitionTimerTask;
public boolean wasInBackground;
private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
...
Aplikacja udostępnia również dwie metody uruchamiania i zatrzymywania stopera / zadania:
public void startActivityTransitionTimer() {
this.mActivityTransitionTimer = new Timer();
this.mActivityTransitionTimerTask = new TimerTask() {
public void run() {
MyApplication.this.wasInBackground = true;
}
};
this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,
MAX_ACTIVITY_TRANSITION_TIME_MS);
}
public void stopActivityTransitionTimer() {
if (this.mActivityTransitionTimerTask != null) {
this.mActivityTransitionTimerTask.cancel();
}
if (this.mActivityTransitionTimer != null) {
this.mActivityTransitionTimer.cancel();
}
this.wasInBackground = false;
}
Ostatnim elementem tego rozwiązania jest dodanie wywołania do każdej z tych metod ze zdarzeń onResume () i onPause () wszystkich działań lub, najlepiej, działania podstawowego, z którego dziedziczą wszystkie konkretne działania:
@Override
public void onResume()
{
super.onResume();
MyApplication myApp = (MyApplication)this.getApplication();
if (myApp.wasInBackground)
{
//Do specific came-here-from-background code
}
myApp.stopActivityTransitionTimer();
}
@Override
public void onPause()
{
super.onPause();
((MyApplication)this.getApplication()).startActivityTransitionTimer();
}
Tak więc w przypadku, gdy użytkownik po prostu porusza się między działaniami aplikacji, funkcja onPause () czynności odchodzącej uruchamia stoper, ale prawie natychmiast nowa wprowadzona aktywność anuluje stoper, zanim osiągnie maksymalny czas przejścia. I tak wasInBackground byłoby fałszywe .
Z drugiej strony, gdy działanie pojawia się na pierwszym planie z poziomu Launchera, wybudzanie urządzenia, kończenie połączenia telefonicznego itp., Bardziej niż prawdopodobne, że zadanie timera wykonane przed tym zdarzeniem, a tym samym parametr wasInBackground został ustawiony na wartość true .
Edycja: nowe komponenty architektury przyniosły coś obiecującego: ProcessLifecycleOwner , patrz odpowiedź @ vokilam
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(AppLifecycleTracker())
}
}
class AppLifecycleTracker : Application.ActivityLifecycleCallbacks {
private var numStarted = 0
override fun onActivityStarted(activity: Activity?) {
if (numStarted == 0) {
// app went to foreground
}
numStarted++
}
override fun onActivityStopped(activity: Activity?) {
numStarted--
if (numStarted == 0) {
// app went to background
}
}
}
Tak. Wiem, że trudno uwierzyć, że to proste rozwiązanie działa, ponieważ mamy tutaj tak wiele dziwnych rozwiązań.
Ale jest nadzieja.
ProcessLifecycleOwner
wydaje się również obiecującym rozwiązaniem.
ProcessLifecycleOwner wyśle
ON_START
,ON_RESUME
wydarzenia, jako pierwsze działanie przechodzi przez tych wydarzeń.ON_PAUSE
,ON_STOP
zdarzenia będą wysyłane z opóźnieniem po przejściu przez nie ostatniej aktywności. To opóźnienie jest wystarczająco długie, aby zagwarantować, żeProcessLifecycleOwner
nie wyślą żadnych zdarzeń, jeśli działania zostaną zniszczone i odtworzone z powodu zmiany konfiguracji.
Implementacja może być tak prosta jak
class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() { // app moved to foreground
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() { // app moved to background
}
}
// register observer
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
Zgodnie z kodem źródłowym bieżąca wartość opóźnienia wynosi 700ms
.
Korzystanie z tej funkcji wymaga również dependencies
:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
implementation "android.arch.lifecycle:extensions:1.0.0"
oraz annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
z repozytorium Google (tj. google()
)
Na podstawie odpowiedzi Martína Marconcinisa (dzięki!) W końcu znalazłem niezawodne (i bardzo proste) rozwiązanie.
public class ApplicationLifecycleHandler implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
private static final String TAG = ApplicationLifecycleHandler.class.getSimpleName();
private static boolean isInBackground = false;
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if(isInBackground){
Log.d(TAG, "app went to foreground");
isInBackground = false;
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int i) {
if(i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN){
Log.d(TAG, "app went to background");
isInBackground = true;
}
}
}
Następnie dodaj to do swojej klasy onCreate () swojej klasy aplikacji
public class MyApp extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
}
}
Używamy tej metody. Wygląda na zbyt prostą do pracy, ale została dobrze przetestowana w naszej aplikacji i w rzeczywistości działa zaskakująco dobrze we wszystkich przypadkach, w tym przechodzenie do ekranu głównego za pomocą przycisku „home”, przycisku „powrót” lub po zablokowaniu ekranu. Spróbuj.
Chodzi o to, że na pierwszym planie Android zawsze rozpoczyna nową aktywność tuż przed zatrzymaniem poprzedniej. Nie jest to gwarantowane, ale tak to działa. BTW, Flurry zdaje się używać tej samej logiki (zgaduję, że tego nie sprawdziłem, ale dotyczy to tych samych zdarzeń).
public abstract class BaseActivity extends Activity {
private static int sessionDepth = 0;
@Override
protected void onStart() {
super.onStart();
sessionDepth++;
if(sessionDepth == 1){
//app came to foreground;
}
}
@Override
protected void onStop() {
super.onStop();
if (sessionDepth > 0)
sessionDepth--;
if (sessionDepth == 0) {
// app went to background
}
}
}
Edycja: zgodnie z komentarzami przenieśliśmy się również do onStart () w późniejszych wersjach kodu. Dodam też super wywołania, których brakowało w moim początkowym poście, ponieważ była to raczej koncepcja niż działający kod.
onStop is called when the activity is no longer visible to the user
.
Jeśli aplikacja składa się z wielu działań i / lub działań ułożonych w stos, takich jak widget paska kart, zastąpienie funkcji onPause () i onResume () nie będzie działać. Tzn. Przy rozpoczynaniu nowej działalności bieżące działania zostaną wstrzymane przed utworzeniem nowej. To samo dotyczy zakończenia (korzystania z przycisku „wstecz”) działania.
Znalazłem dwie metody, które wydają się działać zgodnie z oczekiwaniami.
Pierwszy wymaga uprawnienia GET_TASKS i składa się z prostej metody, która sprawdza, czy najczęściej działająca aktywność na urządzeniu należy do aplikacji, poprzez porównanie nazw pakietów:
private boolean isApplicationBroughtToBackground() {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(context.getPackageName())) {
return true;
}
}
return false;
}
Ta metoda została znaleziona w frameworku Droid-Fu (obecnie nazywanym Ignition).
Druga metoda, którą zaimplementowałem, nie wymaga pozwolenia GET_TASKS, co jest dobre. Zamiast tego wdrożenie jest nieco bardziej skomplikowane.
W swojej klasie MainApplication masz zmienną, która śledzi liczbę uruchomionych działań w aplikacji. W onResume () dla każdego działania zwiększasz zmienną, a w onPause () ją zmniejszasz.
Gdy liczba uruchomionych działań osiągnie wartość 0, aplikacja zostanie umieszczona w tle, JEŻELI następujące warunki są spełnione:
Gdy możesz wykryć, że aplikacja zrezygnowała z pracy w tle, łatwo jest również wykryć, kiedy zostanie przywrócona na pierwszy plan.
Utwórz klasę, która się rozszerza Application
. Następnie w nim możemy wykorzystać jego metodę obejścia, onTrimMemory()
.
Aby wykryć, czy aplikacja przeszła w tło, użyjemy:
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Works for Activity
// Get called every-time when application went to background.
}
else if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { // Works for FragmentActivty
}
}
FragmentActivity
także chcieć dodać level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
też.
Rozważ użycie onUserLeaveHint. Zostanie to wywołane tylko wtedy, gdy aplikacja przejdzie w tło. onPause będzie obsługiwał przypadki narożne, ponieważ można je wywoływać z innych powodów; na przykład, jeśli użytkownik otworzy inne działanie w Twojej aplikacji, takie jak strona ustawień, metoda onPause głównej aktywności zostanie wywołana, nawet jeśli nadal jest w Twojej aplikacji; śledzenie tego, co się dzieje, doprowadzi do błędów, gdy zamiast tego możesz po prostu użyć wywołania zwrotnego onUserLeaveHint, które robi to, o co prosisz.
Gdy wywoływana jest funkcja UserLeaveHint, można ustawić wartość logiczną inBackground na wartość true. Po wywołaniu onResume zakładaj, że wróciłeś na pierwszy plan, jeśli ustawiona jest flaga inBackground. Wynika to z faktu, że onResume będzie również wywoływany w przypadku głównej aktywności, jeśli użytkownik był tylko w menu ustawień i nigdy nie opuścił aplikacji.
Pamiętaj, że jeśli użytkownik naciśnie przycisk Home na ekranie ustawień, onUserLeaveHint zostanie wywołany w ustawieniach, a gdy wróci, Resum zostanie wywołany w ustawieniach. Jeśli masz tylko ten kod wykrywający w głównej działalności, przegapisz ten przypadek użycia. Aby mieć ten kod we wszystkich twoich działaniach bez powielania kodu, przygotuj abstrakcyjną klasę aktywności, która rozszerza działanie i umieść w nim swój wspólny kod. Następnie każde działanie, które masz, może rozszerzyć to działanie abstrakcyjne.
Na przykład:
public abstract AbstractActivity extends Activity {
private static boolean inBackground = false;
@Override
public void onResume() {
if (inBackground) {
// You just came from the background
inBackground = false;
}
else {
// You just returned from another activity within your own app
}
}
@Override
public void onUserLeaveHint() {
inBackground = true;
}
}
public abstract MainActivity extends AbstractActivity {
...
}
public abstract SettingsActivity extends AbstractActivity {
...
}
ActivityLifecycleCallbacks może być interesujące, ale nie jest dobrze udokumentowane.
Jednak jeśli wywołasz registerActivityLifecycleCallbacks (), powinieneś być w stanie uzyskać wywołania zwrotne, gdy Działania są tworzone, niszczone itp. Możesz wywołać getComponentName () dla Działania.
Android.arch.lifecycle pakiet zawiera klasy i interfejsy, które pozwalają budować elementy cyklu życia-aware
Twoja aplikacja powinna implementować interfejs LifecycleObserver:
public class MyApplication extends Application implements LifecycleObserver {
@Override
public void onCreate() {
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private void onAppBackgrounded() {
Log.d("MyApp", "App in background");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
private void onAppForegrounded() {
Log.d("MyApp", "App in foreground");
}
}
Aby to zrobić, musisz dodać tę zależność do pliku build.gradle:
dependencies {
implementation "android.arch.lifecycle:extensions:1.1.1"
}
Zgodnie z zaleceniami Google należy zminimalizować kod wykonywany w metodach cyklu życia działań:
Częstym wzorcem jest wdrażanie działań zależnych komponentów w metodach cyklu życia działań i fragmentów. Jednak ten wzorzec prowadzi do słabej organizacji kodu i rozprzestrzeniania się błędów. Używając komponentów uwzględniających cykl życia, możesz przenieść kod zależnych komponentów z metod cyklu życia do samych komponentów.
Możesz przeczytać więcej tutaj: https://developer.android.com/topic/libraries/architecture/lifecycle
W swojej aplikacji dodaj wywołanie zwrotne i sprawdź aktywność roota w następujący sposób:
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity.isTaskRoot() && !(activity instanceof YourSplashScreenActivity)) {
Log.e(YourApp.TAG, "Reload defaults on restoring from background.");
loadDefaults();
}
}
});
}
Stworzyłem projekt na Github app-foreground-background-listen
Utwórz BaseActivity dla wszystkich działań w aplikacji.
public class BaseActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
public static boolean isAppInFg = false;
public static boolean isScrInFg = false;
public static boolean isChangeScrFg = false;
@Override
protected void onStart() {
if (!isAppInFg) {
isAppInFg = true;
isChangeScrFg = false;
onAppStart();
}
else {
isChangeScrFg = true;
}
isScrInFg = true;
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
if (!isScrInFg || !isChangeScrFg) {
isAppInFg = false;
onAppPause();
}
isScrInFg = false;
}
public void onAppStart() {
// Remove this toast
Toast.makeText(getApplicationContext(), "App in foreground", Toast.LENGTH_LONG).show();
// Your code
}
public void onAppPause() {
// Remove this toast
Toast.makeText(getApplicationContext(), "App in background", Toast.LENGTH_LONG).show();
// Your code
}
}
Teraz użyj tej funkcji BaseActivity jako superklasy wszystkich działań, takich jak MainActivity rozszerza funkcję BaseActivity, a onAppStart będzie wywoływany przy uruchamianiu aplikacji, a onAppPause () będzie wywoływany, gdy aplikacja przejdzie w tło z dowolnego ekranu.
To jest dość łatwe ProcessLifecycleOwner
Dodaj te zależności
implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"
W Kotlinie :
class ForegroundBackgroundListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startSomething() {
Log.v("ProcessLog", "APP IS ON FOREGROUND")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopSomething() {
Log.v("ProcessLog", "APP IS IN BACKGROUND")
}
}
Następnie w swojej podstawowej aktywności:
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get()
.lifecycle
.addObserver(
ForegroundBackgroundListener()
.also { appObserver = it })
}
Zobacz mój artykuł na ten temat: https://medium.com/@egek92/how-to-actally-detect-foreground-background-changes-in-your-android-application-with-wanting-9719cc822c48
Możesz użyć ProcessLifecycleOwner dołączając do niego obserwatora cyklu życia.
public class ForegroundLifecycleObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onAppCreated() {
Timber.d("onAppCreated() called");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onAppStarted() {
Timber.d("onAppStarted() called");
}
@OnLifecycleEvent(Event.ON_RESUME)
public void onAppResumed() {
Timber.d("onAppResumed() called");
}
@OnLifecycleEvent(Event.ON_PAUSE)
public void onAppPaused() {
Timber.d("onAppPaused() called");
}
@OnLifecycleEvent(Event.ON_STOP)
public void onAppStopped() {
Timber.d("onAppStopped() called");
}
}
następnie w onCreate()
swojej klasie aplikacji nazywasz to:
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());
Dzięki temu będziesz w stanie uchwycić zdarzenia ON_PAUSE
i ON_STOP
aplikacji, które mają miejsce, gdy przejdzie ona w tło.
Nie ma prostych metod cyklu życia, które informują, kiedy cała aplikacja przechodzi w tło / na pierwszym planie.
Zrobiłem to w prosty sposób. Postępuj zgodnie z poniższymi instrukcjami, aby wykryć fazę tła aplikacji / pierwszego planu.
Przy odrobinie obejścia jest to możliwe. Tutaj na ratunek przychodzi ActivityLifecycleCallback . Pozwól mi przejść krok po kroku.
Najpierw utwórz klasę, która rozszerza aplikację android.app.Application i implementuje interfejs ActivityLifecycleCallbacks . W Application.onCreate () zarejestruj wywołanie zwrotne.
public class App extends Application implements
Application.ActivityLifecycleCallbacks {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
}
Zarejestruj klasę „APP” w manifeście jak poniżej <application android:name=".App"
.
Gdy aplikacja będzie na pierwszym planie, będzie co najmniej jedna aktywność w stanie uruchomionym, a nie będzie żadnej aktywności w stanie uruchomionym, gdy aplikacja będzie w tle.
Zadeklaruj 2 zmienne jak poniżej w klasie „App”.
private int activityReferences = 0;
private boolean isActivityChangingConfigurations = false;
activityReferences
zachowa liczbę działań w stanie uruchomionym . isActivityChangingConfigurations
to flaga wskazująca, czy bieżąca aktywność przechodzi zmianę konfiguracji, jak przełącznik orientacji.
Za pomocą następującego kodu możesz sprawdzić, czy aplikacja jest na pierwszym planie.
@Override
public void onActivityStarted(Activity activity) {
if (++activityReferences == 1 && !isActivityChangingConfigurations) {
// App enters foreground
}
}
W ten sposób można wykryć, czy aplikacja przechodzi w tło.
@Override
public void onActivityStopped(Activity activity) {
isActivityChangingConfigurations = activity.isChangingConfigurations();
if (--activityReferences == 0 && !isActivityChangingConfigurations) {
// App enters background
}
}
Jak to działa:
Jest to mała sztuczka zrobiona ze sposobem, w jaki metody cyklu życia są wywoływane kolejno. Pozwól mi przejść przez scenariusz.
Załóżmy, że użytkownik uruchamia aplikację i uruchamiane jest działanie A dotyczące uruchamiania. Połączenia cyklu życia będą,
A.onCreate ()
A.onStart () (++ activityReferences == 1) (Aplikacja wchodzi na pierwszy plan)
A.onResume ()
Teraz działanie A rozpoczyna działanie B.
A.onPause ()
B.onCreate ()
B.onStart () (++ activityReferences == 2)
B.onResume ()
A.onStop () (--activityReferences == 1)
Następnie użytkownik wraca z działania B,
B.onPause ()
A.onStart () (++ activityReferences == 2)
A.onResume ()
B.onStop () (--activityReferences == 1)
B.onDestroy ()
Następnie użytkownik naciska przycisk Home,
A.onPause ()
A.onStop () (--activityReferences == 0) (Aplikacja wchodzi w tło)
W przypadku, gdy użytkownik naciśnie przycisk Początek z działania B zamiast przycisku Wstecz, nadal będzie taki sam, a aktywność Odniesienia będą 0
. Dlatego możemy wykryć, że aplikacja wchodzi w tło.
Jaka jest więc rola isActivityChangingConfigurations
? W powyższym scenariuszu załóżmy, że działanie B zmienia orientację. Sekwencja oddzwonienia będzie
B.onPause ()
B.onStop () (--activityReferences == 0) (Aplikacja wchodzi w tło ??)
B.onDestroy ()
B.onCreate ()
B.onStart () (++ activityReferences == 1) (Aplikacja wchodzi na pierwszy plan?)
B.onResume ()
Dlatego mamy dodatkowe sprawdzenie, isActivityChangingConfigurations
aby uniknąć scenariusza, w którym działanie przechodzi zmiany konfiguracji.
Znalazłem dobrą metodę wykrywania aplikacji, bez względu na to, czy wchodzi na pierwszy plan, czy w tło. Oto mój kod . Mam nadzieję, że ci to pomoże.
/**
* Custom Application which can detect application state of whether it enter
* background or enter foreground.
*
* @reference http://www.vardhan-justlikethat.blogspot.sg/2014/02/android-solution-to-detect-when-android.html
*/
public abstract class StatusApplication extends Application implements ActivityLifecycleCallbacks {
public static final int STATE_UNKNOWN = 0x00;
public static final int STATE_CREATED = 0x01;
public static final int STATE_STARTED = 0x02;
public static final int STATE_RESUMED = 0x03;
public static final int STATE_PAUSED = 0x04;
public static final int STATE_STOPPED = 0x05;
public static final int STATE_DESTROYED = 0x06;
private static final int FLAG_STATE_FOREGROUND = -1;
private static final int FLAG_STATE_BACKGROUND = -2;
private int mCurrentState = STATE_UNKNOWN;
private int mStateFlag = FLAG_STATE_BACKGROUND;
@Override
public void onCreate() {
super.onCreate();
mCurrentState = STATE_UNKNOWN;
registerActivityLifecycleCallbacks(this);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// mCurrentState = STATE_CREATED;
}
@Override
public void onActivityStarted(Activity activity) {
if (mCurrentState == STATE_UNKNOWN || mCurrentState == STATE_STOPPED) {
if (mStateFlag == FLAG_STATE_BACKGROUND) {
applicationWillEnterForeground();
mStateFlag = FLAG_STATE_FOREGROUND;
}
}
mCurrentState = STATE_STARTED;
}
@Override
public void onActivityResumed(Activity activity) {
mCurrentState = STATE_RESUMED;
}
@Override
public void onActivityPaused(Activity activity) {
mCurrentState = STATE_PAUSED;
}
@Override
public void onActivityStopped(Activity activity) {
mCurrentState = STATE_STOPPED;
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
mCurrentState = STATE_DESTROYED;
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (mCurrentState == STATE_STOPPED && level >= TRIM_MEMORY_UI_HIDDEN) {
if (mStateFlag == FLAG_STATE_FOREGROUND) {
applicationDidEnterBackground();
mStateFlag = FLAG_STATE_BACKGROUND;
}
}else if (mCurrentState == STATE_DESTROYED && level >= TRIM_MEMORY_UI_HIDDEN) {
if (mStateFlag == FLAG_STATE_FOREGROUND) {
applicationDidDestroyed();
mStateFlag = FLAG_STATE_BACKGROUND;
}
}
}
/**
* The method be called when the application been destroyed. But when the
* device screen off,this method will not invoked.
*/
protected abstract void applicationDidDestroyed();
/**
* The method be called when the application enter background. But when the
* device screen off,this method will not invoked.
*/
protected abstract void applicationDidEnterBackground();
/**
* The method be called when the application enter foreground.
*/
protected abstract void applicationWillEnterForeground();
}
Edycja 2: To, co napisałem poniżej, nie zadziała. Google odrzucił aplikację, która zawiera wywołanie ActivityManager.getRunningTasks (). Z dokumentacji wynika, że ten interfejs API służy wyłącznie do debugowania i programowania. Będę aktualizować ten post, jak tylko będę mieć czas, aby zaktualizować poniższy projekt GitHub o nowy schemat, który używa timerów i jest prawie tak dobry.
Edycja 1: Napisałem wpis na blogu i utworzyłem proste repozytorium GitHub, aby było to naprawdę łatwe.
Zarówno zaakceptowana, jak i najwyżej oceniana odpowiedź nie są najlepszym podejściem. Implementacja najwyżej ocenionej odpowiedzi isApplicationBroughtToBackground () nie obsługuje sytuacji, w której główna aktywność aplikacji ulega działaniu zdefiniowanemu w tej samej aplikacji, ale ma inny pakiet Java. Wymyśliłem sposób, aby to zrobić, co zadziała w tym przypadku.
Wywołaj to w funkcji onPause (), a dowiesz się, czy aplikacja przechodzi w tło, ponieważ uruchomiono inną aplikację, czy użytkownik nacisnął przycisk Home.
public static boolean isApplicationBroughtToBackground(final Activity activity) {
ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1);
// Check the top Activity against the list of Activities contained in the Application's package.
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
try {
PackageInfo pi = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
for (ActivityInfo activityInfo : pi.activities) {
if(topActivity.getClassName().equals(activityInfo.name)) {
return false;
}
}
} catch( PackageManager.NameNotFoundException e) {
return false; // Never happens.
}
}
return true;
}
Prawidłowa odpowiedź tutaj
Utwórz klasę o nazwie MyApp jak poniżej:
public class MyApp implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
private Context context;
public void setContext(Context context)
{
this.context = context;
}
private boolean isInBackground = false;
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
isInBackground = true;
Log.d("status = ","we are out");
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if(isInBackground){
isInBackground = false;
Log.d("status = ","we are in");
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
}
Następnie, gdziekolwiek chcesz (lepsza pierwsza aktywność uruchomiona w aplikacji), dodaj poniższy kod:
MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);
Gotowy! Teraz, gdy aplikacja jest w tle, otrzymujemy dziennik, status : we are out
a kiedy wchodzimy do aplikacji, otrzymujemy dziennikstatus : we are out
Moje rozwiązanie zostało zainspirowane odpowiedzią @ d60402, a także opiera się na oknie czasowym, ale nie używa Timer
:
public abstract class BaseActivity extends ActionBarActivity {
protected boolean wasInBackground = false;
@Override
protected void onStart() {
super.onStart();
wasInBackground = getApp().isInBackground;
getApp().isInBackground = false;
getApp().lastForegroundTransition = System.currentTimeMillis();
}
@Override
protected void onStop() {
super.onStop();
if( 1500 < System.currentTimeMillis() - getApp().lastForegroundTransition )
getApp().isInBackground = true;
}
protected SingletonApplication getApp(){
return (SingletonApplication)getApplication();
}
}
gdzie SingletonApplication
jest rozszerzeniem Application
klasy:
public class SingletonApplication extends Application {
public boolean isInBackground = false;
public long lastForegroundTransition = 0;
}
Korzystałem z tego w Google Analytics EasyTracker i działało. Można to rozszerzyć, aby robić to, czego szukasz, używając prostej liczby całkowitej.
public class MainApplication extends Application {
int isAppBackgrounded = 0;
@Override
public void onCreate() {
super.onCreate();
appBackgroundedDetector();
}
private void appBackgroundedDetector() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStart(activity);
}
@Override
public void onActivityResumed(Activity activity) {
isAppBackgrounded++;
if (isAppBackgrounded > 0) {
// Do something here
}
}
@Override
public void onActivityPaused(Activity activity) {
isAppBackgrounded--;
}
@Override
public void onActivityStopped(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStop(activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
wiem, że jest trochę późno, ale myślę, że wszystkie te odpowiedzi mają pewne problemy, podczas gdy zrobiłem to jak poniżej i działa to idealnie.
utwórz wywołanie zwrotne cyklu życia działania w następujący sposób:
class ActivityLifeCycle implements ActivityLifecycleCallbacks{
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
Activity lastActivity;
@Override
public void onActivityResumed(Activity activity) {
//if (null == lastActivity || (activity != null && activity == lastActivity)) //use this condition instead if you want to be informed also when app has been killed or started for the first time
if (activity != null && activity == lastActivity)
{
Toast.makeText(MyApp.this, "NOW!", Toast.LENGTH_LONG).show();
}
lastActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
i po prostu zarejestruj go w swojej klasie aplikacji, jak poniżej:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
To wydaje się być jednym z najbardziej skomplikowanych pytań w Androidzie, ponieważ (od tego momentu) Android nie ma odpowiedników iOS applicationDidEnterBackground()
ani applicationWillEnterForeground()
wywołań zwrotnych. Kiedyś Biblioteka AppState która została stworzona przez @jenzz .
[AppState] to prosta, reaktywna biblioteka Androida oparta na RxJava, która monitoruje zmiany stanu aplikacji. Powiadamia subskrybentów za każdym razem, gdy aplikacja przechodzi w tło i wraca na pierwszy plan.
Okazało się, że tego właśnie potrzebowałem, szczególnie dlatego, że moja aplikacja miała wiele działań, więc po prostu sprawdzenie onStart()
lub onStop()
aktywność nie zamierzała go wyciąć.
Najpierw dodałem te zależności do stopniowania:
dependencies {
compile 'com.jenzz.appstate:appstate:3.0.1'
compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}
Następnie wystarczyło dodać te wiersze do odpowiedniego miejsca w kodzie:
//Note that this uses RxJava 2.x adapter. Check the referenced github site for other ways of using observable
Observable<AppState> appState = RxAppStateMonitor.monitor(myApplication);
//where myApplication is a subclass of android.app.Application
appState.subscribe(new Consumer<AppState>() {
@Override
public void accept(@io.reactivex.annotations.NonNull AppState appState) throws Exception {
switch (appState) {
case FOREGROUND:
Log.i("info","App entered foreground");
break;
case BACKGROUND:
Log.i("info","App entered background");
break;
}
}
});
W zależności od tego, w jaki sposób subskrybujesz obserwowalne, może być konieczne anulowanie subskrypcji, aby uniknąć wycieków pamięci. Ponownie więcej informacji na stronie github .
To jest zmodyfikowana wersja odpowiedzi @ d60402: https://stackoverflow.com/a/15573121/4747587
Rób wszystko, co tam wspomniano. Ale zamiast mieć Base Activity
i nadać temu status nadrzędny dla każdego działania i zastąpić onResume()
i onPause
, wykonaj następujące czynności:
W swojej klasie aplikacji dodaj wiersz:
registerActivityLifecycleCallbacks (Application.ActivityLifecycleCallbacks);
Ma callback
to wszystkie metody cyklu życia aktywności i możesz teraz zastąpić onActivityResumed()
i onActivityPaused()
.
Spójrz na tę Gist: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
Możesz to łatwo osiągnąć za pomocą ActivityLifecycleCallbacks
i ComponentCallbacks2
czegoś takiego jak poniżej.
Utwórz klasę AppLifeCycleHandler
implementującą powyższe interfejsy.
package com.sample.app;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.os.Bundle;
/**
* Created by Naveen on 17/04/18
*/
public class AppLifeCycleHandler
implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
AppLifeCycleCallback appLifeCycleCallback;
boolean appInForeground;
public AppLifeCycleHandler(AppLifeCycleCallback appLifeCycleCallback) {
this.appLifeCycleCallback = appLifeCycleCallback;
}
@Override
public void onActivityResumed(Activity activity) {
if (!appInForeground) {
appInForeground = true;
appLifeCycleCallback.onAppForeground();
}
}
@Override
public void onTrimMemory(int i) {
if (i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
appInForeground = false;
appLifeCycleCallback.onAppBackground();
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
interface AppLifeCycleCallback {
void onAppBackground();
void onAppForeground();
}
}
W twojej klasie, która rozszerza Application
implementację, AppLifeCycleCallback
aby uzyskać wywołania zwrotne, gdy aplikacja przełącza się między pierwszym planem a tłem. Coś jak poniżej.
public class BaseApplication extends Application implements AppLifeCycleHandler.AppLifeCycleCallback{
@Override
public void onCreate() {
super.onCreate();
AppLifeCycleHandler appLifeCycleHandler = new AppLifeCycleHandler(this);
registerActivityLifecycleCallbacks(appLifeCycleHandler);
registerComponentCallbacks(appLifeCycleHandler);
}
@Override
public void onAppBackground() {
Log.d("LifecycleEvent", "onAppBackground");
}
@Override
public void onAppForeground() {
Log.d("LifecycleEvent", "onAppForeground");
}
}
Mam nadzieję że to pomoże.
EDYCJA Jako alternatywę możesz teraz użyć komponentu architektury uwzględniającej cykl życia.
Ponieważ nie znalazłem żadnego podejścia, które obsługuje rotację bez sprawdzania znaczników czasu, pomyślałem, że podzielę się również tym, jak teraz to robimy w naszej aplikacji. Jedynym dodatkiem do tej odpowiedzi https://stackoverflow.com/a/42679191/5119746 jest to, że uwzględniamy również orientację.
class MyApplication : Application(), Application.ActivityLifecycleCallbacks {
// Members
private var mAppIsInBackground = false
private var mCurrentOrientation: Int? = null
private var mOrientationWasChanged = false
private var mResumed = 0
private var mPaused = 0
Następnie dla oddzwaniania najpierw mamy CV:
// ActivityLifecycleCallbacks
override fun onActivityResumed(activity: Activity?) {
mResumed++
if (mAppIsInBackground) {
// !!! App came from background !!! Insert code
mAppIsInBackground = false
}
mOrientationWasChanged = false
}
I onActivityStopped:
override fun onActivityStopped(activity: Activity?) {
if (mResumed == mPaused && !mOrientationWasChanged) {
// !!! App moved to background !!! Insert code
mAppIsInBackground = true
}
A potem pojawia się dodatek: Sprawdzanie zmian orientacji:
override fun onConfigurationChanged(newConfig: Configuration) {
if (newConfig.orientation != mCurrentOrientation) {
mCurrentOrientation = newConfig.orientation
mOrientationWasChanged = true
}
super.onConfigurationChanged(newConfig)
}
Otóż to. Mam nadzieję, że to komuś pomoże :)
Możemy rozwinąć to rozwiązanie za pomocą LiveData
:
class AppForegroundStateLiveData : LiveData<AppForegroundStateLiveData.State>() {
private var lifecycleListener: LifecycleObserver? = null
override fun onActive() {
super.onActive()
lifecycleListener = AppLifecycleListener().also {
ProcessLifecycleOwner.get().lifecycle.addObserver(it)
}
}
override fun onInactive() {
super.onInactive()
lifecycleListener?.let {
this.lifecycleListener = null
ProcessLifecycleOwner.get().lifecycle.removeObserver(it)
}
}
internal inner class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
value = State.FOREGROUND
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
value = State.BACKGROUND
}
}
enum class State {
FOREGROUND, BACKGROUND
}
}
Teraz możemy zasubskrybować tę LiveData i złapać potrzebne zdarzenia. Na przykład:
appForegroundStateLiveData.observeForever { state ->
when(state) {
AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
}
}
Te odpowiedzi wydają się nieprawidłowe. Te metody są również wywoływane, gdy rozpoczyna się i kończy inna aktywność. Możesz zachować flagę globalną (tak, globały są złe :) i ustaw ją na wartość true za każdym razem, gdy zaczynasz nową aktywność. Ustaw wartość false w polu onCreate każdego działania. Następnie w onPause sprawdzasz tę flagę. Jeśli jest to fałsz, aplikacja przechodzi w tło lub ginie.