Comment détecter quand une application Android passe en arrière-plan et revient au premier plan

382

J'essaie d'écrire une application qui fait quelque chose de spécifique lorsqu'elle est remise au premier plan après un certain temps. Existe-t-il un moyen de détecter lorsqu'une application est envoyée en arrière-plan ou mise au premier plan?

iHorse
la source
2
Peut-être d'ajouter un cas d'utilisation à la question car cela ne semble pas être évident, donc il n'est pas abordé dans les réponses données. L'application peut démarrer une autre application (Galerie par exemple), qui résidera toujours dans la même pile et apparaîtra comme l'un des écrans de l'application, puis appuyez sur le bouton Accueil. Aucune des méthodes reposant sur le cycle de vie de l'application (ou même la gestion de la mémoire) n'est capable de détecter cela. Ils déclencheraient l'état d'arrière-plan dès que l'activité externe apparaît, pas lorsque vous appuyez sur Accueil.
Dennis K
C'est la réponse que vous cherchez: stackoverflow.com/a/42679191/2352699
Fred Porciúncula
1
Voir la solution Google: stackoverflow.com/questions/3667022/…
user1269737

Réponses:

98

Les méthodes onPause()et onResume()sont appelées lorsque l'application est mise en arrière-plan et à nouveau au premier plan. Cependant, ils sont également appelés lorsque l'application est démarrée pour la première fois et avant d'être supprimée. Vous pouvez en savoir plus dans Activity .

Il n'y a pas d'approche directe pour obtenir l'état de l'application en arrière-plan ou au premier plan, mais même moi, j'ai rencontré ce problème et trouvé la solution avec onWindowFocusChangedet onStop.

Pour plus de détails, consultez ici Android: Solution pour détecter lorsqu'une application Android passe en arrière-plan et revenir au premier plan sans getRunningTasks ou getRunningAppProcesses .

Girish Nair
la source
174
Cependant, cette approche provoque des faux positifs comme d'autres l'ont souligné, car ces méthodes sont également appelées lors de la transition entre les activités dans la même application.
John Lehmann
9
C'est pire que ça. Je l'ai essayé et parfois onResume est appelé alors que le téléphone est verrouillé. Si vous voyez la définition de onResume dans la documentation, vous trouverez: Gardez à l'esprit que onResume n'est pas le meilleur indicateur que votre activité est visible pour l'utilisateur; une fenêtre système telle que le garde-clavier peut être en face. Utilisez onWindowFocusChanged (booléen) pour savoir avec certitude que votre activité est visible pour l'utilisateur (par exemple, pour reprendre une partie). developer.android.com/reference/android/app/…
J-Rou
2
La solution publiée dans le lien n'utilise pas onResume / onPause, mais une combinaison de onBackPressed, onStop, onStart et onWindowsFocusChanged. Cela a fonctionné pour moi, et j'ai une hiérarchie d'interface utilisateur assez complexe (avec des tiroirs, des viseurs dynamiques, etc.)
Martin Marconcini
18
OnPause et onResume sont spécifiques à l'activité. Pas d'application. Lorsqu'une application est placée en arrière-plan, puis reprise, elle reprend l'activité spécifique dans laquelle elle se trouvait avant de passer en arrière-plan. Cela signifie que vous devrez implémenter ce que vous voulez faire lors de la reprise de l'arrière-plan dans toutes les activités de votre application. Je crois que la question d'origine cherchait quelque chose comme un "onResume" pour l'application et non l'activité.
SysHex
4
Je ne peux pas croire qu'une API appropriée ne soit pas offerte pour un besoin aussi commun. Au départ, je pensais que onUserLeaveHint () le couperait, mais vous ne pouvez pas dire si l'utilisateur quitte l'application ou non
atsakiridis
197

2018: Android prend en charge cela nativement via les composants du cycle de vie.

MISE À JOUR DE MARS 2018 : Il y a maintenant une meilleure solution. Voir ProcessLifecycleOwner . Vous devrez utiliser les nouveaux composants de l'architecture 1.1.0 (les plus récents à l'heure actuelle), mais il est spécialement conçu pour cela.

Il y a un exemple simple fourni dans cette réponse, mais j'ai écrit un exemple d'application et un blog à ce sujet.

Depuis que j'ai écrit cela en 2014, différentes solutions sont apparues. Certains fonctionnaient, certains étaient censés fonctionner , mais avaient des défauts (y compris le mien!) Et nous, en tant que communauté (Android), avons appris à vivre avec les conséquences et avons écrit des solutions de contournement pour les cas spéciaux.

Ne présumez jamais qu'un seul extrait de code est la solution que vous recherchez, il est peu probable que ce soit le cas; mieux encore, essayez de comprendre ce qu'il fait et pourquoi il le fait.

La MemoryBossclasse n'a jamais été utilisée par moi comme écrit ici, c'était juste un morceau de pseudo-code qui fonctionnait.

À moins qu'il n'y ait une raison valable pour que vous n'utilisiez pas les nouveaux composants de l'architecture (et il y en a certains, surtout si vous ciblez de super vieux API), alors allez-y et utilisez-les. Ils sont loin d'être parfaits, mais aucun ne l'était ComponentCallbacks2.

