So erkennen Sie, wann eine Android-App in den Hintergrund tritt und wieder in den Vordergrund tritt

382

Ich versuche, eine App zu schreiben, die etwas Bestimmtes tut, wenn sie nach einiger Zeit wieder in den Vordergrund gerückt wird. Gibt es eine Möglichkeit zu erkennen, wann eine App in den Hintergrund gesendet oder in den Vordergrund gestellt wird?

iHorse
quelle
2
Möglicherweise wird der Frage ein Anwendungsfall hinzugefügt, da dies nicht offensichtlich zu sein scheint und daher in den gegebenen Antworten nicht behandelt wird. Die App startet möglicherweise eine andere App (z. B. Galerie), die sich weiterhin im selben Stapel befindet und als einer der Bildschirme der App angezeigt wird, und drückt dann die Home-Taste. Keine der Methoden, die sich auf den App-Lebenszyklus (oder sogar die Speicherverwaltung) stützen, kann dies erkennen. Sie lösen den Hintergrundstatus direkt aus, wenn externe Aktivitäten angezeigt werden, nicht, wenn Sie auf Start drücken.
Dennis K
Dies ist die Antwort, die Sie suchen: stackoverflow.com/a/42679191/2352699
Fred Porciúncula
1
Siehe Google-Lösung: stackoverflow.com/questions/3667022/…
user1269737

Antworten:

98

Die Methoden onPause()und onResume()werden aufgerufen, wenn die Anwendung wieder in den Hintergrund und in den Vordergrund gerückt wird. Sie werden jedoch auch aufgerufen, wenn die Anwendung zum ersten Mal gestartet wird und bevor sie beendet wird. Weitere Informationen finden Sie unter Aktivität .

Es gibt keinen direkten Ansatz, um den Anwendungsstatus im Hintergrund oder im Vordergrund abzurufen, aber selbst ich habe mich diesem Problem gestellt und die Lösung mit onWindowFocusChangedund gefunden onStop.

Weitere Informationen finden Sie hier. Android: Lösung, um zu erkennen, wann eine Android-App in den Hintergrund tritt und ohne getRunningTasks oder getRunningAppProcesses wieder in den Vordergrund zurückkehrt .

Girish Nair
quelle
173
Dieser Ansatz führt jedoch zu Fehlalarmen, wie andere betonten, da diese Methoden auch beim Übergang zwischen Aktivitäten in derselben App aufgerufen werden.
John Lehmann
9
Es ist schlimmer als das. Ich habe es versucht und manchmal wird onResume aufgerufen, während das Telefon gesperrt ist. Wenn Sie die Definition von onResume in der Dokumentation sehen, finden Sie: Beachten Sie, dass onResume nicht der beste Indikator dafür ist, dass Ihre Aktivität für den Benutzer sichtbar ist. Ein Systemfenster wie der Schlüsselschutz kann vorne sein. Verwenden Sie onWindowFocusChanged (boolean), um sicherzugehen, dass Ihre Aktivität für den Benutzer sichtbar ist (z. B. um ein Spiel fortzusetzen). developer.android.com/reference/android/app/…
J-Rou
2
Die im Link veröffentlichte Lösung verwendet nicht onResume / onPause, sondern eine Kombination aus onBackPressed, onStop, onStart und onWindowsFocusChanged. Es hat bei mir funktioniert und ich habe eine ziemlich komplexe UI-Hierarchie (mit Schubladen, dynamischen Viewpagern usw.)
Martin Marconcini
18
OnPause und onResume sind aktivitätsspezifisch. Keine Anwendung. Wenn eine App in den Hintergrund gestellt und dann fortgesetzt wird, setzt sie die spezifische Aktivität fort, in der sie sich befand, bevor sie in den Hintergrund wechselt. Dies bedeutet, dass Sie in allen Aktivitäten Ihrer Anwendung alles implementieren müssen, was Sie für die Wiederaufnahme des Hintergrunds tun möchten. Ich glaube, die ursprüngliche Frage suchte nach etwas wie einem "onResume" für die Anwendung und nicht nach Aktivität.
SysHex
4
Ich kann nicht glauben, dass eine richtige API für einen so häufigen Bedarf nicht angeboten wird. Anfangs dachte ich, onUserLeaveHint () würde es schneiden, aber Sie können nicht sagen, ob der Benutzer die Anwendung verlässt oder nicht
atsakiridis
197

2018: Android unterstützt dies nativ über Lebenszykluskomponenten.

März 2018 UPDATE : Es gibt jetzt eine bessere Lösung. Siehe ProcessLifecycleOwner . Sie müssen die neuen Architekturkomponenten 1.1.0 (aktuell zu diesem Zeitpunkt) verwenden, diese wurden jedoch speziell dafür entwickelt.

Diese Antwort enthält ein einfaches Beispiel , aber ich habe eine Beispiel-App und einen Blog-Beitrag darüber geschrieben.

Seit ich das 2014 geschrieben habe, sind verschiedene Lösungen entstanden. Einige funktionierten, andere funktionierten , hatten aber Fehler (einschließlich meiner!) Und wir als Community (Android) lernten, mit den Konsequenzen zu leben, und schrieben Problemumgehungen für die Sonderfälle.

Gehen Sie niemals davon aus, dass ein einziger Codeausschnitt die Lösung ist, nach der Sie suchen. Dies ist unwahrscheinlich. Besser noch, versuchen Sie zu verstehen, was es tut und warum es es tut.

Die MemoryBossKlasse wurde von mir nie so verwendet, wie hier geschrieben, es war nur ein Stück Pseudocode, der zufällig funktionierte.

Wenn es keinen triftigen Grund für Sie gibt, die neuen Architekturkomponenten nicht zu verwenden (und es gibt einige, insbesondere wenn Sie auf super alte APIs abzielen), fahren Sie fort und verwenden Sie sie. Sie sind alles andere als perfekt, aber sie waren es auch nicht ComponentCallbacks2.

