web-dev-qa-db-fra.com

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

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

357
iHorse

Les méthodes onPause() et onResume() sont appelées lorsque l'application est ramenée à l'arrière-plan et à l'avant-plan. Cependant, ils sont également appelés lorsque l'application est lancée pour la première fois et avant qu'elle ne soit tuée. Vous pouvez en lire plus dans Activité.

Il n’existe pas d’approche directe pour obtenir le statut de l’application en arrière-plan ou au premier plan, mais même j’ai fait face à ce problème et trouvé la solution avec onWindowFocusChanged et onStop.

Pour plus de détails, cliquez ici Android: solution pour détecter le moment où une application Android passe en arrière-plan et revient au premier plan sans getRunningTasks ou getRunningAppProcesses.

88
Swind

2018: Android prend en charge cette fonctionnalité de manière native via des composants de cycle de vie.

MISE À JOUR DE Mars 2018 : Il existe maintenant une meilleure solution. Voir ProcessLifecycleOwner . Vous devrez utiliser les nouveaux composants d’architecture 1.1.0 (les plus récents à ce jour), mais c’est spécifiquement conçu à cet effet.

Un exemple simple est fourni dans cette réponse mais j’ai écrit un exemple d’application et un message de blog à ce sujet.

Depuis que j'ai écrit ceci en 2014, différentes solutions sont apparues. Certains travaillaient, certains pensaient fonctionner , mais ils 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 utilisateurs. cas spéciaux.

Ne supposez jamais qu’un extrait de code est la solution que vous recherchez, c’est peu probable. mieux encore, essayez de comprendre ce qu’il fait et pourquoi il le fait.

La classe MemoryBoss n’a jamais été utilisée par moi comme il est écrit ici, c’était juste un morceau de pseudo-code qui fonctionnait.

À moins qu’il n’y ait une raison valable de ne pas utiliser les nouveaux composants d’architecture (et qu’il en existe, en particulier si vous ciblez de très vieux apis), utilisez-les. Ils sont loin d'être parfaits, mais ComponentCallbacks2 non plus.

UPDATE/NOTES (November 2015) : Deux personnes ont fait des commentaires. Tout d'abord, >= doit être utilisé à la place de == car la documentation indique que vous ne devrait pas vérifier les valeurs exactes . C'est bien dans la plupart des cas, mais gardez à l'esprit que si vous seulement vous souciez de faire quelque chose lorsque l'application est passée à l'arrière-plan , vous devrez aussi utiliser == et avec une autre solution (comme les rappels Activity Lifecycle), ou vous ne pourrez pas obtenir votre effet désiré. L'exemple (et cela m'est arrivé) est que si vous voulez verrouiller votre application avec un écran de mot de passe lorsqu'il passe en arrière-plan (comme 1Password si vous le connaissez bien) ), vous risquez de verrouiller accidentellement votre application si vous manquez de mémoire et que vous testez subitement >= TRIM_MEMORY, car Android déclenchera un appel LOW MEMORY qui est supérieur au vôtre. Alors faites attention à ce que vous testez.

De plus, certaines personnes ont demandé comment détecter votre retour.

Le moyen le plus simple auquel je puisse penser est expliqué ci-dessous, mais comme certaines personnes ne le connaissent pas, j’ajoute un pseudo-code ici. En supposant que vous ayez YourApplication et les classes MemoryBoss 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 les dialogues peuvent mettre une activité en pause. Je parie donc que vous ne voulez pas que votre application pense "qu'elle est passée à l'arrière-plan" si tout ce que vous faisiez était d'afficher une boîte de dialogue en plein écran, mais votre kilométrage peut varier.

Et c'est tout. Le code du bloc if ne sera exécuté qu'une seule fois , même si vous passez à une autre activité, la nouvelle (qui contient également extends BaseActivity) signalera wasInBackground est false pour qu'il n'exécute pas le code jusqu'à ce que onMemoryTrimmed soit appelé et que le drapeau soit à nouveau défini sur true .

J'espère que ça t'as aidé.