MISE À JOUR / NOTES (novembre 2015) : Les gens ont fait deux commentaires, le premier est que cela >=devrait être utilisé au lieu de ==parce que la documentation indique que vous ne devriez pas vérifier les valeurs exactes . C'est très bien dans la plupart des cas, mais gardez à l'esprit que si vous ne voulez faire quelque chose que lorsque l'application est passée en arrière-plan, vous devrez utiliser == et la combiner avec une autre solution (comme les rappels de cycle de vie d'activité), ou vous peut ne pas obtenir l'effet souhaité. L'exemple (et cela m'est arrivé) est que si vous voulez verrouillervotre application avec un écran de mot de passe en arrière-plan (comme 1Password si vous le connaissez), vous pouvez accidentellement verrouiller votre application si vous manquez de mémoire et que vous testez soudainement >= TRIM_MEMORY, car Android déclenchera un LOW MEMORYappel et c'est plus élevé que le vôtre. Soyez donc prudent comment / ce que vous testez.

De plus, certaines personnes ont demandé comment détecter quand vous revenez.

La manière la plus simple à laquelle je pense est expliquée ci-dessous, mais comme certaines personnes ne la connaissent pas, j'ajoute un pseudo-code ici. En supposant que vous avez YourApplicationet les MemoryBossclasses, dans votre class BaseActivity extends Activity(vous devrez en créer un si vous n'en avez pas).

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

Je recommande onStart car Dialogs peut suspendre une activité, donc je parie que vous ne voulez pas que votre application pense "qu'elle est passée en arrière-plan" si tout ce que vous avez fait était d'afficher une boîte de dialogue en plein écran, mais votre kilométrage peut varier.

Et c'est tout. Le code dans le bloc si vous ne sera exécutée une fois la nouvelle (qui a également, même si vous allez à une autre activité, extends BaseActivity) fera rapport wasInBackgroundest falsedonc il ne sera pas exécuter le code, jusqu'à ce que l' onMemoryTrimmedon appelle et le drapeau est défini sur true à nouveau .

J'espère que cela pourra aider.

MISE À JOUR / NOTES (avril 2015) : Avant de commencer à copier et coller sur ce code, notez que j'ai trouvé quelques exemples où il peut ne pas être fiable à 100% et doit être combiné avec d'autres méthodes pour obtenir les meilleurs résultats. Il existe notamment deux cas connus où le onTrimMemoryrappel n'est pas garanti d'être exécuté:

  1. Si votre téléphone verrouille l'écran pendant que votre application est visible (par exemple, votre appareil se verrouille après nn minutes), ce rappel n'est pas appelé (ou pas toujours) parce que l'écran de verrouillage est juste au-dessus, mais votre application est toujours en cours d'exécution, bien que couverte.

  2. Si votre appareil est relativement faible en mémoire (et soumis à un stress mémoire), le système d'exploitation semble ignorer cet appel et passer directement à des niveaux plus critiques.

Maintenant, selon l'importance pour vous de savoir quand votre application est passée en arrière-plan, vous devrez peut-être étendre cette solution avec le suivi du cycle de vie de l'activité et ainsi de suite.

Gardez simplement à l'esprit ce qui précède et ayez une bonne équipe d'AQ;)

FIN DE MISE À JOUR

Il peut être tard, mais il existe une méthode fiable dans Ice Cream Sandwich (API 14) et au-dessus .

Il s'avère que lorsque votre application n'a plus d'interface utilisateur visible, un rappel est déclenché. Le rappel, que vous pouvez implémenter dans une classe personnalisée, s'appelle ComponentCallbacks2 (oui, avec deux). Cette fonction de rappel n'est disponible qu'en API niveau 14 (Ice Cream Sandwich) et au-dessus.

Vous obtenez essentiellement un appel à la méthode:

public abstract void onTrimMemory (int level)

Le niveau est de 20 ou plus spécifiquement

public static final int TRIM_MEMORY_UI_HIDDEN

J'ai testé cela et cela fonctionne toujours, car le niveau 20 n'est qu'une "suggestion" que vous souhaitiez peut-être libérer certaines ressources car votre application n'est plus visible.

Pour citer les documents officiels:

Niveau pour onTrimMemory (int): le processus montrait une interface utilisateur et ne le fait plus. De grandes allocations avec l'interface utilisateur doivent être libérées à ce stade pour permettre une meilleure gestion de la mémoire.

Bien sûr, vous devez l'implémenter pour faire ce qu'il dit (purger la mémoire qui n'a pas été utilisée depuis un certain temps, effacer certaines collections qui étaient inutilisées, etc. Les possibilités sont infinies (voir les documents officiels pour d'autres plus possibles). niveaux critiques ).

Mais, ce qui est intéressant, c'est que le système d'exploitation vous dit: HEY, votre application est passée en arrière-plan!

C'est exactement ce que vous vouliez savoir en premier lieu.

Comment déterminez-vous votre retour?

Eh bien c'est facile, je suis sûr que vous avez une "BaseActivity" afin que vous puissiez utiliser votre onResume () pour signaler le fait que vous êtes de retour. Parce que la seule fois où vous direz que vous n'êtes pas de retour, c'est lorsque vous recevez réellement un appel à la onTrimMemoryméthode ci-dessus .

Ça marche. Vous n'obtenez pas de faux positifs. Si une activité reprend, vous êtes de retour, 100% du temps. Si l'utilisateur retourne à l'arrière, vous recevez un autre onTrimMemory()appel.

Vous devez vous inscrire à vos activités (ou mieux encore, un cours personnalisé).

