Jak wykryć, kiedy aplikacja na Androida przechodzi w tło i wraca na pierwszy plan

382

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?

iHorse
źródło
2
Może być dodanie przypadku użycia do pytania, ponieważ nie wydaje się to oczywiste, więc nie zostało to uwzględnione w udzielonych odpowiedziach. Aplikacja może uruchomić inną aplikację (na przykład Galerię), która nadal będzie znajdować się na tym samym stosie i będzie wyświetlana jako jeden z ekranów aplikacji, a następnie naciśnij przycisk Początek. Żadna z metod polegających na cyklu życia aplikacji (ani nawet zarządzaniu pamięcią) nie jest w stanie tego wykryć. Wyzwolą stan tła w momencie pojawienia się Aktywności zewnętrznej, a nie po naciśnięciu przycisku Dom.
Dennis K,
Oto odpowiedź, której szukasz: stackoverflow.com/a/42679191/2352699
Fred Porciúncula
1
Zobacz rozwiązanie Google: stackoverflow.com/questions/3667022/…
user1269737

Odpowiedzi:

98

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ą onWindowFocusChangedi 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 .

Girish Nair
źródło
173
Jednak to podejście powoduje fałszywe alarmy, jak zauważyli inni, ponieważ metody te są również wywoływane przy przechodzeniu między czynnościami w tej samej aplikacji.
John Lehmann
9
Gorzej niż to. Próbowałem, a czasami onResume jest wywoływany, gdy telefon jest zablokowany. Jeśli zobaczysz definicję onResume w dokumentacji, zobaczysz: Pamiętaj, że onResume nie jest najlepszym wskaźnikiem, że twoja aktywność jest widoczna dla użytkownika; okno systemowe, takie jak blokada klawiatury, może znajdować się z przodu. Użyj onWindowFocusChanged (boolean), aby mieć pewność, że twoja aktywność jest widoczna dla użytkownika (na przykład, aby wznowić grę). developer.android.com/reference/android/app/…
J-Rou,
2
Rozwiązanie zamieszczone w łączu nie używa onResume / onPause, zamiast kombinacji onBackPressed, onStop, onStart i onWindowsFocusChanged. Działa to dla mnie i mam dość złożoną hierarchię interfejsu użytkownika (z szufladami, dynamicznymi przeglądarkami itp.)
Martin Marconcini
18
OnPause i onResume są specyficzne dla działania. Nie dotyczy. Gdy aplikacja zostanie umieszczona w tle, a następnie wznowiona, wznawia działanie, w którym była, zanim przejdzie do tła. Oznacza to, że będziesz musiał wdrożyć wszystko, co chcesz zrobić po wznowieniu pracy w tle we wszystkich działaniach aplikacji. Wierzę, że pierwotne pytanie szukało czegoś w rodzaju „onResume” dla aplikacji, a nie działania.
SysHex,
4
Nie mogę uwierzyć, że właściwe API nie jest oferowane z powodu tak powszechnej potrzeby. Początkowo myślałem, że onUserLeaveHint () by go wyciął, ale nie możesz powiedzieć, czy użytkownik opuszcza aplikację, czy nie
atsakiridis
197

2018: Android obsługuje to natywnie poprzez komponenty cyklu życia.

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.

MemoryBossKlasa 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 MEMORYpołą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 YourApplicationi MemoryBossklasy, 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 wasInBackgroundto, falseaby nie wykonało kodu, dopóki nie onMemoryTrimmedzostanie 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 onTrimMemorynie można zagwarantować, że oddzwonienie zostanie wykonane:

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

  2. 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ą onTrimMemorymetodą.

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, Interfacemożesz dodać elsedo niego ifi 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.

Martin Marconcini
źródło
Podczas sprawdzania tego w serwisie wydaje się, że jest uruchamiany tylko po naciśnięciu przycisku Home. Naciśnięcie przycisku Wstecz nie uruchamia tego w KitKat.
Naucz się OpenGL ES
Usługa nie ma interfejsu użytkownika, więc może być z tym związana. Wykonaj kontrolę w swojej podstawowej działalności, a nie w usłudze. Chcesz wiedzieć, kiedy twój interfejs jest ukryty (i być może powiadomić serwis, więc idzie na pierwszy plan)
Martin Marconcini
1
Nie działa po wyłączeniu telefonu. Nie jest wyzwalany.
Juangcg,
2
Korzystanie z ComponentCallbacks2.onTrimMemory () (w połączeniu z ActivityLifecycleCallbacks) jest jedynym niezawodnym rozwiązaniem, jakie do tej pory znalazłem, dzięki Martin! Dla zainteresowanych zapoznaj się z moją odpowiedzią.
rickul
3
Używam tej metody od roku temu i zawsze była dla mnie niezawodna. Dobrze wiedzieć, że inni też z niego korzystają. Po prostu używam, level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENco 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ć.
sorianiv
175

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 .