UPDATE/NOTES (Avril 2015) : Avant de vous lancer dans la copie et le collage de ce code, notez que j'ai trouvé quelques cas où cela ne s'est peut-être pas passé. être fiable à 100% et doivent être combinés avec d'autres méthodes pour obtenir les meilleurs résultats. Il existe notamment deux instances connues dans lesquelles l'exécution du rappel onTrimMemory n'est pas garantie:

  1. Si votre téléphone verrouille l'écran alors que votre application est visible (par exemple, votre appareil se verrouille au bout de nn minutes), ce rappel n'est pas appelé (ou pas toujours) car l'écran verrouillé est juste au-dessus, mais votre application est toujours "en cours d'exécution" bien qu'elle soit couverte.

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

Désormais, en fonction de l'importance pour vous de savoir quand votre application est passée à l'arrière-plan, vous pouvez ou non avoir besoin d'étendre cette solution tout en gardant une trace du cycle de vie de l'activité et de tout le reste.

Il suffit de garder ce qui précède à l’esprit et d’avoir une bonne équipe d’assurance qualité;)

FIN DE MISE À JOUR

C'est peut-être tard, mais il existe une méthode fiable entre sandwich à la crème glacée (API 14) et ci-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, est appelé ComponentCallbacks2 (oui, avec deux). Ce rappel est uniquement disponible dans les API de niveau 14 (sandwich) et au-dessus.

Vous obtenez essentiellement un appel à la méthode:

public abstract void onTrimMemory (int level)

Le niveau est 20 ou plus spécifiquement

public static final int TRIM_MEMORY_UI_HIDDEN

Je teste cela et cela fonctionne toujours, car le niveau 20 est simplement une "suggestion" selon laquelle vous souhaiterez peut-être libérer des 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. Les allocations importantes avec l'interface utilisateur doivent être libérées à ce stade pour permettre à la mémoire d'être mieux gérée.

Bien sûr, vous devez implémenter ceci pour faire ce qui est dit (purger la mémoire qui n’a pas été utilisée depuis un certain temps, effacer certaines collections inutilisées, etc. Les possibilités sont infinies (voir la documentation officielle pour d’autres possibles niveaux plus critiques ).

Mais ce qui est intéressant, c’est que le système d’exploitation vous dit: HEY, votre application est passée au second plan!

Ce qui est exactement ce que vous vouliez savoir en premier lieu.

Comment déterminez-vous quand vous êtes rentré?

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 quand vous recevez un appel à la méthode onTrimMemory ci-dessus.

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

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

Le moyen le plus simple de garantir que vous recevez toujours cela 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? ), procédez comme suit:

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 Interface, vous pouvez ajouter un else à ce if et implémenter ComponentCallbacks (sans le 2) utilisé dans les éléments inférieurs à l'API 14. Ce rappel ne possède que la fonction onLowMemory(). method et ne sont pas appelés lorsque vous accédez à l'arrière-plan , mais vous devez l'utiliser pour réduire la mémoire.

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

La dernière étape consiste à annuler l'inscription du rappel. Le meilleur endroit est probablement la méthode onTerminate() de votre application , mais , cette méthode n'est pas appelée sur un périphérique 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, à moins que vous ne souhaitiez vraiment pas être enregistré, vous pouvez l'ignorer, car votre processus est en train de mourir au niveau du système d'exploitation.

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

unregisterComponentCallbacks(mMemoryBoss);

Et c'est tout.

180
Martin Marconcini

Voici comment j'ai réussi à résoudre ce problème. Cela fonctionne sur le principe que l'utilisation d'une référence de temps entre les transitions d'activité fournira très probablement la preuve suffisante qu'une application a été "en arrière-plan" ou non.

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

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 fournit é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 à partir de laquelle 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, onPause () de l'activité qui démarre démarre le chronomètre, mais presque immédiatement la nouvelle activité en cours l'annule avant qu'il ne puisse atteindre le délai de transition maximal. Et ainsi wasInBackground serait false.