Le moyen le plus simple de garantir que vous recevez toujours ceci est de créer une classe simple comme celle-ci:

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

Pour l'utiliser, dans votre implémentation d'application ( vous en avez un, DROIT? ), Faites quelque chose comme:

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

Si vous créez un, Interfacevous pouvez ajouter un elseà cela ifet implémenter ComponentCallbacks(sans le 2) utilisé dans quoi que ce soit en dessous de l'API 14. Ce rappel n'a que la onLowMemory()méthode et n'est pas appelé lorsque vous passez en arrière-plan , mais vous devez l'utiliser pour réduire la mémoire .

Maintenant, lancez votre application et appuyez sur home. Votre onTrimMemory(final int level)méthode doit être appelée (indice: ajouter la journalisation).

La dernière étape consiste à se désinscrire du rappel. Le meilleur endroit est probablement la onTerminate()méthode de votre application, mais cette méthode n'est pas appelée sur un appareil réel:

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

Donc, sauf si vous avez vraiment une situation où vous ne voulez plus être enregistré, vous pouvez l'ignorer en toute sécurité, car votre processus est en train de mourir au niveau du système d'exploitation de toute façon.

Si vous décidez de vous désinscrire à un moment donné (si vous fournissez, par exemple, un mécanisme d'arrêt pour que votre application se nettoie et meure), vous pouvez faire:

unregisterComponentCallbacks(mMemoryBoss);

Et c'est tout.

Martin Marconcini
la source
Lors de la vérification d'un service, semble ne se déclencher que lorsque le bouton d'accueil est enfoncé. Appuyer sur le bouton de retour ne déclenche pas cela sur KitKat.
Apprenez OpenGL ES le
Le service n'a pas d'interface utilisateur, il pourrait donc être lié à cela. Faites le contrôle de votre activité de base, pas sur un service. Vous voulez savoir quand votre interface utilisateur est cachée (et peut-être dire au service, donc ça passe au premier plan)
Martin Marconcini
1
Cela ne fonctionne pas lorsque vous éteignez votre téléphone. Il n'est pas déclenché.
Juangcg
2
L'utilisation de ComponentCallbacks2.onTrimMemory () (en combinaison avec ActivityLifecycleCallbacks) est la seule solution fiable que j'ai trouvée jusqu'à présent, merci Martin! Pour les personnes intéressées, voir ma réponse fournie.
rickul
3
J'utilise cette méthode depuis un an et elle a toujours été fiable pour moi. Il est bon de savoir que d'autres personnes l'utilisent également. J'utilise juste level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENce qui évite le problème dans votre mise à jour, point 2. En ce qui concerne le point 1, ce n'est pas un problème pour moi, car l'application n'est pas vraiment passée en arrière-plan, c'est donc la façon dont elle est censée fonctionner.
sorianiv
175

Voici comment j'ai réussi à résoudre ce problème. Il part du principe que l'utilisation d'une référence temporelle entre les transitions d'activité fournira très probablement des preuves suffisantes qu'une application a été "mise en arrière-plan" ou non.

Tout d'abord, j'ai utilisé une instance android.app.Application (appelons-la MyApplication) qui a un Timer, une TimerTask, une constante pour représenter le nombre maximal de millisecondes que la transition d'une activité à une autre pourrait raisonnablement prendre (je suis allé avec une valeur de 2s), et un booléen pour indiquer si l'application était "en arrière-plan" ou non:

public class MyApplication extends Application {

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

L'application propose également deux méthodes pour démarrer et arrêter le minuteur / la tâche:

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

Le dernier élément de cette solution consiste à ajouter un appel à chacune de ces méthodes à partir des événements onResume () et onPause () de toutes les activités ou, de préférence, dans une activité de base dont toutes vos activités concrètes héritent:

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

Ainsi, dans le cas où l'utilisateur navigue simplement entre les activités de votre application, la onPause () de l'activité en cours démarre le chronomètre, mais presque immédiatement, la nouvelle activité entrée annule le chronomètre avant qu'il n'atteigne le temps de transition maximum. Et donc, InBackground serait faux .

D'un autre côté, lorsqu'une activité arrive au premier plan à partir du lanceur, le réveil de l'appareil, la fin d'un appel téléphonique, etc., plus que probablement la tâche du minuteur exécutée avant cet événement, et donc InBackground a été défini sur true .

d60402
la source
4
Salut d60402, votre réponse est vraiment utile .. merci beaucoup pour cette réponse ... petit avis .. MyApplication devrait mentionner dans la balise d'application du fichier Manifest comme android: name = "MyApplication", sinon l'application plante ... juste pour aider quelqu'un comme moi
praveenb
2
marque du grand programmeur, solution simple à l'un des problèmes les plus compliqués que j'ai jamais rencontrés.
Aashish Bhatnagar
2
Super solution! Merci. Si quelqu'un obtient l'erreur "ClassCastException", vous avez peut-être manqué de l'ajouter dans la balise d'application dans votre Manifest.xml <application android: name = "your.package.MyApplication"
Wahib Ul Haq
27
Il s'agit d'une implémentation agréable et simple. Cependant, je pense que cela devrait être implémenté dans onStart / onStop plutôt que dans onPause / onResume. OnPause sera appelée même si j'ouvre une boîte de dialogue qui couvre partiellement l'activité. Et la fermeture de la boîte de dialogue invoquerait en faitRésumer, ce qui donnerait l'impression que l'application vient de passer au premier plan
Shubhayu
7
J'espère utiliser une variante de cette solution. Le point sur les dialogues identifiés ci-dessus est un problème pour moi, j'ai donc essayé la suggestion de @ Shubhayu (onStart / onStop). Cependant, cela n'aide pas car lorsque vous allez A-> B, onStart () de l'activité B est appelé avant onStop () de l'activité A.
Trevor
150

Edit: les nouveaux composants de l'architecture ont apporté quelque chose de prometteur: ProcessLifecycleOwner , voir la réponse de @ vokilam


La solution réelle selon une conférence 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
    }
  }

}