UPDATE / NOTES (November 2015) : Die Leute haben zwei Kommentare abgegeben. Erstens >=sollte diese verwendet werden, anstatt, ==weil in der Dokumentation angegeben ist, dass Sie nicht nach genauen Werten suchen sollten . Dies ist in den meisten Fällen in Ordnung, aber denken Sie daran, dass Sie == verwenden und es auch mit einer anderen Lösung (wie Activity Lifecycle-Rückrufen) oder Ihnen kombinieren müssen , wenn Sie nur etwas tun möchten, wenn die App in den Hintergrund tritt möglicherweise nicht den gewünschten Effekt erzielen. Das Beispiel (und das ist mir passiert) ist das, wenn Sie sperren möchtenWenn Ihre App mit einem Kennwortbildschirm in den Hintergrund tritt (wie 1Password, wenn Sie damit vertraut sind), können Sie Ihre App versehentlich sperren, wenn Ihnen der Arbeitsspeicher ausgeht und Sie plötzlich darauf testen >= TRIM_MEMORY, da Android einen LOW MEMORYAnruf auslöst höher als deins. Seien Sie also vorsichtig, wie / was Sie testen.

Darüber hinaus haben einige Leute gefragt, wie Sie erkennen können, wann Sie zurückkommen.

Der einfachste Weg, den ich mir vorstellen kann, wird unten erklärt, aber da einige Leute damit nicht vertraut sind, füge ich hier einen Pseudocode hinzu. Angenommen, Sie haben YourApplicationund die MemoryBossKlassen in Ihrer class BaseActivity extends Activity(Sie müssen eine erstellen, wenn Sie keine haben).

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

Ich empfehle onStart, da Dialoge eine Aktivität anhalten können. Ich wette, Sie möchten nicht, dass Ihre App denkt, dass sie in den Hintergrund tritt, wenn Sie nur einen Vollbilddialog angezeigt haben, aber Ihr Kilometerstand kann variieren.

Und das ist alles. Der Code in der if - Block wird nur einmal ausgeführt werden , auch wenn Sie auf eine andere Tätigkeit gehen, die neue (das auch extends BaseActivity) berichten wasInBackgroundist , falseso dass es den Code nicht ausgeführt wird, bis onMemoryTrimmedaufgerufen wird und das Flag auf true gesetzt wieder .

Ich hoffe, das hilft.

UPDATE / NOTES (April 2015) : Bevor Sie mit dem Kopieren und Einfügen dieses Codes beginnen, beachten Sie, dass ich einige Fälle gefunden habe, in denen er möglicherweise nicht 100% zuverlässig ist und mit anderen Methoden kombiniert werden muss , um die besten Ergebnisse zu erzielen. Insbesondere sind zwei Fälle bekannt, in denen die onTrimMemoryAusführung des Rückrufs nicht garantiert werden kann:

  1. Wenn Ihr Telefon den Bildschirm sperrt, während Ihre App sichtbar ist (sagen wir, Ihr Gerät wird nach nn Minuten gesperrt), wird dieser Rückruf nicht (oder nicht immer) aufgerufen, da der Sperrbildschirm nur oben angezeigt wird, Ihre App jedoch weiterhin "ausgeführt" wird, obwohl sie abgedeckt ist.

  2. Wenn Ihr Gerät relativ wenig Speicherplatz hat (und unter Speicherstress steht), scheint das Betriebssystem diesen Aufruf zu ignorieren und direkt zu kritischeren Ebenen zu wechseln.

Abhängig davon, wie wichtig es für Sie ist, zu wissen, wann Ihre App in den Hintergrund getreten ist, müssen Sie diese Lösung möglicherweise erweitern oder nicht, um den Aktivitätslebenszyklus und so weiter zu verfolgen.

Denken Sie einfach an die oben genannten Punkte und haben Sie ein gutes QS-Team;)

ENDE DES UPDATE

Es mag spät sein, aber es gibt eine zuverlässige Methode in Ice Cream Sandwich (API 14) und höher .

Es stellt sich heraus, dass ein Rückruf ausgelöst wird, wenn Ihre App keine sichtbare Benutzeroberfläche mehr hat. Der Rückruf, den Sie in einer benutzerdefinierten Klasse implementieren können, heißt ComponentCallbacks2 (ja, mit zwei). Dieser Rückruf ist nur in API Level 14 (Ice Cream Sandwich) und höher verfügbar .

Grundsätzlich erhalten Sie einen Aufruf der Methode:

public abstract void onTrimMemory (int level)

Das Level ist 20 oder genauer

public static final int TRIM_MEMORY_UI_HIDDEN

Ich habe dies getestet und es funktioniert immer, da Level 20 nur ein "Vorschlag" ist, dass Sie möglicherweise einige Ressourcen freigeben möchten, da Ihre App nicht mehr sichtbar ist.

Um die offiziellen Dokumente zu zitieren:

Level für onTrimMemory (int): Der Prozess hat eine Benutzeroberfläche angezeigt und tut dies nicht mehr. Zu diesem Zeitpunkt sollten große Zuordnungen mit der Benutzeroberfläche freigegeben werden, damit der Speicher besser verwaltet werden kann.

Natürlich sollten Sie dies implementieren , um tatsächlich tun , was er sagt (Purge - Speicher , die in bestimmten Zeit verwendet hat, deaktivieren Sie einige Sammlungen nicht die ungenutzt gesessen haben usw. Die Möglichkeiten sind endlos (siehe die offiziellen Dokumente für andere möglich mehr kritische Ebenen).

Aber das Interessante ist, dass das Betriebssystem Ihnen sagt: Hey, Ihre App ist in den Hintergrund getreten!

Welches ist genau das, was Sie zuerst wissen wollten.

Wie bestimmen Sie, wann Sie zurück sind?

Nun, das ist einfach. Ich bin sicher, Sie haben eine "BaseActivity", sodass Sie mit onResume () darauf hinweisen können , dass Sie zurück sind. Denn Sie werden nur dann sagen, dass Sie nicht zurück sind, wenn Sie tatsächlich einen Anruf bei der oben genannten onTrimMemoryMethode erhalten.

Es klappt. Sie erhalten keine Fehlalarme. Wenn eine Aktivität wieder aufgenommen wird, sind Sie zu 100% zurück. Wenn der Benutzer wieder nach hinten geht, erhalten Sie einen weiteren onTrimMemory()Anruf.

Sie müssen Ihre Aktivitäten (oder noch besser eine benutzerdefinierte Klasse) abonnieren.

Der einfachste Weg, um sicherzustellen, dass Sie dies immer erhalten, besteht darin, eine einfache Klasse wie die folgende zu erstellen:

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

Um dies zu verwenden, gehen Sie in Ihrer Anwendungsimplementierung ( Sie haben eine, RECHTS? ) So vor:

MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
   super.onCreate();
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      mMemoryBoss = new MemoryBoss();
      registerComponentCallbacks(mMemoryBoss);
   } 
}