D'autre part, lorsqu'une activité passe au premier plan du lanceur, le réveil de l'appareil, la fin de l'appel téléphonique, etc., il est plus que probable que la tâche de minuterie exécutée avant cet événement et donc wasInBackground a été définie. to true.

174
d60402

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


La solution actuelle selon un Google I/O talk :

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 ici beaucoup de solutions étranges.

Mais il y a de l'espoir.

133

ProcessLifecycleOwner semble également être une solution prometteuse.

ProcessLifecycleOwner enverra les événements ON_START, ON_RESUME, lorsqu’une première activité passe par ces événements. ON_PAUSE, ON_STOP, les événements seront distribués avec un délai après la dernière activité passée. Ce délai est suffisamment long pour garantir que ProcessLifecycleOwner n'enverra aucun événement 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

public class AppLifecycleListener implements LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void onMoveToForeground() {
        // app moved to foreground
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void onMoveToBackground() {
       // app moved to background
    }
}

// register observer
ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleListener());

Selon le code source, le délai actuel est 700ms.

L'utilisation de cette fonctionnalité requiert également le dependencies:

implementation "Android.Arch.lifecycle:extensions:1.1.1" 
annotationProcessor "Android.Arch.lifecycle:compiler:1.1.1"
93
vokilam

Basé sur 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);

    }

}
65
rickul

Nous utilisons cette méthode. Cela semble trop simple pour fonctionner, mais cela a été bien testé dans notre application et fonctionne en fait étonnamment bien dans tous les cas, y compris le passage à 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 marche. BTW, Flurry semble utiliser la même logique (juste une supposition, je n'ai pas vérifié cela, mais cela dépend des 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 avons également migré vers onStart () dans les versions ultérieures du code. En outre, j'ajoute de super appels, qui étaient absents de mon message initial, car il s'agissait davantage d'un concept. code de travail.

61
Nick Frolov

Si votre application comporte plusieurs activités et/ou activités empilées, telles qu'un widget de barre de tabulation, le remplacement de onPause () et de onResume () ne fonctionnera pas. Ie lors du démarrage d'une nouvelle activité, les activités en cours seront mises en pause avant la création de la nouvelle. Il en va de même lorsque vous terminez (en utilisant le bouton "Précédent") 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 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 atteint 0, l'application est mise en arrière-plan SI les conditions suivantes sont remplies:

  • L’activité mise en pause n’est pas terminée (le bouton "Précédent" a été utilisé). Cela peut être fait en utilisant la méthode activity.isFinishing ()
  • Une nouvelle activité (même nom de package) ne démarre pas. 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 lors de la création/reprise d'une activité.

Lorsque vous pouvez détecter que l'application s'est résignée à l'arrière-plan, il est également facile de détecter son retour au premier plan.

54
Emil

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

Pour détecter si l'application est passée en tâche de fond, nous allons utiliser:

 @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
        }
    }
33
Harpreet

Pensez à utiliser onUserLeaveHint. Ce n'est appelé que lorsque votre application passe en arrière-plan. onPause devra traiter les cas critiques, 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 si vous pouvez simplement utiliser le rappel onUserLeaveHint qui fait ce que vous demandez.

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

Rappelez-vous que si l'utilisateur appuie sur le bouton d'accueil alors qu'il se trouvait dans l'écran de configuration, onUserLeaveHint sera appelé dans l'activité de configuration et lorsqu'il reviendra sur onResume sera appelé dans l'activité de configuration. Si vous ne disposez que de ce code de détection dans votre activité principale, vous manquerez ce cas d'utilisation. Pour avoir ce code dans toutes vos activités sans dupliquer le code, utilisez une classe d'activité abstraite qui étend l'activité et insérez votre code commun. 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 {
    ...
}
18
OldSchool4664

ActivityLifecycleCallbacks pourrait être intéressant, mais ce n'est pas bien documenté.

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

13
Reno

Le package Android.Arch.lifecycle fournit des classes et des interfaces vous permettant de créer des composants sensibles 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"
}