Oui. Je sais qu'il est difficile de croire que cette solution simple fonctionne, car nous avons tant de solutions étranges ici.

Mais il y a de l'espoir.

Fred Porciúncula
la source
3
Cela fonctionne parfaitement! J'ai déjà essayé tant de solutions étranges qui avaient tant de défauts ... merci beaucoup! Je le cherche depuis un moment.
Eggakin Baconwalker
7
Cela fonctionne pour plusieurs activités, mais pour un - onrotate indiquera que toutes les activités ont disparu ou en arrière
poisson mort
2
@Shyri vous avez raison, mais cela fait partie de cette solution, vous devez donc vous inquiéter. Si Firebase s'appuie sur cela, je pense que mon application médiocre peut aussi :) Excellente réponse BTW.
ElliotM
3
@deadfish Vérifiez le lien vers les E / S fourni en haut de la réponse. Vous pouvez vérifier les intervalles de temps entre l'arrêt et le début de l'activité pour déterminer si vous êtes vraiment passé à l'arrière-plan ou non. C'est une solution brillante, en fait.
Alex Berdnikov
2
Existe-t-il une solution Java? C'est du kotlin.
Giacomo Bartoli du
116

ProcessLifecycleOwner semble également être une solution prometteuse.

ProcessLifecycleOwner dépêchera ON_START, ON_RESUMEévénements, un premier se déplace d'activité par ces événements. ON_PAUSE, ON_STOP, Les événements seront livrés avec un retard après une dernière activité passée à travers eux. Ce délai est suffisamment long pour garantir queProcessLifecycleOwner qu'aucun événement ne sera envoyé si les activités sont détruites et recréées en raison d'un changement de configuration.

Une implémentation peut être aussi simple que

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

Selon le code source, la valeur actuelle du retard est 700ms.

L'utilisation de cette fonctionnalité nécessite également dependencies:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
vokilam
la source
10
Notez que vous devez ajouter les dépendances du cycle de vie implementation "android.arch.lifecycle:extensions:1.0.0"et annotationProcessor "android.arch.lifecycle:compiler:1.0.0"du référentiel de Google (c'est-à-dire google())
Sir Codesalot
1
Cela a très bien fonctionné pour moi, merci. J'ai dû utiliser l'api 'android.arch.lifecycle: extensions: 1.1.0' au lieu de l'implémentation en raison d'une erreur indiquant que la dépendance Android a une version différente pour le chemin de classe de compilation et d'exécution.
FSUWX2011
C'est une excellente solution car elle fonctionne en modules sans avoir besoin d'une référence d'activité!
Max
Cela ne fonctionne pas lorsque l'application se bloque. Existe-t-il une solution pour que l'événement tombe en panne avec l'application via cette solution
tejraj
Excellente solution. J'ai sauvé ma journée.
Sunny
69

Sur la base de la réponse de Martín Marconcinis (merci!) J'ai finalement trouvé une solution fiable (et très simple).

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

Ajoutez ensuite ceci à votre onCreate () de votre classe Application

public class MyApp extends android.app.Application {

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

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

    }

}
Rickul
la source
Pouvez-vous montrer comment vous l'utilisez dans une application, dois-je l'appeler à partir de la classe App ou ailleurs?
JPM
c'est parfait merci !! fonctionne très bien dans les tests jusqu'à présent
aherrick
Cet exemple est incomplet. Qu'est-ce que registerActivityLifecycleCallbacks?
Noman
c'est une méthode dans la classe android.app.Application
rickul
1
bravo +1 pour aller en haut, car c'est parfait, ne cherchez pas d'autres réponses, cela est basé sur la réponse @reno mais avec un exemple réel
Stoycho Andreev
63

Nous utilisons cette méthode. Cela semble trop simple pour fonctionner, mais il a été bien testé dans notre application et fonctionne en fait étonnamment bien dans tous les cas, y compris pour accéder à l'écran d'accueil par le bouton "Accueil", par le bouton "Retour" ou après le verrouillage de l'écran. Essaie.

L'idée est que, au premier plan, Android commence toujours une nouvelle activité juste avant d'arrêter la précédente. Ce n'est pas garanti, mais c'est comme ça que ça fonctionne. BTW, Flurry semble utiliser la même logique (juste une supposition, je n'ai pas vérifié cela, mais il accroche aux mêmes événements).

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

}

Edit: selon les commentaires, nous sommes également passés à onStart () dans les versions ultérieures du code. En outre, j'ajoute des super appels, qui manquaient dans mon message initial, car il s'agissait plus d'un concept que d'un code de travail.