Wenn Sie ein erstellen Interfacekönnen Sie eine hinzufügen elsezu , dass ifund implementieren ComponentCallbacks(ohne die 2) in etwas unterhalb API verwendet 14. Das Callback nur das hat onLowMemory()Methode und nicht aufgerufen wird erhalten , wenn Sie in den Hintergrund , aber Sie es zu trimmen Speicher verwenden sollen .

Starten Sie jetzt Ihre App und drücken Sie nach Hause. Ihre onTrimMemory(final int level)Methode sollte aufgerufen werden (Hinweis: Protokollierung hinzufügen).

Der letzte Schritt besteht darin, die Registrierung vom Rückruf aufzuheben. Der wahrscheinlich beste Ort ist die onTerminate()Methode Ihrer App, aber diese Methode wird auf einem realen Gerät nicht aufgerufen:

/**
 * 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.
 */

Wenn Sie also nicht wirklich eine Situation haben, in der Sie nicht mehr registriert werden möchten, können Sie dies ignorieren, da Ihr Prozess auf Betriebssystemebene ohnehin zum Erliegen kommt.

Wenn Sie sich irgendwann entscheiden, die Registrierung aufzuheben (wenn Sie beispielsweise einen Mechanismus zum Herunterfahren Ihrer App bereitstellen, um zu bereinigen und zu sterben), können Sie Folgendes tun:

unregisterComponentCallbacks(mMemoryBoss);

Und das ist es.

Martin Marconcini
quelle
Wenn Sie dies von einem Dienst aus überprüfen, scheint es nur zu feuern, wenn die Home-Taste gedrückt wird. Durch Drücken der Zurück-Taste wird dies bei KitKat nicht ausgelöst.
Lernen Sie OpenGL ES
Der Dienst hat keine Benutzeroberfläche, daher könnte dies damit zusammenhängen. Führen Sie den Check in Ihrer Basisaktivität durch, nicht in einem Service. Sie möchten wissen, wann Ihre Benutzeroberfläche ausgeblendet ist (und vielleicht dem Dienst mitteilen, damit er in den Vordergrund geht)
Martin Marconcini
1
Es funktioniert nicht, wenn Sie Ihr Telefon ausschalten. Es wird nicht ausgelöst.
Juangcg
2
Die Verwendung von ComponentCallbacks2.onTrimMemory () (in Kombination mit ActivityLifecycleCallbacks) ist die einzige zuverlässige Lösung, die ich bisher gefunden habe, danke Martin! Für Interessierte siehe meine Antwort.
Rickul
3
Ich verwende diese Methode seit einem Jahr und sie war für mich immer zuverlässig. Es ist gut zu wissen, dass andere Leute es auch benutzen. Ich verwende nur das, level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENwas das Problem in Ihrem Update vermeidet, Punkt 2. In Bezug auf Punkt 1 ist es mir kein Problem, da die App nicht wirklich in den Hintergrund getreten ist, also sollte es so funktionieren.
Sorianiv
175

So habe ich es geschafft, das zu lösen. Es wird davon ausgegangen, dass die Verwendung einer Zeitreferenz zwischen Aktivitätsübergängen höchstwahrscheinlich einen ausreichenden Beweis dafür liefert, dass eine App "im Hintergrund" ist oder nicht.

Zuerst habe ich eine android.app.Application-Instanz (nennen wir sie MyApplication) verwendet, die einen Timer, eine TimerTask, eine Konstante hat, um die maximale Anzahl von Millisekunden darzustellen, die der Übergang von einer Aktivität zu einer anderen vernünftigerweise dauern könnte (ich ging mit einem Wert von 2s) und einem Booleschen Wert, der angibt, ob sich die App "im Hintergrund" befand oder nicht:

public class MyApplication extends Application {

    private Timer mActivityTransitionTimer;
    private TimerTask mActivityTransitionTimerTask;
    public boolean wasInBackground;
    private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
    ...

Die Anwendung bietet außerdem zwei Methoden zum Starten und Stoppen des Timers / der Aufgabe:

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

Der letzte Teil dieser Lösung besteht darin, jeder dieser Methoden einen Aufruf aus den Ereignissen onResume () und onPause () aller Aktivitäten oder vorzugsweise einer Basisaktivität hinzuzufügen, von der alle Ihre konkreten Aktivitäten erben:

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

Wenn der Benutzer also einfach zwischen den Aktivitäten Ihrer App navigiert, startet die onPause () der abgehenden Aktivität den Timer, aber fast sofort bricht die neu eingegebene Aktivität den Timer ab, bevor er die maximale Übergangszeit erreichen kann. Und so wäre InBackground falsch .

Wenn andererseits eine Aktivität vom Launcher in den Vordergrund tritt, das Gerät aufwacht, den Telefonanruf beendet usw., wurde die Timer-Task höchstwahrscheinlich vor diesem Ereignis ausgeführt und somit wurde InBackground auf true gesetzt .

d60402
quelle
4
Hallo d60402, Ihre Antwort ist wirklich hilfreich. Vielen Dank für diese Antwort. Kleiner Hinweis. MyApplication sollte im Manifest-Dateianwendungs-Tag wie android: name = "MyApplication" erwähnt werden, andernfalls stürzt die App ab ... nur um zu helfen jemand wie ich
Praveenb
2
Zeichen des großen Programmierers, einfache Lösung für eines der kompliziertesten Probleme, auf die ich jemals gestoßen bin.
Aashish Bhatnagar
2
Tolle Lösung! Vielen Dank. Wenn jemand den Fehler "ClassCastException" erhält, haben Sie möglicherweise das Hinzufügen des Anwendungs-Tags in Ihrer Manifest.xml <application android: name = "your.package.MyApplication"
Wahib Ul Haq
27
Dies ist eine schöne und einfache Implementierung. Ich bin jedoch der Meinung, dass dies eher in onStart / onStop als in onPause / onResume implementiert werden sollte. Die onPause wird auch dann aufgerufen, wenn ich einen Dialog starte, der die Aktivität teilweise abdeckt. Und das Schließen des Dialogs würde tatsächlich onResume aufrufen und es so erscheinen lassen, als ob die App gerade in den Vordergrund
getreten wäre
7
Ich hoffe, eine Variation dieser Lösung verwenden zu können. Der oben identifizierte Punkt über Dialoge ist ein Problem für mich, deshalb habe ich @ Shubhayus Vorschlag (onStart / onStop) ausprobiert. Dies hilft jedoch nicht, da bei A-> B onStart () von Aktivität B vor onStop () von Aktivität A aufgerufen wird.
Trevor
150

Bearbeiten: Die neuen Architekturkomponenten brachten etwas Vielversprechendes: ProcessLifecycleOwner , siehe @ vokilams Antwort


Die eigentliche Lösung nach einem Google I / O-Vortrag :

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

}