Conformément aux recommandations de 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, cette tendance conduit à une mauvaise organisation du code et à la multiplication des erreurs. En utilisant des composants sensibles au cycle de vie, vous pouvez déplacer le code des composants dépendants des méthodes du cycle de vie et les insérer dans les composants eux-mêmes.

Vous pouvez en lire plus ici: https://developer.Android.com/topic/libraries/architecture/lifecycle

10
matdev

Dans votre application, ajoutez le rappel et vérifiez l'activité de la racine 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();
            }
        }
    });
}
8
Cynichniy Bandera

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

Créez une activité de base pour toutes les activités de 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
    }
}

Maintenant, utilisez cette BaseActivity comme une super classe de toute votre activité, comme MainActivity étend BaseActivity et onAppStart sera appelé lorsque vous démarrez votre application et onAppPause () sera appelé lorsque l'application affichera l'arrière-plan à partir de n'importe quel écran.

6
kiran boghra

C'est assez facile avec ProcessLifecycleOwner

Ajoutez ces dépendances

implementation "Android.Arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "Android.Arch.lifecycle:compiler:$project.archLifecycleVersion"

En 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

6
Ege Kuzubasioglu

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

J'ai fait cela d'une manière simple. Suivez les instructions ci-dessous pour détecter l’arrière-plan/la phase d’avant-plan de l’application.

Avec un peu de solution de contournement, c'est possible. Ici, ActivityLifecycleCallbacks vient à la rescousse. Laissez-moi parcourir pas à pas.

  1. Commencez par créer une classe qui étend l’interface 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 suit: <application Android:name=".App".

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

    Déclarez 2 variables comme ci-dessous dans la classe “App”.

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

    activityReferences conservera le nombre de tâches dans l'état commencé. isActivityChangingConfigurations est un indicateur pour indiquer si l'activité actuelle subit un changement de configuration, comme un commutateur d'orientation.

  4. En utilisant le code suivant, vous pouvez détecter si l’application est mise 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 marche:

C'est une petite astuce réalisée avec la façon dont les méthodes Lifecycle sont appelées en séquence. Laissez-moi parcourir 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 où l'utilisateur appuie sur le bouton d'accueil de l'activité B au lieu du bouton de retour, il en sera toujours de même et activitéRéférences sera 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 change 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 effectué une vérification supplémentaire de isActivityChangingConfigurations afin d’éviter le scénario où l’Activité subit les modifications de configuration.

3
Komal Nikhare

J'ai trouvé une bonne méthode pour détecter une 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();

}

3
Folyd

Vous pouvez utiliser:

void protégé onRestart ()

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

enter image description here

3
AYBABTU

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 (). D'après la documentation , il est évident que cette API est uniquement destinée au débogage et au développement. Je mettrai à jour ce post 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 qui est presque aussi bon.

Edit 1: J'ai écrit un article de blog et créé n simple référentiel GitHub pour que cela soit vraiment facile.

La réponse acceptée et la meilleure cotée ne sont pas vraiment la meilleure approche. L'implémentation de isApplicationBroughtToBackground () dans la réponse la mieux notée ne gère pas la situation dans laquelle l'activité principale de l'application cède la place à une activité définie dans la même application, mais comporte un package Java différent. Je suis venu avec un moyen de faire cela qui fonctionnera dans ce cas.

Appelez ceci dans onPause (), et il vous dira si votre application passe en arrière-plan, car 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;
}
3
Sky Kelsey

Réponse correcte 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 le souhaitez (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 journal status : we are out et lorsque nous allons dans l'application, nous obtenons journal status : we are out

2
erfan

Voici la version modifiée de la réponse de @ d60402: https://stackoverflow.com/a/15573121/4747587

Faites tout ce qui est mentionné ici. Mais au lieu d’avoir un Base Activity et d’en faire un parent pour chaque activité et de remplacer la onResume() et onPause, procédez comme suit:

Dans votre classe d'application, ajoutez la ligne:

registerActivityLifecycleCallbacks (rappel Application.ActivityLifecycleCallbacks);

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

Jetez un oeil à cet article: https://Gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b

1
Henry

Vous pouvez utiliser ProcessLifecycleOwner pour y attacher 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");
    }
}