d60402
źródło
4
Cześć d60402, twoja odpowiedź jest naprawdę pomocna .. bardzo dziękuję za tę odpowiedź ... małe powiadomienie .. MyApplication powinna wspomnieć w znaczniku aplikacji pliku manifestu, takim jak android: name = "MyApplication", w przeciwnym razie aplikacja ulega awarii ... tylko po to, aby pomóc ktoś taki jak ja
praveenb
2
znak wielkiego programisty, proste rozwiązanie jednego z najbardziej skomplikowanych problemów, jakie kiedykolwiek spotkałem.
Aashish Bhatnagar
2
Niesamowite rozwiązanie! Dzięki. Jeśli ktoś otrzyma błąd „ClassCastException”, być może przegapiłeś dodanie go do znacznika aplikacji w pliku Manifest.xml <aplikacja android: name = "your.package.MyApplication"
Wahib Ul Haq
27
To ładne i proste wdrożenie. Jednak uważam, że powinno to zostać zaimplementowane w onStart / onStop zamiast onPause / onResume. Funkcja onPause zostanie wywołana, nawet jeśli uruchomię okno dialogowe, które częściowo obejmuje działanie. A zamknięcie okna dialogowego faktycznie wywołałoby Wznowienie sprawiłoby, że wyglądałoby to tak, jakby aplikacja właśnie wyszła na pierwszy plan
Shubhayu
7
Mam nadzieję, że skorzystam z odmiany tego rozwiązania. Wskazane powyżej dialogi stanowią dla mnie problem, więc wypróbowałem sugestię @ Shubhayu (onStart / onStop). Nie pomaga to jednak, ponieważ przy przejściu A-> B wywoływane jest działanie onStart () działania B przed działaniem onStop () działania A.
Trevor
150

Edycja: nowe komponenty architektury przyniosły coś obiecującego: ProcessLifecycleOwner , patrz odpowiedź @ vokilam


Rzeczywiste rozwiązanie według rozmowy Google I / O :

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.

Fred Porciúncula
źródło
3
To działa idealnie! Próbowałem już tylu dziwnych rozwiązań, które miały tyle wad ... bardzo dziękuję! Szukałem tego przez jakiś czas.
Eggakin Baconwalker
7
Działa dla wielu działań, ale dla jednego - onrotate wskaże, że wszystkie działania zniknęły lub w tle
deadfish
2
@Shyri masz rację, ale to część tego rozwiązania, więc musisz się martwić. Jeśli baza ogniowa na tym polega, myślę, że moja przeciętna aplikacja też może :) Świetna odpowiedź BTW.
ElliotM
3
@deadfish Sprawdź link do I / O podany na górze odpowiedzi. Możesz sprawdzić odstępy czasu między zatrzymaniem aktywności a rozpoczęciem, aby ustalić, czy naprawdę poszedłeś w tle, czy nie. W rzeczywistości jest to genialne rozwiązanie.
Alex Berdnikov
2
Czy istnieje rozwiązanie Java? To jest kotlin.
Giacomo Bartoli
116

ProcessLifecycleOwner wydaje się również obiecującym rozwiązaniem.

ProcessLifecycleOwner wyśle ON_START, ON_RESUMEwydarzenia, jako pierwsze działanie przechodzi przez tych wydarzeń. ON_PAUSE, ON_STOPzdarzenia 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ć, że ProcessLifecycleOwnernie 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"
vokilam
źródło
10
Należy pamiętać, że należy dodać zależności cyklu życia implementation "android.arch.lifecycle:extensions:1.0.0"oraz annotationProcessor "android.arch.lifecycle:compiler:1.0.0"z repozytorium Google (tj. google())
Sir Codesalot
1
Działa to dla mnie świetnie, dziękuję. Musiałem użyć interfejsu API „android.arch.lifecycle: extensions: 1.1.0” zamiast implementacji z powodu błędu informującego, że zależność Androida ma inną wersję ścieżki kompilacji i środowiska wykonawczego.
FSUWX2011
To świetne rozwiązanie, ponieważ działa w modułach bez potrzeby odwoływania się do działania!
Maks.
To nie działa, gdy aplikacja ulega awarii. Czy jest jakieś rozwiązanie, które
mogłoby spowodować
Świetne rozwiązanie. Uratowałem mój dzień.
Sunny
69

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

    }

}
rickul
źródło
Czy możesz pokazać, w jaki sposób używasz tego w aplikacji, czy mogę to nazwać z klasy aplikacji czy gdzie indziej?
JPM
to jest idealne, dziękuję !! jak dotąd świetnie sprawdza się w testowaniu
aherrick
Ten przykład jest niekompletny. Co to jest registerActivityLifecycleCallbacks?
Noman,
jest to metoda w klasie android.app.Application
rickul
1
dobrze zrobione +1, aby przejść na górę, ponieważ jest idealny, nie szukaj innych odpowiedzi, jest to oparte na odpowiedzi @reno, ale z prawdziwym przykładem
Stoycho Andreev
63

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.