Ja. Ich weiß, es ist schwer zu glauben, dass diese einfache Lösung funktioniert, da wir hier so viele seltsame Lösungen haben.

Aber es gibt Hoffnung.

Fred Porciúncula
quelle
3
Das funktioniert perfekt! Ich habe bereits so viele seltsame Lösungen ausprobiert, die so viele Fehler hatten ... vielen Dank! Ich habe eine Weile danach gesucht.
Eggakin Baconwalker
7
Es funktioniert für mehrere Aktivitäten, aber für eine - Onrotate zeigt an, dass alle Aktivitäten weg sind oder im Hintergrund
Deadfish
2
@Shyri Sie haben Recht, aber das ist Teil dieser Lösung, also müssen Sie sich Sorgen machen. Wenn Firebase sich darauf verlässt, denke ich, dass meine mittelmäßige App das auch kann :) Tolle Antwort übrigens.
ElliotM
3
@deadfish Überprüfen Sie den Link zu E / A oben in der Antwort. Sie können die Zeitlücken zwischen dem Stoppen und Starten der Aktivität überprüfen, um festzustellen, ob Sie wirklich in den Hintergrund gegangen sind oder nicht. Dies ist eigentlich eine brillante Lösung.
Alex Berdnikov
2
Gibt es eine Java-Lösung? Das ist Kotlin.
Giacomo Bartoli
116

ProcessLifecycleOwner scheint auch eine vielversprechende Lösung zu sein.

ProcessLifecycleOwner versenden ON_START, ON_RESUMEEreignisse, als erste Aktivität bewegt sich durch diese Ereignisse. ON_PAUSE, ON_STOPWird Veranstaltungen mit versendet werden Verzögerung nach einer letzten Aktivität durch sie geleitet. Diese Verzögerung ist lang genug, um sicherzustellen, dass ProcessLifecycleOwnerkeine Ereignisse gesendet werden, wenn Aktivitäten aufgrund einer Konfigurationsänderung zerstört und neu erstellt werden.

Eine Implementierung kann so einfach sein wie

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

Der aktuelle Verzögerungswert beträgt laut Quellcode 700ms.

Für die Verwendung dieser Funktion ist Folgendes erforderlich dependencies:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
Vokilam
quelle
10
Beachten Sie, dass Sie die Lebenszyklusabhängigkeiten implementation "android.arch.lifecycle:extensions:1.0.0"und annotationProcessor "android.arch.lifecycle:compiler:1.0.0"aus dem Google-Repository (dh google())
hinzufügen müssen
1
Das hat super für mich funktioniert, danke. Ich musste api 'android.arch.lifecycle: extensions: 1.1.0' anstelle der Implementierung verwenden, da der Fehler darin bestand, dass die Android-Abhängigkeit eine andere Version für den Kompilierungs- und Laufzeitklassenpfad hat.
FSUWX2011
Dies ist eine großartige Lösung, da sie in Modulen funktioniert, ohne dass eine Aktivitätsreferenz erforderlich ist!
Max
Dies funktioniert nicht, wenn die App abgestürzt ist. Gibt es eine Lösung, um App Absturz Ereignis auch durch diese Lösung zu bekommen
tejraj
Tolle Lösung. Hat meinen Tag gerettet.
Sonniger
69

Basierend auf Martín Marconcinis Antwort (danke!) Habe ich endlich eine zuverlässige (und sehr einfache) Lösung gefunden.

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

Fügen Sie dies dann zu Ihrem onCreate () Ihrer Anwendungsklasse hinzu

public class MyApp extends android.app.Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
        registerActivityLifecycleCallbacks(handler);
        registerComponentCallbacks(handler);

    }

}
Rickul
quelle
Kannst du zeigen, wie du das in einer App verwendest? Nenne ich das aus der App-Klasse oder woanders?
JPM
das ist perfekt danke !! funktioniert bisher hervorragend beim Testen
aherrick
Dieses Beispiel ist unvollständig. Was ist registerActivityLifecycleCallbacks?
Noman
Es ist eine Methode in der Klasse android.app.Application
Rickul
1
gut gemacht +1, um an die Spitze zu kommen, weil es perfekt ist, schauen Sie nicht nach anderen Antworten, dies basiert auf @reno Antwort, aber mit echtem Beispiel
Stoycho Andreev
63

Wir verwenden diese Methode. Es sieht zu einfach aus, um zu arbeiten, aber es wurde in unserer App gut getestet und funktioniert in allen Fällen überraschend gut, einschließlich des Aufrufen des Startbildschirms über die Schaltfläche "Startseite", über die Schaltfläche "Zurück" oder nach der Bildschirmsperre. Versuche es.

Die Idee ist, dass Android im Vordergrund immer neue Aktivitäten startet, bevor die vorherige beendet wird. Das ist nicht garantiert, aber so funktioniert es. Übrigens scheint Flurry dieselbe Logik zu verwenden (nur eine Vermutung, das habe ich nicht überprüft, aber es hängt mit denselben Ereignissen zusammen).

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

}

Bearbeiten: Gemäß den Kommentaren sind wir in späteren Versionen des Codes auch zu onStart () übergegangen. Außerdem füge ich Superanrufe hinzu, die in meinem ersten Beitrag fehlten, da dies eher ein Konzept als ein Arbeitscode war.

Nick Frolov
quelle
2
Dies ist die zuverlässigste Antwort, obwohl ich onStart anstelle von onResume verwende.
Greg Ennis
Sie sollten super.onResume () und super.onStop () in den Überschreibungsmethoden aufrufen. Andernfalls wird eine android.app.SuperNotCalledException ausgelöst.
Jan Laussmann
1
Für mich funktioniert es nicht ... oder es löst zumindest das Ereignis aus, wenn Sie das Gerät auch drehen (was imho eine Art falsch positives Ergebnis ist).
Noya
Sehr einfache und effektive Lösung! Ich bin mir jedoch nicht sicher, ob es mit teilweise transparenten Aktivitäten funktioniert, bei denen einige Teile der vorherigen Aktivität sichtbar sind. Aus den Dokumenten , onStop is called when the activity is no longer visible to the user.
Nicolas Buquet
3
Was passiert, wenn der Benutzer die Ausrichtung bei der ersten Aktivität ändert? Es wird gemeldet, dass die App in den Hintergrund gegangen ist, was nicht stimmt. Wie gehen Sie mit diesem Szenario um?
Nimrod Dayan
54