ensuite, sur la onCreate() de votre classe Application, appelez ceci:

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

avec cela, vous pourrez capturer les événements de ON_PAUSE et ON_STOP de votre application qui se produisent quand elle passe en arrière-plan.

1
Alécio Carvalho

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

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

            }
        });
    }
}
1
Bill Mote

je sais que c'est un peu tard, mais je pense que toutes ces réponses ont quelques problèmes alors que je l'ai fait comme ci-dessous et cela fonctionne parfaitement.

créer un rappel de cycle de vie d'activité comme ceci:

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

Cela semble être l’une des questions les plus complexes de Android puisque (au moment de l'écriture de cette lettre), Android n'a pas d'équivalent iOS de applicationDidEnterBackground() ou applicationWillEnterForeground(). rappels. J'ai utilisé un AppState Library qui a été mis en place par @ jenzz .

[AppState est] une bibliothèque Android simple et réactive, basée sur RxJava, qui surveille les changements d'état de l'application. Il informe 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, en particulier parce que mon application comportait plusieurs activités. Il était donc simple de vérifier onStart() ou onStop() sur une activité.

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’est agi simplement d’ajouter ces lignes à un emplacement 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;
        }
    }
});

En fonction de votre abonnement à l'observable, vous devrez peut-être vous désabonner pour éviter les fuites de mémoire. Encore une fois plus d'informations sur le page github .

1
Deniz

Étant donné que je n'ai trouvé aucune approche, qui gère également la rotation sans vérifier les horodatages, j'ai également partagé comment nous procédons 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'addition: 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 ça aide quelqu'un :)

1
Julian Horst

Ma solution a été inspirée par la réponse de @ d60402 et repose également sur une fenêtre temporelle, mais n'utilise pas la variable 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();
  }
}

SingletonApplication est une extension de Application classe:

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

Vous pouvez y parvenir facilement à l'aide de ActivityLifecycleCallbacks et ComponentCallbacks2 comme ci-dessous.

Créez une classe AppLifeCycleHandler implémentant les interfaces susmentionnées.

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 Application implémentez AppLifeCycleCallback pour obtenir les rappels lorsque l'application bascule entre l'avant-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 Alternativement, vous pouvez maintenant utiliser le composant d'architecture sensible au cycle de vie.

1
Naveen T P

C’est ma solution https://github.com/doridori/AndroidUtils/blob/master/App/src/main/Java/com/doridori/lib/app/ActivityCounter.Java

Fondamentalement impliqué dans le comptage des méthodes de cycle de vie pour toutes les activités avec une minuterie pour détecter les cas où aucune activité n'est actuellement au premier plan alors que l'application est (c'est-à-dire en rotation)

0
Dori

J'ai réussi à surveiller la navigation des applications en arrière-plan et en arrière-plan en implémentant une BaseActivity exploitant l'utilisation des rappels d'activité onResume, onPause et onStop. Voici mes implémentations.

override fun onResume() {
    super.onResume()
    if (AppActivityState.state == AppState.ON_LAUNCHED) {
        // We are in the first launch.
        onLaunched()
    } else {
        if (AppActivityState.state == AppState.ON_BACKGROUND) {
            // We came from background to foreground.
            AppActivityState.state = AppState.ON_FOREGROUND
            onForeground()
        } else {
            // We are just navigating through pages.
            AppActivityState.state = AppState.RESUMED
        }
    }
}

override fun onPause() {
    super.onPause()
    // If state is followed by onStop then it means we will going to background.
    AppActivityState.state = AppState.PAUSED
}

override fun onStop() {
    super.onStop()

    // App will go to background base on the 'pause' cue.
    if (AppActivityState.state == AppState.PAUSED) {
        AppActivityState.state = AppState.ON_BACKGROUND
        onBackground()
    }
}

Après avoir créé BaseActivity, il vous suffit d’étendre cette activité à n’importe quelle activité de votre application.

Dans ce type d'implémentation, vous pouvez détecter avec précision les éléments suivants: - onBackground> l'application passe en arrière-plan - onForeground> l'application revient au premier plan - onLaunch> l'application vient d'ouvrir

J'espère que cela t'aidera :)