Nick Frolov
źródło
2
To najbardziej wiarygodna odpowiedź, chociaż używam onStart zamiast onResume.
Greg Ennis,
Powinieneś dodać wywołania do super.onResume () i super.onStop () w przesłoniętych metodach. W przeciwnym razie zgłaszany jest wyjątek android.app.SuperNotCalledException.
Jan Laussmann
1
dla mnie to nie działa ... lub przynajmniej wywołuje zdarzenie, gdy obracasz urządzenie (co jest rodzajem fałszywie pozytywnego imho).
Noya,
Bardzo proste i skuteczne rozwiązanie! Ale nie jestem pewien, czy to działa z częściowo przezroczystymi działaniami, które pozwalają zobaczyć niektóre części poprzedniej aktywności. Od docs, onStop is called when the activity is no longer visible to the user.
Nicolas Buquet
3
co się stanie, jeśli użytkownik zmieni orientację przy pierwszym działaniu? Poinformuje, że aplikacja przeszła do tła, co nie jest prawdą. Jak sobie radzisz z tym scenariuszem?
Nimrod Dayan
54

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:

  • Wstrzymane działanie nie zostało zakończone (użyto przycisku „wstecz”). Można to zrobić za pomocą metody activity.isFinishing ()
  • Nowa aktywność (ta sama nazwa pakietu) nie jest uruchamiana. Możesz przesłonić metodę startActivity (), aby ustawić zmienną, która to wskazuje, a następnie zresetować ją w onPostResume (), która jest ostatnią metodą, która zostanie uruchomiona po utworzeniu / wznowieniu działania.

Gdy możesz wykryć, że aplikacja zrezygnowała z pracy w tle, łatwo jest również wykryć, kiedy zostanie przywrócona na pierwszy plan.

