446 Stimmen

Wie man erkennt, wenn eine Android-App in den Hintergrund geht und wieder in den Vordergrund kommt

Ich versuche, eine Anwendung zu schreiben, die etwas Bestimmtes tut, wenn sie nach einer gewissen Zeit wieder in den Vordergrund gebracht wird. Gibt es eine Möglichkeit zu erkennen, wann eine App in den Hintergrund oder in den Vordergrund gebracht wird?

231voto

Martin Marconcini Punkte 25006

2018: Android unterstützt dies von Haus aus durch Lebenszykluskomponenten.

März 2018 UPDATE : Es gibt jetzt eine bessere Lösung. Siehe ProcessLifecycleOwner . Sie müssen die neuen Architekturkomponenten 1.1.0 (die derzeit neueste Version) verwenden, aber es ist speziell zu diesem Zweck entwickelt.

Es gibt ein einfaches Beispiel in dieser Antwort aber ich habe eine Beispiel-Applikation und eine Blogbeitrag darüber.

Seit ich dies im Jahr 2014 geschrieben habe, gab es verschiedene Lösungen. Einige funktionierten, einige waren gedacht, um zu arbeiten Wir haben als Gemeinschaft (Android) gelernt, mit den Konsequenzen zu leben, und haben Umgehungslösungen für die speziellen Fälle entwickelt.

Gehen Sie niemals davon aus, dass ein einzelner Codeschnipsel die Lösung ist, nach der Sie suchen, das ist unwahrscheinlich; versuchen Sie lieber zu verstehen, was er tut und warum er es tut.

En MemoryBoss Klasse wurde von mir nie in der hier beschriebenen Form verwendet, sondern war nur ein Stück Pseudocode, das zufällig funktionierte.

Wenn es keinen triftigen Grund gibt, die Komponenten der neuen Architektur nicht zu verwenden (und davon gibt es einige, vor allem, wenn Sie auf sehr alte Apis abzielen), dann sollten Sie sie verwenden. Sie sind bei weitem nicht perfekt, aber das waren sie auch nicht ComponentCallbacks2 .

UPDATE / HINWEISE (November 2015) : Die Leute haben zwei Kommentare abgegeben, der erste ist, dass >= sollte stattdessen verwendet werden == weil die Dokumentation besagt, dass Sie sollte nicht auf exakte Werte geprüft werden . Dies ist in den meisten Fällen in Ordnung, aber bedenken Sie, dass Sie, wenn Sie sólo die gerne tun etwas wenn die Anwendung in den Hintergrund geht, müssen Sie == y auch mit einer anderen Lösung kombinieren (z.B. Activity Lifecycle Callbacks), oder Sie darf nicht bekommen Ihre gewünschte Wirkung. Das Beispiel (und das ist mir passiert) ist, dass Sie, wenn Sie wollen sperren Wenn Sie Ihre App mit einem Passwortbildschirm versehen, wenn sie in den Hintergrund geht (wie 1Password, wenn Sie damit vertraut sind), können Sie Ihre App versehentlich sperren, wenn Sie wenig Speicherplatz haben und plötzlich testen, ob >= TRIM_MEMORY denn Android löst eine LOW MEMORY Anruf und der ist höher als Ihrer. Seien Sie also vorsichtig, wie/was Sie testen.

Außerdem haben einige Leute gefragt, wie man feststellen kann, wann man zurückkommt.

Die einfachste Methode, die mir einfällt, wird weiter unten erklärt, aber da einige Leute damit nicht vertraut sind, füge ich hier einen Pseudocode ein. Angenommen, Sie haben YourApplication und die MemoryBoss Klassen, in Ihrem class BaseActivity extends Activity (falls Sie noch keine haben, müssen Sie eine erstellen).

@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, weil Dialoge eine Aktivität unterbrechen können. Ich wette, Sie wollen nicht, dass Ihre Anwendung denkt, sie sei in den Hintergrund gegangen", wenn Sie nur einen Vollbilddialog angezeigt haben.

Und das ist alles. Der Code im if-Block wird nur einmal ausgeführt werden Auch wenn Sie zu einer anderen Aktivität gehen, wird die neue Aktivität (die auch extends BaseActivity ) wird berichten wasInBackground es false damit er den Code nicht ausführt, bis onMemoryTrimmed aufgerufen und das Flag wieder auf true gesetzt wird .