0
Rhusfer

Voici ma solution. Enregistrez simplement ce ActivityLifecycleCallbacks dans votre classe d'application principale. Dans les commentaires, je mentionne un cas de profil d'activité de profil d'utilisateur. Cette activité est simplement une activité avec des bords transparents.

/**
 * This class used Activity lifecycle callbacks to determine when the application goes to the
 * background as well as when it is brought to the foreground.
 */
public class Foreground implements Application.ActivityLifecycleCallbacks
{
    /**
     * How long to wait before checking onStart()/onStop() count to determine if the app has been
     * backgrounded.
     */
    public static final long BACKGROUND_CHECK_DELAY_MS = 500;

    private static Foreground sInstance;

    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
    private boolean mIsForeground = false;
    private int mCount;

    public static void init(final Application application)
    {
        if (sInstance == null)
        {
            sInstance = new Foreground();
            application.registerActivityLifecycleCallbacks(sInstance);
        }
    }

    public static Foreground getInstance()
    {
        return sInstance;
    }

    public boolean isForeground()
    {
        return mIsForeground;
    }

    public boolean isBackground()
    {
        return !mIsForeground;
    }

    @Override
    public void onActivityStarted(final Activity activity)
    {
        mCount++;

        // Remove posted Runnables so any Meteor disconnect is cancelled if the user comes back to
        // the app before it runs.
        mMainThreadHandler.removeCallbacksAndMessages(null);

        if (!mIsForeground)
        {
            mIsForeground = true;
        }
    }

    @Override
    public void onActivityStopped(final Activity activity)
    {
        mCount--;

        // A transparent Activity like community user profile won't stop the Activity that launched
        // it. If you launch another Activity from the user profile or hit the Android home button,
        // there are two onStops(). One for the user profile and one for its parent. Remove any
        // posted Runnables so we don't get two session ended events.
        mMainThreadHandler.removeCallbacksAndMessages(null);
        mMainThreadHandler.postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                if (mCount == 0)
                {
                    mIsForeground = false;
                }
            }
        }, BACKGROUND_CHECK_DELAY_MS);
    }

    @Override
    public void onActivityCreated(final Activity activity, final Bundle savedInstanceState)
    {

    }

    @Override
    public void onActivityResumed(final Activity activity)
    {

    }

    @Override
    public void onActivityPaused(final Activity activity)
    {

    }

    @Override
    public void onActivitySaveInstanceState(final Activity activity, final Bundle outState)
    {

    }

    @Override
    public void onActivityDestroyed(final Activity activity)
    {

    }
}
0
Stephen

Nous pouvons développer 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 */ }
    }
}
0
Alex Kisel

Mon application doit "redémarrer" après le retour de l'arrière-plan - afficher une série d'activités, en fonction des sollicitations des clients. Après une recherche approfondie sur la façon de gérer les transitions d’arrière-plan/premier plan (traité très différemment entre iOS et Android), j’ai traversé cette question. Vous avez trouvé une aide très utile ici, spécialement de la réponse la plus votée et celle signalée comme correcte. Cependant, il suffit de rétablir l’activité racine CHAQUE FOIS que l’application entre au premier plan a l’air trop gênante lorsque l’on pense à UX. La solution qui a fonctionné pour moi, et celle qui me semble la plus adaptée - basée sur les fonctionnalités des applications Youtube et Twitter - consistait à combiner les réponses de @GirishNair à @ d60402: Appel du minuteur lorsque la mémoire de rognage de l'application était sélectionnée:

@Override
public void onTrimMemory(int level) {
    if (stateOfLifeCycle.equals("Stop")) {
        startActivityTransitionTimer();
    }

    super.onTrimMemory(level);
}

Ma limite de minuterie est fixée à 30 secondes - je songe à l'augmenter un peu.

private final long MAX_ACTIVITY_TRANSITION_TIME = 30000;