Emil
źródło
18
Google prawdopodobnie odrzuci aplikację korzystającą z ActivityManager.getRunningTasks (). Dokumentacja mówi, że jest to wyłącznie cel dla wrogów. developer.android.com/reference/android/app/…
Sky Kelsey,
1
Odkryłem, że musiałem zastosować kombinację tych podejść. onUserLeaveHint () został wywołany podczas uruchamiania działania w 14. „@Override public void onUserLeaveHint () {inBackground = isApplicationBroughtToBackground (); } `
listowanie łodzi
7
Użytkownicy nie będą zbyt zadowoleni z używania potężnego uprawnienia android.permission.GET_TASKS.
MSquare
6
getRunningTasks jest przestarzałe na poziomie interfejsu API 21.
Noya
33

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
        }
    }
Harpreet
źródło
1
Dla FragmentActivitytakże chcieć dodać level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEteż.
Srujan Simha
2
Wielkie dzięki za wskazanie tej metody, muszę pokazywać okno dialogowe Pin, ilekroć użytkownik wznowi aktywność w tle, użył tej metody do napisania wartości wstępnej i sprawdził tę wartość na baseActivity.
Sam
18

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 {
    ...
}
OldSchool4664
źródło
19
onUserLeaveHint jest także wywoływany podczas przechodzenia do innej aktywności
Jonas Stawski
3
Funkcja onUserLeaveHint nie jest wywoływana, gdy np. przychodzi połączenie telefoniczne, a aktywność wywołująca staje się aktywna, więc jest to również przypadek krawędziowy - mogą być również inne przypadki, ponieważ można dodać flagę w celu tłumienia połączenia onUserLeaveHint. developer.android.com/reference/android/content/…
Groxx
1
Ponadto onResume nie działa dobrze. Próbowałem, a czasami onResume jest wywoływany, gdy telefon jest zablokowany. Jeśli zobaczysz definicję onResume w dokumentacji, zobaczysz: Pamiętaj, że onResume nie jest najlepszym wskaźnikiem, że twoja aktywność jest widoczna dla użytkownika; okno systemowe, takie jak blokada klawiatury, może znajdować się z przodu. Użyj onWindowFocusChanged (boolean), aby mieć pewność, że twoja aktywność jest widoczna dla użytkownika (na przykład, aby wznowić grę). developer.android.com/reference/android/app/…
J-Rou,
to rozwiązanie nie pomaga zdecydować na pierwszym planie / w tle, jeśli istnieje wiele
działań.Plz skieruj
14

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.

Reno
źródło
11
Ponieważ poziom interfejsu API 14 = \
imort
Wygląda na to, że ten jest czysty i działa dla mnie. Dzięki
duanbo1983,
Czym różni się to od przyjętej odpowiedzi, obie polegają na tym samym cyklu życia czynności, prawda?
Saitama,
13

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

matdev
źródło
i dodaj to do manifestu, np .: <application android: name = ". AnotherApp">
Dan Alboteanu,
9

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();
            }
        }
    });
}
Cynichniy Bandera
źródło
Zastanowiłbym się nad zastosowaniem tego sposobu implementacji. Przejście z jednego działania do drugiego zajmuje tylko kilka milisekund. W zależności od czasu, kiedy zniknie ostatnia aktywność, można rozważyć ponowne zalogowanie użytkownika przy użyciu określonej strategii.
drindt
6

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.

Kiran Boghra
źródło
@kiran boghra: Czy w twoim rozwiązaniu są jakieś fałszywe alarmy?
Harish Vishwakarma
W tym przypadku można użyć idealnej odpowiedzi: funkcje onStart () i onStop (). który mówi ci o twojej aplikacji
Pir Fahim Shah
6

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

Ege Kuzubasioglu
źródło
5

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_PAUSEi ON_STOPaplikacji, które mają miejsce, gdy przejdzie ona w tło.

Alécio Carvalho
źródło
4

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.

  1. 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);
        }
    }
  2. Zarejestruj klasę „APP” w manifeście jak poniżej <application android:name=".App".

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

    activityReferenceszachowa liczbę działań w stanie uruchomionym . isActivityChangingConfigurationsto flaga wskazująca, czy bieżąca aktywność przechodzi zmianę konfiguracji, jak przełącznik orientacji.

  4. 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
        }
    }
  5. 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, isActivityChangingConfigurationsaby uniknąć scenariusza, w którym działanie przechodzi zmiany konfiguracji.

Komal Nikhare
źródło
3

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();

}

Folyd
źródło
3

Możesz użyć:

protected void onRestart ()

Różnicowanie między nowymi uruchomieniami i restartami.

wprowadź opis zdjęcia tutaj

AYBABTU
źródło
3

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;
}
Sky Kelsey
źródło
FYI, wywołanie tego w onStart () zamiast tego pozwoli uniknąć wywołania, gdy zostanie wyświetlone proste okno dialogowe, na przykład z powodu alarmu.
Sky Kelsey,
2

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

erfan
źródło
1

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 SingletonApplicationjest rozszerzeniem Applicationklasy:

public class SingletonApplication extends Application {
  public boolean isInBackground = false;
  public long lastForegroundTransition = 0;
}
wtryskiwacz
źródło
1

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

            }
        });
    }
}
Bill Mote
źródło
1

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());
}
Amir Ziarati
źródło
Jest to wywoływane przez cały czas przy każdej aktywności. Jak mogę tego użyć, jeśli na przykład chcę wykryć status użytkownika online
Maksim Kniazev
tego właśnie chce pytanie. wywoływane jest tylko po przejściu do ekranu głównego i powrocie do dowolnej aktywności.
Amir Ziarati,
jeśli masz na myśli łączność z Internetem, myślę, że lepiej jest sprawdzić to, kiedy jest to potrzebne. jeśli chcesz zadzwonić do interfejsu API, sprawdź połączenie internetowe tuż przed połączeniem.
Amir Ziarati,
1

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 .

deniz
źródło
1

To jest zmodyfikowana wersja odpowiedzi @ d60402: https://stackoverflow.com/a/15573121/4747587

Rób wszystko, co tam wspomniano. Ale zamiast mieć Base Activityi 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 callbackto 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

Henz
źródło
1

Możesz to łatwo osiągnąć za pomocą ActivityLifecycleCallbacksi ComponentCallbacks2czegoś takiego jak poniżej.

Utwórz klasę AppLifeCycleHandlerimplementują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 Applicationimplementację, AppLifeCycleCallbackaby 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.

Naveen TP
źródło
1

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

Julian Horst
źródło
1

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 */ }
    }
}
Alex Kisel
źródło
0

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.

Joris Weimar
źródło
Nie mówiłem o bazie danych ... co masz na myśli?
Joris Weimar,
Popieram twoją odpowiedź. nawet jeśli możemy zapisać tę wartość flagi w bazie danych podczas wywołania pauzy, nie jest to dobre rozwiązanie ..
Sandeep P