Ich hoffe, das hilft.

UPDATE / HINWEISE (April 2015) : Bevor Sie diesen Code kopieren und einfügen, beachten Sie, dass ich einige Fälle gefunden habe, in denen er nicht 100% zuverlässig ist und muss kombiniert werden mit anderen Methoden, um die besten Ergebnisse zu erzielen. Insbesondere gibt es zwei bekannte Fälle wo die onTrimMemory wird nicht garantiert, dass der Rückruf ausgeführt wird:

  1. Wenn Ihr Telefon den Bildschirm sperrt, während Ihre App sichtbar ist (z. B. Ihr Gerät wird nach nn Minuten gesperrt), wird dieser Callback nicht (oder nicht immer) aufgerufen, da der Sperrbildschirm nur oben liegt, Ihre App aber noch "läuft", wenn auch verdeckt.

  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 überzugehen.

Je nachdem, wie wichtig es für Sie ist, zu wissen, wann Ihre Anwendung in den Hintergrund gegangen ist, müssen Sie diese Lösung vielleicht erweitern, indem Sie den Lebenszyklus der Aktivitäten verfolgen und so weiter.

Behalten Sie einfach die oben genannten Punkte im Hinterkopf und haben Sie ein gutes QA-Team ;)

ENDE DER AKTUALISIERUNG

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

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

Sie erhalten im Grunde einen Aufruf der Methode:

public abstract void onTrimMemory (int level)

Der Level ist 20 oder mehr speziell

public static final int TRIM_MEMORY_UI_HIDDEN

Ich habe das getestet und es funktioniert immer, denn Stufe 20 ist nur ein "Vorschlag", dass Sie vielleicht einige Ressourcen freigeben sollten, da Ihre App nicht mehr sichtbar ist.

Um die offiziellen Dokumente zu zitieren:

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

Natürlich sollten Sie dies so implementieren, dass es auch wirklich das tut, was es sagt (Speicher bereinigen, der in einer bestimmten Zeit nicht benutzt wurde, einige Sammlungen löschen, die ungenutzt geblieben sind, usw.). Die Möglichkeiten sind endlos (siehe die offiziellen Dokumente für andere mögliche kritischer Ebenen).

Das Interessante ist jedoch, dass das Betriebssystem Ihnen dies mitteilt: HEY, deine App ist in den Hintergrund gegangen!

Das ist genau das, was Sie ursprünglich wissen wollten.

Wie stellen Sie fest, wann Sie zurück sind?

Nun, das ist einfach, ich bin sicher, Sie haben eine "BaseActivity", so dass Sie puede Verwenden Sie Ihr onResume(), um zu signalisieren, dass Sie zurück sind. Denn das einzige Mal, dass Sie sagen, dass Sie nicht zurück sind, ist, wenn Sie tatsächlich einen Aufruf der oben genannten onTrimMemory Methode.

Es funktioniert. Es gibt keine Fehlalarme. Wenn eine Aktivität wieder aufgenommen wird, sind Sie in 100 % der Fälle wieder da. Wenn der Benutzer wieder zurückgeht, erhalten Sie eine weitere onTrimMemory() anrufen.

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

Der einfachste Weg, dies zu gewährleisten, ist die Erstellung einer einfachen Klasse wie dieser:

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 nutzen, müssen Sie in Ihrer Anwendungsimplementierung ( Sie haben eine, RICHTIG? ), tun Sie etwas wie:

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 eine Interface könnten Sie eine else dazu if und umsetzen ComponentCallbacks (ohne die 2), die in allem unter API 14 verwendet wird. Dieser Callback hat nur die onLowMemory() Methode und wird nicht aufgerufen, wenn Sie in den Hintergrund gehen aber Sie sollten es nutzen, um den Speicher zu reduzieren.

Starten Sie nun Ihre App und drücken Sie auf Home. Ihr onTrimMemory(final int level) Methode aufgerufen werden sollte (Hinweis: Protokollierung hinzufügen).