Nick Frolov
la source
2
C'est la réponse la plus fiable, bien que j'utilise onStart au lieu de onResume.
Greg Ennis
Vous devez ajouter des appels à super.onResume () et super.onStop () dans les méthodes substituées. Sinon, une exception android.app.SuperNotCalledException est levée.
Jan Laussmann
1
pour moi, cela ne fonctionne pas ... ou du moins cela déclenche l'événement lorsque vous faites tourner l'appareil aussi (ce qui est une sorte de faux imho positif).
Noya
Solution très simple et efficace! Mais je ne suis pas sûr que cela fonctionne avec des activités partiellement transparentes qui permettent de voir certaines parties de l'activité précédente. De la documentation, onStop is called when the activity is no longer visible to the user.
Nicolas Buquet
3
que se passe-t-il si l'utilisateur change d'orientation sur la première activité? Il signalera que l'application est passée en arrière-plan, ce qui n'est pas vrai. Comment gérez-vous ce scénario?
Nimrod Dayan
54

Si votre application se compose de plusieurs activités et / ou d'activités empilées comme un widget de barre d'onglets, la substitution de onPause () et onResume () ne fonctionnera pas. C'est-à-dire qu'au démarrage d'une nouvelle activité, les activités en cours seront interrompues avant la création de la nouvelle. Il en va de même lorsque vous terminez (à l'aide du bouton "retour") une activité.

J'ai trouvé deux méthodes qui semblent fonctionner comme souhaité.

La première nécessite l'autorisation GET_TASKS et consiste en une méthode simple qui vérifie si l'activité en cours d'exécution sur le périphérique appartient à l'application, en comparant les noms de packages:

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

Cette méthode a été trouvée dans le framework Droid-Fu (maintenant appelé Ignition).

La deuxième méthode que j'ai implémentée moi-même ne nécessite pas l'autorisation GET_TASKS, ce qui est bien. Au lieu de cela, il est un peu plus compliqué à mettre en œuvre.

Dans votre classe MainApplication, vous avez une variable qui suit le nombre d'activités en cours d'exécution dans votre application. Dans onResume () pour chaque activité, vous augmentez la variable et dans onPause () vous la diminuez.

Lorsque le nombre d'activités en cours d'exécution atteint 0, l'application est mise en arrière-plan SI les conditions suivantes sont remplies:

  • L'activité en pause n'est pas terminée (le bouton "retour" a été utilisé). Cela peut être fait en utilisant la méthode activity.isFinishing ()
  • Une nouvelle activité (même nom de package) n'est pas en cours de démarrage. Vous pouvez remplacer la méthode startActivity () pour définir une variable qui l'indique, puis la réinitialiser dans onPostResume (), qui est la dernière méthode à exécuter lorsqu'une activité est créée / reprise.

Lorsque vous pouvez détecter que l'application a démissionné en arrière-plan, il est également facile de détecter lorsqu'elle est ramenée au premier plan.

Emil
la source
18
Google rejettera probablement une application qui utilise ActivityManager.getRunningTasks (). La documentation indique que c'est uniquement à des fins de développement. developer.android.com/reference/android/app/…
Sky Kelsey
1
J'ai trouvé que je devais utiliser une combinaison de ces approches. onUserLeaveHint () a été appelé lors du lancement d'une activité en 14. `@Override public void onUserLeaveHint () {inBackground = isApplicationBroughtToBackground (); } `
annonce bateau
7
Les utilisateurs ne seront pas trop contents d'utiliser une puissante autorisation android.permission.GET_TASKS.
MSquare
6
getRunningTasks est déconseillé dans le niveau 21 de l'API.
Noya
33

Créez une classe qui s'étend Application. Ensuite, nous pouvons utiliser sa méthode de remplacement,onTrimMemory() .

Pour détecter si l'application est passée en arrière-plan, nous utiliserons:

 @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
la source
1
Car FragmentActivityvous pourriez aussi vouloir ajouter level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEaussi.
Srujan Simha
2
Merci beaucoup d'avoir pointé cette méthode, j'ai besoin d'afficher une boîte de dialogue Pin chaque fois que l'utilisateur reprend l'activité pour le fond, utilise cette méthode pour écrire une valeur pref et vérifie cette valeur sur baseActivity.
Sam
18

Pensez à utiliser onUserLeaveHint. Cela ne sera appelé que lorsque votre application sera en arrière-plan. onPause aura des cas d'angle à gérer, car il peut être appelé pour d'autres raisons; par exemple, si l'utilisateur ouvre une autre activité dans votre application telle que votre page de paramètres, la méthode onPause de votre activité principale sera appelée même si elle est toujours dans votre application; le suivi de ce qui se passe entraînera des bogues lorsque vous pourrez simplement utiliser le rappel onUserLeaveHint qui fait ce que vous demandez.

Quand on UserLeaveHint est appelé, vous pouvez définir un indicateur booléen inBackground sur true. Lorsque onResume est appelé, supposez seulement que vous êtes revenu au premier plan si l'indicateur inBackground est défini. En effet, onResume sera également appelé sur votre activité principale si l'utilisateur n'était que dans votre menu de paramètres et n'a jamais quitté l'application.

N'oubliez pas que si l'utilisateur clique sur le bouton d'accueil dans votre écran de paramètres, onUserLeaveHint sera appelé dans votre activité de paramètres et lorsqu'il reviendra, Resume sera appelé dans votre activité de paramètres. Si vous n'avez que ce code de détection dans votre activité principale, vous raterez ce cas d'utilisation. Pour avoir ce code dans toutes vos activités sans dupliquer le code, ayez une classe d'activité abstraite qui étend l'activité et mettez votre code commun dedans. Ensuite, chaque activité que vous avez peut prolonger cette activité abstraite.