Wenn Ihre App aus mehreren Aktivitäten und / oder gestapelten Aktivitäten wie einem Registerkartenleisten-Widget besteht, funktioniert das Überschreiben von onPause () und onResume () nicht. Das heißt, wenn eine neue Aktivität gestartet wird, werden die aktuellen Aktivitäten angehalten, bevor die neue erstellt wird. Gleiches gilt, wenn Sie eine Aktivität beenden (mit der Schaltfläche "Zurück").

Ich habe zwei Methoden gefunden, die wie gewünscht zu funktionieren scheinen.

Die erste erfordert die Berechtigung GET_TASKS und besteht aus einer einfachen Methode, die durch Vergleichen der Paketnamen überprüft, ob die am häufigsten ausgeführte Aktivität auf dem Gerät zur Anwendung gehört:

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

Diese Methode wurde im Droid-Fu-Framework (jetzt Ignition genannt) gefunden.

Die zweite Methode, die ich selbst implementiert habe, erfordert nicht die Berechtigung GET_TASKS, was gut ist. Stattdessen ist die Implementierung etwas komplizierter.

In Ihrer MainApplication-Klasse haben Sie eine Variable, die die Anzahl der ausgeführten Aktivitäten in Ihrer Anwendung verfolgt. In onResume () erhöhen Sie für jede Aktivität die Variable und in onPause () verringern Sie sie.

Wenn die Anzahl der ausgeführten Aktivitäten 0 erreicht, wird die Anwendung in den Hintergrund gestellt, wenn die folgenden Bedingungen erfüllt sind:

  • Die angehaltene Aktivität wird nicht beendet (die Schaltfläche "Zurück" wurde verwendet). Dies kann mithilfe der Methode activity.isFinishing () erfolgen.
  • Eine neue Aktivität (gleicher Paketname) wird nicht gestartet. Sie können die Methode startActivity () überschreiben, um eine Variable festzulegen, die dies anzeigt, und sie dann in onPostResume () zurücksetzen. Dies ist die letzte Methode, die ausgeführt wird, wenn eine Aktivität erstellt / fortgesetzt wird.

Wenn Sie feststellen können, dass die Anwendung im Hintergrund zurückgetreten ist, können Sie leicht erkennen, wenn sie ebenfalls wieder in den Vordergrund gestellt wird.

Emil
quelle
18
Google wird wahrscheinlich eine App ablehnen, die ActivityManager.getRunningTasks () verwendet. Die Dokumentation besagt, dass es sich nur um Feindentwicklungszwecke handelt. developer.android.com/reference/android/app/…
Sky Kelsey
getRunnintTasks()
Cornstalks
1
Ich stellte fest, dass ich eine Kombination dieser Ansätze verwenden musste. onUserLeaveHint () wurde aufgerufen, als eine Aktivität in 14 gestartet wurde. `@Override public void onUserLeaveHint () {inBackground = isApplicationBroughtToBackground (); } `
Listing Boot
7
Benutzer werden sich nicht über die Verwendung einer leistungsstarken Berechtigung android.permission.GET_TASKS freuen.
MSquare
6
getRunningTasks ist in API Level 21 veraltet.
Noya
33

Erstellen Sie eine Klasse , die erweitert wird Application. Dann können wir darin seine Override-Methode verwenden onTrimMemory().

Um festzustellen, ob die Anwendung in den Hintergrund getreten ist, verwenden wir:

 @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
quelle
1
Für FragmentActivitySie auch hinzufügen möchten level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEauch.
Srujan Simha
2
Vielen Dank für den Hinweis auf diese Methode. Ich muss einen Pin-Dialog anzeigen, wenn der Benutzer die Aktivität für den Hintergrund fortsetzt, diese Methode zum Schreiben eines Voreinstellungswerts verwendet und diesen Wert in baseActivity überprüft hat.
Sam
18

Erwägen Sie die Verwendung von onUserLeaveHint. Dies wird nur aufgerufen, wenn Ihre App in den Hintergrund tritt. onPause muss Eckfälle behandeln, da es aus anderen Gründen aufgerufen werden kann. Wenn der Benutzer beispielsweise eine andere Aktivität in Ihrer App öffnet, z. B. Ihre Einstellungsseite, wird die onPause-Methode Ihrer Hauptaktivität aufgerufen, obwohl sie sich noch in Ihrer App befindet. Das Verfolgen der Vorgänge führt zu Fehlern, wenn Sie stattdessen einfach den onUserLeaveHint-Rückruf verwenden können, der genau das tut, was Sie verlangen.

Wenn auf UserLeaveHint aufgerufen wird, können Sie ein boolesches inBackground-Flag auf true setzen. Wenn onResume aufgerufen wird, nehmen Sie nur an, dass Sie wieder in den Vordergrund zurückgekehrt sind, wenn das Flag inBackground gesetzt ist. Dies liegt daran, dass onResume auch für Ihre Hauptaktivität aufgerufen wird, wenn sich der Benutzer nur in Ihrem Einstellungsmenü befand und die App nie verlassen hat.

Denken Sie daran, dass onUserLeaveHint in Ihrer Einstellungsaktivität aufgerufen wird, wenn der Benutzer in Ihrem Einstellungsbildschirm auf die Home-Schaltfläche drückt, und wenn er zurückkehrt, wird onResume in Ihrer Einstellungsaktivität aufgerufen. Wenn Sie nur diesen Erkennungscode in Ihrer Hauptaktivität haben, werden Sie diesen Anwendungsfall verpassen. Um diesen Code in all Ihren Aktivitäten zu haben, ohne Code zu duplizieren, verfügen Sie über eine abstrakte Aktivitätsklasse, die die Aktivität erweitert, und fügen Sie Ihren gemeinsamen Code ein. Dann kann jede Aktivität, die Sie haben, diese abstrakte Aktivität erweitern.