Der letzte Schritt ist die Abmeldung vom Rückruf. Wahrscheinlich ist der beste Ort dafür die onTerminate() Methode Ihrer App, mais wird diese Methode auf einem echten 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.
 */

Solange Sie also nicht wirklich eine Situation haben, in der Sie nicht mehr registriert werden wollen, können Sie es sicher ignorieren, da Ihr Prozess auf Betriebssystemebene sowieso stirbt.

Wenn Sie sich entscheiden, die Registrierung zu einem bestimmten Zeitpunkt aufzuheben (wenn Sie z. B. einen Mechanismus zum Herunterfahren Ihrer Anwendung bereitstellen), können Sie dies tun:

unregisterComponentCallbacks(mMemoryBoss);

Und das war's.

178voto

d60402 Punkte 3190

Ich habe dieses Problem folgendermaßen gelöst. Es basiert auf der Prämisse, dass die Verwendung einer Zeitreferenz zwischen Aktivitätsübergängen höchstwahrscheinlich einen ausreichenden Beweis dafür liefert, dass eine App "im Hintergrund" war oder nicht.

Zunächst habe ich eine Android.app.Application-Instanz (nennen wir sie MyApplication) verwendet, die einen Timer, eine TimerTask, eine Konstante für die maximale Anzahl von Millisekunden, die der Übergang von einer Aktivität zu einer anderen vernünftigerweise dauern könnte (ich habe einen Wert von 2s gewählt), und einen booleschen Wert enthält, der angibt, ob die App "im Hintergrund" ist 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, einen Aufruf zu jeder dieser Methoden von den onResume()- und onPause()-Ereignissen aller Aktivitäten oder, vorzugsweise, in 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 onPause() der auslaufenden Aktivität den Timer, aber fast sofort bricht die neue Aktivität, die eingegeben wird, den Timer ab, bevor er die maximale Übergangszeit erreichen kann. Und so wasInBackground wäre falsch .

Andererseits, wenn eine Aktivität vom Launcher in den Vordergrund kommt, das Gerät aufwacht, ein Telefonat beendet wird usw., ist es sehr wahrscheinlich, dass die Timer-Aufgabe vor diesem Ereignis ausgeführt wurde, und somit wasInBackground wurde festgelegt auf wahr .

172voto

vokilam Punkte 9843

UPDATE November 2021

Die tatsächliche Einrichtung sieht wie folgt aus

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
    }
}

class AppLifecycleListener : DefaultLifecycleObserver {

    override fun onStart(owner: LifecycleOwner) { // app moved to foreground
    }

    override fun onStop(owner: LifecycleOwner) { // app moved to background
    }
}

Abhängigkeiten

implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version"

ORIGINALANTWORT

ProcessLifecycleOwner scheint ebenfalls eine vielversprechende Lösung zu sein.

ProcessLifecycleOwner versendet ON_START , ON_RESUME Ereignisse, da eine erste Aktivität diese Ereignisse durchläuft. ON_PAUSE , ON_STOP werden die Ereignisse mit einer Verzögerung nachdem eine letzte Aktivität sie durchlaufen hat. Diese Verzögerung ist lang genug, um zu gewährleisten, dass ProcessLifecycleOwner sendet keine Ereignisse, 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())

Nach Angaben von Quellcode Der aktuelle Verzögerungswert beträgt 700ms .

Die Nutzung dieser Funktion erfordert außerdem die dependencies :

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"

171voto

Fred Porciúncula Punkte 8044

Bearbeiten: die neuen Architekturkomponenten brachten etwas vielversprechendes: ProcessLifecycleOwner siehe @vokilam's Antwort


Die aktuelle Lösung nach einem Google I/O-Gespräch :

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.

112voto

Swind Punkte 449

En onPause() y onResume() Methoden werden aufgerufen, wenn die Anwendung in den Hintergrund und wieder in den Vordergrund gebracht wird. Sie werden aber auch aufgerufen, wenn die Anwendung zum ersten Mal gestartet wird und bevor sie beendet wird. Mehr dazu lesen Sie in Tätigkeit .

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

Für weitere Details siehe hier Android: Lösung, um zu erkennen, wenn eine Android-App in den Hintergrund geht und wieder in den Vordergrund kommt, ohne getRunningTasks oder getRunningAppProcesses .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X