Par exemple:

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
la source
19
onUserLeaveHint est également appelé lors de la navigation vers une autre activité
Jonas Stawski
3
onUserLeaveHint n'est pas appelé lorsque, par exemple, un appel téléphonique arrive et que l'activité d'appel devient active, il y a donc également un cas périphérique - il peut y avoir d'autres cas également, puisque vous pouvez ajouter un indicateur à l'intention de supprimer l'appel onUserLeaveHint. developer.android.com/reference/android/content/…
Groxx
1
De plus, onResume ne fonctionne pas bien. Je l'ai essayé et parfois onResume est appelé alors que le téléphone est verrouillé. Si vous voyez la définition de onResume dans la documentation, vous trouverez: Gardez à l'esprit que onResume n'est pas le meilleur indicateur que votre activité est visible pour l'utilisateur; une fenêtre système telle que le protège-clavier peut être en face. Utilisez onWindowFocusChanged (booléen) pour savoir avec certitude que votre activité est visible pour l'utilisateur (par exemple, pour reprendre une partie). developer.android.com/reference/android/app/…
J-Rou
cette solution n'aide pas à décider du premier plan / arrière-plan s'il y a plusieurs activités.Plz referent stackoverflow.com/questions/3667022/…
Raj Trivedi
14

ActivityLifecycleCallbacks peut être intéressant, mais il n'est pas bien documenté.

Cependant, si vous appelez registerActivityLifecycleCallbacks (), vous devriez pouvoir obtenir des rappels lorsque des activités sont créées, détruites, etc. Vous pouvez appeler getComponentName () pour l'activité.

Reno
la source
11
Depuis le niveau d'api 14 = \
imort
On dirait que celui-ci est propre et fonctionne pour moi. Merci
duanbo1983
En quoi est-ce différent de la réponse acceptée, les deux s'appuient sur le même cycle de vie d'activité, n'est-ce pas?
Saitama
13

Le package android.arch.lifecycle fournit des classes et des interfaces qui vous permettent de créer des composants adaptés au cycle de vie

Votre application doit implémenter l'interface 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");
    }
}

Pour ce faire, vous devez ajouter cette dépendance à votre fichier build.gradle:

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

Comme recommandé par Google, vous devez minimiser le code exécuté dans les méthodes d'activités du cycle de vie:

Un modèle courant consiste à implémenter les actions des composants dépendants dans les méthodes de cycle de vie des activités et des fragments. Cependant, ce modèle conduit à une mauvaise organisation du code et à la prolifération des erreurs. En utilisant des composants compatibles avec le cycle de vie, vous pouvez déplacer le code des composants dépendants hors des méthodes de cycle de vie et dans les composants eux-mêmes.

Vous pouvez en savoir plus ici: https://developer.android.com/topic/libraries/architecture/lifecycle

matdev
la source
et ajoutez ceci pour se manifester comme: <application android: name = ". AnotherApp">
Dan Alboteanu
9

Dans votre application, ajoutez le rappel et vérifiez l'activité root de la manière suivante:

@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
la source
J'envisagerais d'utiliser ce mode de mise en œuvre. Une transition d'une activité à une autre ne prend que quelques millisecondes. En fonction du moment où la dernière activité disparaît, on peut envisager de se reconnecter à l'utilisateur par une stratégie spécifique.
drindt
6

J'ai créé un projet sur Github app-foreground-background-listen

Créez une activité de base pour toutes les activités dans votre application.

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

Utilisez maintenant cette BaseActivity comme une super classe de toutes vos activités comme MainActivity étend BaseActivity et onAppStart sera appelé lorsque vous démarrez votre application et onAppPause () sera appelé lorsque l'application passera en arrière-plan à partir de n'importe quel écran.

kiran boghra
la source
@kiran boghra: Y a-t-il des faux positifs dans votre solution?
Harish Vishwakarma
Réponse parfaite, la fonction onStart () et onStop () peut être utilisée dans ce cas. qui vous parle de votre application
Pir Fahim Shah
6

C'est assez facile avec ProcessLifecycleOwner

Ajoutez ces dépendances

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

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

Puis dans votre activité de base:

override fun onCreate() {
        super.onCreate()

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

Voir mon article sur ce sujet: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48

Ege Kuzubasioglu
la source
5

Vous pouvez utiliser ProcessLifecycleOwner en y attachant un observateur de cycle de vie.

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

puis, dans la onCreate()classe Application, vous appelez ceci:

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

avec cela, vous pourrez capturer les événements ON_PAUSEet ON_STOPde votre application qui se produisent quand il passe en arrière-plan.

Alécio Carvalho
la source
4

Il n'y a pas de méthodes de cycle de vie simples pour vous dire quand toute l'application passe en arrière-plan / au premier plan.

Je l'ai fait de manière simple. Suivez les instructions ci-dessous pour détecter la phase d'arrière-plan / de premier plan de l'application.

Avec une petite solution de contournement, c'est possible. Ici, ActivityLifecycleCallbacks vient à la rescousse. Permettez-moi de vous guider pas à pas.

  1. Tout d'abord, créez une classe qui étend android.app.Application et implémente l' interface ActivityLifecycleCallbacks . Dans Application.onCreate (), enregistrez le rappel.

    public class App extends Application implements 
        Application.ActivityLifecycleCallbacks {
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    }
  2. Enregistrez la classe «App» dans le manifeste comme ci-dessous <application android:name=".App".

  3. Il y aura au moins une activité à l'état démarré lorsque l'application est au premier plan et il n'y aura pas d'activité à l'état démarré lorsque l'application est en arrière-plan.

    Déclarez 2 variables comme ci-dessous dans la classe «App».

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

    activityReferencesconservera le nombre d'activités dans l' état démarré . isActivityChangingConfigurationsest un indicateur pour indiquer si l'activité en cours subit un changement de configuration comme un commutateur d'orientation.

  4. En utilisant le code suivant, vous pouvez détecter si l'application passe au premier plan.

    @Override
    public void onActivityStarted(Activity activity) {
        if (++activityReferences == 1 && !isActivityChangingConfigurations) {
            // App enters foreground
        }
    }
  5. Voici comment détecter si l'application passe en arrière-plan.

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

Comment ça fonctionne:

C'est un petit truc fait avec la façon dont les méthodes de cycle de vie sont appelées en séquence. Permettez-moi de vous présenter un scénario.

Supposons que l'utilisateur lance l'application et que l'activité de lancement A soit lancée. Les appels du cycle de vie seront,

A.onCreate ()

A.onStart () (++ activityReferences == 1) (l'application entre au premier plan)

A.onResume ()

Maintenant, l'activité A commence l'activité B.

A.onPause ()

B.onCreate ()

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

B.onResume ()

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

Ensuite, l'utilisateur revient de l'activité B,

B.onPause ()

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

A.onResume ()

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

B.onDestroy ()

Ensuite, l'utilisateur appuie sur le bouton Accueil,

A.onPause ()

A.onStop () (--activityReferences == 0) (l'application entre en arrière-plan)

Dans le cas, si l'utilisateur appuie sur le bouton Accueil de l'activité B au lieu du bouton Précédent, ce sera toujours le même et les références d'activité seront 0 . Par conséquent, nous pouvons détecter que l'application entre en arrière-plan.

Alors, quel est le rôle de isActivityChangingConfigurations? Dans le scénario ci-dessus, supposons que l'activité B modifie l'orientation. La séquence de rappel sera,

B.onPause ()

B.onStop () (--activityReferences == 0) (l'application entre en arrière-plan ??)

B.onDestroy ()

B.onCreate ()

B.onStart () (++ activityReferences == 1) (L'application entre au premier plan ??)

B.onResume ()

C'est pourquoi nous avons une vérification supplémentaire isActivityChangingConfigurationspour éviter le scénario lorsque l'activité passe par les changements de configuration.

Komal Nikhare
la source
3

J'ai trouvé une bonne méthode pour détecter l'application, que ce soit en avant-plan ou en arrière-plan. Voici mon code . J'espère que cela vous aidera.

/**
 * 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
la source
3

Vous pouvez utiliser:

vide protégé onRestart ()

Pour différer entre les nouveaux démarrages et redémarrages.

entrez la description de l'image ici

AYBABTU
la source
3

Edit 2: Ce que j'ai écrit ci-dessous ne fonctionnera pas réellement. Google a rejeté une application qui inclut un appel à ActivityManager.getRunningTasks (). De la documentation ressort de que cette API est uniquement destinée au débogage et au développement. Je mettrai à jour cet article dès que j'aurai le temps de mettre à jour le projet GitHub ci-dessous avec un nouveau schéma qui utilise des minuteries et est presque aussi bon.

Edit 1: J'ai rédigé un article de blog et créé un simple référentiel GitHub pour rendre cela vraiment facile.

La réponse acceptée et la mieux notée ne sont pas vraiment la meilleure approche. L'implémentation de la réponse la mieux notée de isApplicationBroughtToBackground () ne gère pas la situation où l'activité principale de l'application cède le pas à une activité définie dans la même application, mais elle a un package Java différent. J'ai trouvé un moyen de le faire qui fonctionnera dans ce cas.

Appelez cela dans onPause (), et il vous dira si votre application va en arrière-plan parce qu'une autre application a démarré ou si l'utilisateur a appuyé sur le bouton d'accueil.

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
la source
Pour info, appeler cela dans onStart () à la place évitera qu'il soit appelé quand une simple boîte de dialogue est lancée, par exemple, lorsqu'une alarme se déclenche.
Sky Kelsey
2

Bonne réponse ici

Créez une classe avec le nom MyApp comme ci-dessous:

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

    }
}

Ensuite, partout où vous voulez (meilleure première activité lancée dans l'application), ajoutez le code ci-dessous:

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

Terminé! Maintenant, lorsque l'application est en arrière-plan, nous obtenons le journal status : we are out et lorsque nous allons dans l'application, nous obtenons le journalstatus : we are out

erfan
la source
1

Ma solution a été inspirée par la réponse de @ d60402 et repose également sur une fenêtre de temps, mais sans utiliser le 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();
  }
}

SingletonApplicationest une extension de Applicationclasse:

public class SingletonApplication extends Application {
  public boolean isInBackground = false;
  public long lastForegroundTransition = 0;
}
injecteur
la source
1

J'utilisais cela avec Google Analytics EasyTracker, et cela a fonctionné. Il pourrait être étendu pour faire ce que vous cherchez en utilisant un simple entier.

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
la source
1

Je sais que c'est un peu tard mais je pense que toutes ces réponses ont des problèmes pendant que je le fais comme ci-dessous et cela fonctionne parfaitement.

créer un rappel de cycle de vie d'activité comme celui-ci:

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

    }
}

et enregistrez-le simplement sur votre classe d'application comme ci-dessous:

public class MyApp extends Application {

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
Amir Ziarati
la source
Cela est appelé tout le temps sur chaque activité. Comment puis-je l'utiliser si, par exemple, je veux détecter le statut en ligne de l'utilisateur
Maksim Kniazev
c'est ce que la question veut. il n'est appelé que lorsque vous accédez à l'écran d'accueil et revenez à une activité.
Amir Ziarati
si vous voulez dire la connectivité Internet, je pense que c'est mieux de vérifier cela quand vous en avez besoin. si vous devez appeler un api, vérifiez la connexion Internet juste avant d'appeler.
Amir Ziarati
1

Cela semble être l'une des questions les plus compliquées d'Android car (au moment de la rédaction de ce document), Android n'a pas d'équivalent iOS applicationDidEnterBackground()ni de applicationWillEnterForeground()rappel. J'ai utilisé une bibliothèque AppState créée par @jenzz .

[AppState est] une bibliothèque Android simple et réactive basée sur RxJava qui surveille les changements d'état des applications. Il avertit les abonnés chaque fois que l'application passe en arrière-plan et revient au premier plan.

Il s'est avéré que c'était exactement ce dont j'avais besoin, surtout parce que mon application avait plusieurs activités, donc simplement vérifier onStart()ou onStop()sur une activité n'allait pas le couper.

J'ai d'abord ajouté ces dépendances à gradle:

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

Ensuite, il s'agissait simplement d'ajouter ces lignes à un endroit approprié dans votre code:

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

Selon la façon dont vous vous abonnez à l'observable, vous devrez peut-être vous désabonner pour éviter les fuites de mémoire. Encore plus d'informations sur la page github .

deniz
la source
1

Il s'agit de la version modifiée de la réponse de @ d60402: https://stackoverflow.com/a/15573121/4747587

Faites tout ce qui y est mentionné. Mais au lieu d'avoir un Base Activityet d'en faire un parent pour chaque activité et pour remplacer le onResume()et onPause, procédez comme suit:

Dans votre classe d'application, ajoutez la ligne:

registerActivityLifecycleCallbacks (rappel Application.ActivityLifecycleCallbacks);

Cela callbacka toutes les méthodes de cycle de vie d'activité et vous pouvez maintenant remplacer onActivityResumed()et onActivityPaused().

Jetez un œil à ce Gist: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b

Henri
la source
1

Vous pouvez y parvenir facilement à l'aide de ActivityLifecycleCallbackset ComponentCallbacks2quelque chose comme ci-dessous.

Créez une classe AppLifeCycleHandlerimplémentant au-dessus desdites interfaces.

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

Dans votre classe, qui étend l' Applicationimplémentation AppLifeCycleCallbackpour obtenir les rappels lorsque l'application bascule entre le premier plan et l'arrière-plan. Quelque chose comme ci-dessous.

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

J'espère que cela t'aides.

EDIT Comme alternative, vous pouvez maintenant utiliser le composant d'architecture sensible au cycle de vie.

Naveen TP
la source
1

Comme je n'ai trouvé aucune approche, qui gère également la rotation sans vérifier les horodatages, j'ai pensé partager également comment nous le faisons maintenant dans notre application. Le seul ajout à cette réponse https://stackoverflow.com/a/42679191/5119746 est que nous prenons également en compte l'orientation.

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

Ensuite, pour les rappels, nous avons d'abord le CV:

   // ActivityLifecycleCallbacks

   override fun onActivityResumed(activity: Activity?) {

      mResumed++

      if (mAppIsInBackground) {

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

         mAppIsInBackground = false
      }
      mOrientationWasChanged = false
    }

Et onActivityStopped:

   override fun onActivityStopped(activity: Activity?) {

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

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

        mAppIsInBackground = true
    }

Et puis, voici l'ajout: Vérification des changements d'orientation:

   override fun onConfigurationChanged(newConfig: Configuration) {

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

C'est ça. J'espère que cela aide quelqu'un :)

Julian Horst
la source
1

Nous pouvons étendre cette solution en utilisant 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
    }
}

Maintenant, nous pouvons nous abonner à ce LiveData et attraper les événements nécessaires. Par exemple:

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

Ces réponses ne semblent pas être correctes. Ces méthodes sont également appelées lorsqu'une autre activité commence et se termine. Ce que vous pouvez faire est de garder un indicateur global (oui, les globaux sont mauvais :) et définissez-le sur true chaque fois que vous démarrez une nouvelle activité. Réglez-le sur false dans le onCreate de chaque activité. Ensuite, dans la onPause, vous cochez ce drapeau. Si c'est faux, votre application se met en arrière-plan ou elle se fait tuer.

Joris Weimar
la source
Je n'ai pas parlé d'une base de données ... que voulez-vous dire?
Joris Weimar
j'appuie votre réponse. même si nous pouvons enregistrer cette valeur d'indicateur dans la base de données lors d'un appel en pause, ce n'est pas la bonne solution.
Sandeep P