Zum Beispiel:

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
quelle
19
onUserLeaveHint wird auch aufgerufen, wenn Sie zu einer anderen Aktivität navigieren
Jonas Stawski
3
onUserLeaveHint wird nicht aufgerufen, wenn z. B. ein Telefonanruf eingeht und die Anrufaktivität aktiv wird. Dies hat also auch einen Randfall - es kann auch andere Fälle geben, da Sie der Absicht, den Anruf onUserLeaveHint zu unterdrücken, ein Flag hinzufügen können. developer.android.com/reference/android/content/…
Groxx
1
Außerdem funktioniert onResume nicht gut. Ich habe es versucht und manchmal wird onResume aufgerufen, während das Telefon gesperrt ist. Wenn Sie die Definition von onResume in der Dokumentation sehen, werden Sie Folgendes feststellen: Beachten Sie, dass onResume nicht der beste Indikator dafür ist, dass Ihre Aktivität für den Benutzer sichtbar ist. Ein Systemfenster wie der Schlüsselschutz kann vorne sein. Verwenden Sie onWindowFocusChanged (Boolescher Wert), um sicherzugehen, dass Ihre Aktivität für den Benutzer sichtbar ist (z. B. um ein Spiel fortzusetzen). developer.android.com/reference/android/app/…
J-Rou
Diese Lösung hilft nicht bei der Entscheidung über Vordergrund / Hintergrund, wenn es mehrere Aktivitäten gibt. Bitte
Raj Trivedi
14

ActivityLifecycleCallbacks sind möglicherweise von Interesse, aber nicht gut dokumentiert.

Wenn Sie registerActivityLifecycleCallbacks () aufrufen , sollten Sie in der Lage sein, Rückrufe zu erhalten, wenn Aktivitäten erstellt, zerstört usw. werden. Sie können getComponentName () für die Aktivität aufrufen .

Reno
quelle
11
Seit API Level 14 = \
Imort
Sieht so aus, als wäre dieser sauber und funktioniert für mich. Danke
duanbo1983
Wie unterscheidet sich dies von der akzeptierten Antwort? Beide basieren auf demselben Aktivitätslebenszyklus, oder?
Saitama
13

Das Paket android.arch.lifecycle bietet Klassen und Schnittstellen, mit denen Sie lebenszyklusbewusste Komponenten erstellen können

Ihre Anwendung sollte die LifecycleObserver-Schnittstelle implementieren:

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

Dazu müssen Sie diese Abhängigkeit zu Ihrer build.gradle-Datei hinzufügen:

dependencies {
    implementation "android.arch.lifecycle:extensions:1.1.1"
}

Wie von Google empfohlen, sollten Sie den Code minimieren, der in den Lebenszyklusmethoden von Aktivitäten ausgeführt wird:

Ein übliches Muster besteht darin, die Aktionen der abhängigen Komponenten in den Lebenszyklusmethoden von Aktivitäten und Fragmenten zu implementieren. Dieses Muster führt jedoch zu einer schlechten Organisation des Codes und zur Verbreitung von Fehlern. Durch die Verwendung lebenszyklusbewusster Komponenten können Sie den Code abhängiger Komponenten aus den Lebenszyklusmethoden in die Komponenten selbst verschieben.

Weitere Informationen finden Sie hier: https://developer.android.com/topic/libraries/architecture/lifecycle

matdev
quelle
und fügen Sie dies hinzu, um sich wie folgt zu manifestieren: <application android: name = ". AnotherApp">
Dan Alboteanu
9

Fügen Sie in Ihrer Anwendung den Rückruf hinzu und suchen Sie auf folgende Weise nach Root-Aktivitäten:

@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
quelle
Ich würde diese Art der Implementierung in Betracht ziehen. Ein Übergang von einer Aktivität zu einer anderen dauert nur wenige Millisekunden. Basierend auf dem Zeitpunkt, zu dem die letzte Aktivität verschwindet, kann davon ausgegangen werden, dass der Benutzer durch eine bestimmte Strategie erneut angemeldet wird.
Drindt
6

Ich habe ein Projekt auf Github App-Vordergrund-Hintergrund-Listen erstellt

Erstellen Sie eine BaseActivity für alle Aktivitäten in Ihrer Anwendung.

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

Verwenden Sie diese BaseActivity nun als Superklasse für alle Ihre Aktivitäten, wie MainActivity BaseActivity erweitert, und onAppStart wird aufgerufen, wenn Sie Ihre Anwendung starten, und onAppPause () wird aufgerufen, wenn die Anwendung von einem beliebigen Bildschirm aus in den Hintergrund tritt.

kiran boghra
quelle
@ kiran boghra: Gibt es falsch positive Ergebnisse in Ihrer Lösung?
Harish Vishwakarma
Perfekte Antwort In diesem Fall können die Funktionen onStart () und onStop () verwendet werden. was Ihnen über Ihre App erzählt
Pir Fahim Shah
6

Dies ist mit ProcessLifecycleOwner ziemlich einfach

Fügen Sie diese Abhängigkeiten hinzu

implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"

In Kotlin :

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

Dann in Ihrer Basisaktivität:

override fun onCreate() {
        super.onCreate()

        ProcessLifecycleOwner.get()
                .lifecycle
                .addObserver(
                        ForegroundBackgroundListener()
                                .also { appObserver = it })
    }

Siehe meinen Artikel zu diesem Thema: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48

Ege Kuzubasioglu
quelle
5

Sie können den ProcessLifecycleOwner verwenden, um einen Lebenszyklusbeobachter daran anzuhängen .

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

dann onCreate()nennen Sie in Ihrer Anwendungsklasse Folgendes:

ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());

Auf diese Weise können Sie die Ereignisse ON_PAUSEund ON_STOPAnwendungen Ihrer Anwendung erfassen, die im Hintergrund auftreten.

Alécio Carvalho
quelle
4

Es gibt keine einfachen Lebenszyklusmethoden, mit denen Sie feststellen können, wann die gesamte Anwendung in den Hintergrund tritt.

Ich habe das auf einfache Weise gemacht. Befolgen Sie die folgenden Anweisungen, um die Hintergrund- / Vordergrundphase der Anwendung zu erkennen.