Et lorsque l'application passe au premier plan, est relancée ou qu'elle est détruite, appelez la méthode pour annuler le chronomètre.

Sur l'extension de l'application:

@Override
public void onActivityCreated(Activity activity, Bundle arg1) {
    stopActivityTransitionTimer();
    stateOfLifeCycle = "Create";
}

@Override
public void onActivityDestroyed(Activity activity) {
    stopActivityTransitionTimer();
    stateOfLifeCycle = "Destroy";
}

Sur l'activité (de préférence sur une activité de base, héritée des autres):

@Override
protected void onStart() {
    super.onStart();
    if (App.wasInBackground) {
        stopActivityTransitionTimer();
    }
}

Dans mon cas, lorsque l'application passe au premier plan après le délai maximal, une nouvelle tâche est créée. Par conséquent, la fonction stopActivityTransitionTimer () est appelée onActivityCreated () ou onActivityDestroyed (), dans la classe d'extension de l'application, ce qui rend inutile d'appeler la méthode dans une activité. . J'espère que ça aide.

0
Pablo

Que diriez-vous de cette solution

public class BaseActivity extends Activity
{

    static String currentAct = "";

    @Override
    protected void onStart()
    {
        super.onStart();

        if (currentAct.equals(""))
            Toast.makeText(this, "Start", Toast.LENGTH_LONG).show();

        currentAct = getLocalClassName();
    }

    @Override
    protected void onStop()
    {
        super.onStop();

        if (currentAct.equals(getLocalClassName()))
        {
            currentAct = "";
            Toast.makeText(this, "Stop", Toast.LENGTH_LONG).show();
        }
    }
}

Toute activité doit étendre BaseActivity.

Quand une activité appelle une autre (A-> B) alors currentAct n’est pas égal à getLocalClassName () car onStart () de la deuxième activité (B) est appelé avant le onStop () de la première (A) ( https : //developer.Android.com/guide/components/activities.html#CoordinatingActivities ).

Lorsque l'utilisateur appuie sur le bouton d'accueil ou que vous passez d'une application à l'autre, il suffit d'appeler onStop (), puis currentAct est égal à getLocalClassName ().

0
Ismael

En utilisant le code ci-dessous, je peux obtenir l'état de mon avant-plan ou de mon arrière-plan de l'application.

Pour plus de détails sur son fonctionnement, cliquez sur le texte fort ici

import Android.content.ComponentCallbacks2;
import Android.content.Context;
import Android.support.v7.app.AppCompatActivity;
import Android.os.Bundle;
import Android.widget.Toast;

public class MainActivity extends AppCompatActivity {

private Context context;
private Toast toast;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    context = this;
}

private void showToast(String message) {
    //If toast is already showing cancel it
    if (toast != null) {
        toast.cancel();
    }

    toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
    toast.show();
}

@Override
protected void onStart() {
    super.onStart();
    showToast("App In Foreground");
}

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
        showToast("App In Background");
    }
  }
}
0
Jitendra Singh

Ce que j'ai fait est de m'assurer que toutes les activités intégrées à l'application sont lancées avec startActivityForResult, puis de vérifier si onActivityResult a été appelé avant onResume. Si ce n'était pas le cas, cela signifie que nous venons de rentrer de quelque part en dehors de notre application.

boolean onActivityResultCalledBeforeOnResume;

@Override
public void startActivity(Intent intent) {
    startActivityForResult(intent, 0);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    onActivityResultCalledBeforeOnResume = true;
}

@Override
protected void onResume() {
    super.onResume();
    if (!onActivityResultCalledBeforeOnResume) {
        // here, app was brought to foreground
    }
    onActivityResultCalledBeforeOnResume = false;
}
0
arturh

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 drapeau global (oui, les globales sont mauvaises :) et définissez-le sur true chaque fois que vous démarrez une nouvelle activité. Définissez-le sur false dans la création de chaque activité. Ensuite, dans le onPause, cochez cette case. Si elle est fausse, votre application passe en arrière-plan ou se fait tuer.

0
Joris Weimar