Mit einer kleinen Problemumgehung ist dies möglich. Hier hilft ActivityLifecycleCallbacks . Lassen Sie mich Schritt für Schritt durchgehen.

  1. Erstellen Sie zunächst eine Klasse, die die android.app.Application erweitert und die ActivityLifecycleCallbacks- Schnittstelle implementiert . Registrieren Sie in Application.onCreate () den Rückruf.

    public class App extends Application implements 
        Application.ActivityLifecycleCallbacks {
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    }
  2. Registrieren Sie die App-Klasse im Manifest wie folgt <application android:name=".App".

  3. Es befindet sich mindestens eine Aktivität im gestarteten Zustand, wenn sich die App im Vordergrund befindet, und es befindet sich keine Aktivität im gestarteten Zustand, wenn sich die App im Hintergrund befindet.

    Deklarieren Sie 2 Variablen wie folgt in der Klasse „App“.

    private int activityReferences = 0;
    private boolean isActivityChangingConfigurations = false;

    activityReferenceshält die Anzahl der Aktivitäten im gestarteten Zustand. isActivityChangingConfigurationsist ein Flag, das angibt, ob die aktuelle Aktivität wie ein Orientierungsschalter eine Konfigurationsänderung durchläuft.

  4. Mit dem folgenden Code können Sie feststellen, ob die App im Vordergrund steht.

    @Override
    public void onActivityStarted(Activity activity) {
        if (++activityReferences == 1 && !isActivityChangingConfigurations) {
            // App enters foreground
        }
    }
  5. So erkennen Sie, ob die App in den Hintergrund tritt.

    @Override
    public void onActivityStopped(Activity activity) {
        isActivityChangingConfigurations = activity.isChangingConfigurations();
        if (--activityReferences == 0 && !isActivityChangingConfigurations) {
            // App enters background
        }
    }

Wie es funktioniert:

Dies ist ein kleiner Trick, der mit der Art und Weise ausgeführt wird, wie die Lifecycle-Methoden nacheinander aufgerufen werden. Lassen Sie mich ein Szenario durchgehen.

Angenommen, der Benutzer startet die App und die Startaktivität A wird gestartet. Die Lifecycle-Aufrufe lauten:

A.onCreate ()

A.onStart () (++ activityReferences == 1) (App tritt in den Vordergrund)

A.onResume ()

Jetzt startet Aktivität A Aktivität B.

A.onPause ()

B.onCreate ()

B.onStart () (++ activityReferences == 2)

B.onResume ()

A.onStop () (--activityReferences == 1)

Dann navigiert der Benutzer von Aktivität B zurück.

B.onPause ()

A.onStart () (++ activityReferences == 2)

A.onResume ()

B.onStop () (--activityReferences == 1)

B.onDestroy ()

Dann drückt der Benutzer die Home-Taste.

A.onPause ()

A.onStop () (--activityReferences == 0) (App gibt Hintergrund ein)

Falls der Benutzer die Home-Taste von Aktivität B anstelle der Zurück-Taste drückt, ist dies immer noch derselbe und die Aktivitätsreferenzen 0. Daher können wir erkennen, wie die App den Hintergrund betritt.

Also, was ist die Rolle von isActivityChangingConfigurations? Angenommen, die Aktivität B ändert im obigen Szenario die Ausrichtung. Die Rückrufsequenz lautet:

B.onPause ()

B.onStop () (--activityReferences == 0) (App gibt Hintergrund ein ??)

B.onDestroy ()

B.onCreate ()

B.onStart () (++ activityReferences == 1) (App tritt in den Vordergrund ??)

B.onResume ()

Aus diesem Grund haben wir eine zusätzliche Überprüfung isActivityChangingConfigurations, um das Szenario zu vermeiden, in dem die Aktivität die Konfigurationsänderungen durchläuft.

Komal Nikhare
quelle
3

Ich habe eine gute Methode gefunden, um Anwendungen zu erkennen, unabhängig davon, ob sie in den Vordergrund oder in den Hintergrund treten. Hier ist mein Code . Hoffe das hilft dir.

/**
 * 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
quelle
3

Sie können verwenden:

protected void onRestart ()

Unterschied zwischen Neustarts und Neustarts.

Geben Sie hier die Bildbeschreibung ein

AYBABTU
quelle
3

Edit 2: Was ich unten geschrieben habe, funktioniert eigentlich nicht. Google hat eine App abgelehnt, die einen Aufruf von ActivityManager.getRunningTasks () enthält. Aus der Dokumentation geht hervor, dass diese API nur zu Debugging- und Entwicklungszwecken dient. Ich werde diesen Beitrag aktualisieren, sobald ich Zeit habe, das GitHub-Projekt unten mit einem neuen Schema zu aktualisieren, das Timer verwendet und fast genauso gut ist.

Bearbeiten 1: Ich habe einen Blog-Beitrag geschrieben und ein einfaches GitHub-Repository erstellt , um dies wirklich einfach zu machen.

Die akzeptierte und die am besten bewertete Antwort sind nicht wirklich der beste Ansatz. Die Implementierung von isApplicationBroughtToBackground () durch die am besten bewertete Antwort behandelt nicht die Situation, in der die Hauptaktivität der Anwendung einer Aktivität nachgibt, die in derselben Anwendung definiert ist, aber ein anderes Java-Paket enthält. Ich habe einen Weg gefunden, dies zu tun, der in diesem Fall funktioniert.

Rufen Sie dies in onPause () auf, und Sie erfahren, ob Ihre Anwendung in den Hintergrund tritt, weil eine andere Anwendung gestartet wurde oder der Benutzer die Home-Taste gedrückt hat.

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;
}
Himmel Kelsey
quelle
Wenn Sie dies stattdessen in onStart () aufrufen, wird vermieden, dass es aufgerufen wird, wenn ein einfacher Dialog ausgelöst wird, beispielsweise wenn ein Alarm ausgelöst wird.
Sky Kelsey
2

Richtige Antwort hier

Erstellen Sie eine Klasse mit dem Namen MyApp wie folgt:

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

    }
}

Fügen Sie dann überall den gewünschten Code hinzu (bessere erste Aktivität, die in der App gestartet wurde):

MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);

Erledigt! Wenn sich die App im Hintergrund befindet, erhalten wir ein Protokoll, status : we are out und wenn wir in die App gehen, erhalten wir ein Protokollstatus : we are out

erfan
quelle
1

Meine Lösung wurde von der Antwort von @ d60402 inspiriert und basiert auch auf einem Zeitfenster, verwendet jedoch nicht Folgendes 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();
  }
}

wo das SingletonApplicationeine Erweiterung der ApplicationKlasse ist:

public class SingletonApplication extends Application {
  public boolean isInBackground = false;
  public long lastForegroundTransition = 0;
}
Injektor
quelle
1

Ich habe dies mit Google Analytics EasyTracker verwendet und es hat funktioniert. Es könnte erweitert werden, um das zu tun, was Sie suchen, indem Sie eine einfache Ganzzahl verwenden.

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

Ich weiß, dass es etwas spät ist, aber ich denke, all diese Antworten haben einige Probleme, während ich es wie unten gemacht habe, und das funktioniert perfekt.

Erstellen Sie einen Aktivitätslebenszyklus-Rückruf wie folgt:

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

    }
}

und registrieren Sie es einfach in Ihrer Anwendungsklasse wie folgt:

public class MyApp extends Application {

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
Amir Ziarati
quelle
Dies wird bei jeder Aktivität ständig aufgerufen. Wie kann ich dies verwenden, wenn ich zum Beispiel den Online-Status eines Benutzers erkennen möchte
Maksim Kniazev
das ist was die frage will. Es wird nur aufgerufen, wenn Sie zum Startbildschirm gehen und zu einer Aktivität zurückkehren.
Amir Ziarati
Wenn Sie Internet-Konnektivität meinen, denke ich, ist es besser, dies zu überprüfen, wenn Sie es brauchen. Wenn Sie eine API anrufen müssen, überprüfen Sie die Internetverbindung kurz vor dem Anruf.
Amir Ziarati
1

Dies scheint eine der kompliziertesten Fragen in Android zu sein, da Android (zum jetzigen Zeitpunkt) keine iOS-Entsprechungen applicationDidEnterBackground()oder applicationWillEnterForeground()Rückrufe hat. Ich habe eine AppState-Bibliothek verwendet , die von @jenzz zusammengestellt wurde .

[AppState] ist eine einfache, reaktive Android-Bibliothek, die auf RxJava basiert und Änderungen des App-Status überwacht. Es benachrichtigt Abonnenten jedes Mal, wenn die App in den Hintergrund tritt und wieder in den Vordergrund tritt.

Es stellte sich heraus, dass dies genau das ist, was ich brauchte, insbesondere weil meine App mehrere Aktivitäten hatte, sodass das einfache Überprüfen onStart()oder Aktivieren onStop()einer Aktivität dies nicht verhindern würde.

Zuerst habe ich diese Abhängigkeiten zu gradle hinzugefügt:

dependencies {
    compile 'com.jenzz.appstate:appstate:3.0.1'
    compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}

Dann war es einfach, diese Zeilen an einer geeigneten Stelle in Ihrem Code hinzuzufügen:

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

Abhängig davon, wie Sie das Observable abonnieren, müssen Sie es möglicherweise abbestellen, um Speicherverluste zu vermeiden. Nochmals mehr Infos auf der Github-Seite .

deniz
quelle
1

Dies ist die geänderte Version der Antwort von @ d60402: https://stackoverflow.com/a/15573121/4747587

Mach alles was dort erwähnt wird. Aber anstatt ein zu haben Base Activityund das als Elternteil für jede Aktivität und das Überschreiben des onResume()und zu machen onPause, gehen Sie wie folgt vor:

Fügen Sie in Ihrer Anwendungsklasse die folgende Zeile hinzu:

registerActivityLifecycleCallbacks (Rückruf Application.ActivityLifecycleCallbacks);

Dies callbackhat alle Methoden des Aktivitätslebenszyklus und Sie können jetzt onActivityResumed()und überschreiben onActivityPaused().

Schauen Sie sich diese Übersicht an: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b

Henry
quelle
1

Sie können dies leicht mit Hilfe von ActivityLifecycleCallbacksund so ComponentCallbacks2etwas wie unten erreichen.

Erstellen Sie eine Klasse, AppLifeCycleHandlerdie über diesen Schnittstellen implementiert ist.

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

In Ihrer Klasse, die Applicationimplementiert AppLifeCycleCallback, implementieren Sie , um die Rückrufe zu erhalten, wenn die App zwischen Vordergrund und Hintergrund wechselt. So etwas wie unten.

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

Hoffe das hilft.

BEARBEITEN Alternativ können Sie jetzt die Life Cycle-fähige Architekturkomponente verwenden.

Naveen TP
quelle
1

Da ich keinen Ansatz gefunden habe, der auch die Rotation ohne Überprüfung der Zeitstempel handhabt, dachte ich, ich teile auch, wie wir es jetzt in unserer App machen. Die einzige Ergänzung zu dieser Antwort https://stackoverflow.com/a/42679191/5119746 ist, dass wir auch die Ausrichtung berücksichtigen.

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

Dann haben wir für die Rückrufe zuerst den Lebenslauf:

   // ActivityLifecycleCallbacks

   override fun onActivityResumed(activity: Activity?) {

      mResumed++

      if (mAppIsInBackground) {

         // !!! App came from background !!! Insert code

         mAppIsInBackground = false
      }
      mOrientationWasChanged = false
    }

Und onActivityStopped:

   override fun onActivityStopped(activity: Activity?) {

       if (mResumed == mPaused && !mOrientationWasChanged) {

       // !!! App moved to background !!! Insert code

        mAppIsInBackground = true
    }

Und dann kommt hier der Zusatz: Auf Orientierungsänderungen prüfen:

   override fun onConfigurationChanged(newConfig: Configuration) {

       if (newConfig.orientation != mCurrentOrientation) {
           mCurrentOrientation = newConfig.orientation
           mOrientationWasChanged = true
       }
       super.onConfigurationChanged(newConfig)
   }

Das ist es. Hoffe das hilft jemandem :)

Julian Horst
quelle
1

Wir können diese Lösung erweitern mit 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
    }
}

Jetzt können wir diese LiveData abonnieren und die erforderlichen Ereignisse abfangen. Zum Beispiel:

appForegroundStateLiveData.observeForever { state ->
    when(state) {
        AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
        AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
    }
}
Alex Kisel
quelle
0

Diese Antworten scheinen nicht richtig zu sein. Diese Methoden werden auch aufgerufen, wenn eine andere Aktivität beginnt und endet. Was Sie tun können, ist, ein globales Flag beizubehalten (ja, globale Werte sind schlecht :) und dies bei jedem Start einer neuen Aktivität auf true zu setzen. Setzen Sie es im onCreate jeder Aktivität auf false. Dann aktivieren Sie in der onPause dieses Flag. Wenn es falsch ist, tritt Ihre App in den Hintergrund oder wird getötet.

Joris Weimar
quelle
Ich habe nicht über eine Datenbank gesprochen ... was meinst du?
Joris Weimar
Ich unterstütze deine Antwort. Obwohl wir diesen Flag-Wert in der Datenbank speichern können, während wir eine Pause einlegen, ist dies nicht die gute Lösung.
Sandeep P