Cómo detectar cuándo una aplicación de Android pasa a segundo plano y vuelve a primer plano

382

Estoy tratando de escribir una aplicación que haga algo específico cuando se vuelve a poner en primer plano después de un tiempo. ¿Hay alguna manera de detectar cuándo una aplicación se envía al fondo o se pone en primer plano?

iHorse
fuente
2
Puede ser agregar un caso de uso a la pregunta porque no parece ser obvio, por lo que no se aborda en las respuestas proporcionadas. La aplicación puede iniciar otra aplicación (Galería, por ejemplo), que seguirá residiendo en la misma pila y aparecerá como una de las pantallas de la aplicación, y luego presione el botón Inicio. Ninguno de los métodos que dependen del ciclo de vida de la aplicación (o incluso la administración de memoria) puede detectar esto. Activarían el estado de fondo justo cuando aparece la actividad externa, no cuando presiona Inicio.
Dennis K
Esta es la respuesta que estás buscando: stackoverflow.com/a/42679191/2352699
Fred Porciúncula
1
Consulte la solución de Google: stackoverflow.com/questions/3667022/…
user1269737

Respuestas:

98

Los métodos onPause()y onResume()se invocan cuando la aplicación se lleva al fondo y al primer plano nuevamente. Sin embargo, también se les llama cuando la aplicación se inicia por primera vez y antes de que se elimine. Puedes leer más en Actividad .

No hay ningún enfoque directo para obtener el estado de la aplicación mientras está en segundo plano o en primer plano, pero incluso me he enfrentado a este problema y encontré la solución con onWindowFocusChangedyonStop .

Para obtener más detalles, consulte aquí Android: solución para detectar cuándo una aplicación de Android pasa a segundo plano y vuelve a primer plano sin getRunningTasks o getRunningAppProcesses .

Girish Nair
fuente
174
Sin embargo, este enfoque causa falsos positivos como otros señalaron, porque estos métodos también se llaman cuando se realiza la transición entre actividades en la misma aplicación.
John Lehmann
99
Es peor que eso. Lo probé y a veces se llama a onResume mientras el teléfono está bloqueado. Si ve la definición de onResume en la documentación, encontrará: Tenga en cuenta que onResume no es el mejor indicador de que su actividad es visible para el usuario; una ventana del sistema como el teclado puede estar al frente. Use onWindowFocusChanged (boolean) para saber con certeza que su actividad es visible para el usuario (por ejemplo, para reanudar un juego). developer.android.com/reference/android/app/…
J-Rou
2
La solución publicada en el enlace no utiliza onResume / onPause, sino una combinación de onBackPressed, onStop, onStart y onWindowsFocusChanged. Funcionó para mí, y tengo una jerarquía de interfaz de usuario bastante compleja (con cajones, visores dinámicos, etc.)
Martin Marconcini
18
OnPause y onResume son específicos de la actividad. No aplicación. Cuando una aplicación se coloca en segundo plano y luego se reanuda, reanuda la actividad específica en la que estaba antes de pasar al fondo. Esto significa que necesitaría implementar todo lo que desee al reanudar desde el fondo en toda la Actividad de su Aplicación. Creo que la pregunta original buscaba algo así como un "onResume" para Aplicación y no Actividad.
SysHex
44
No puedo creer que no se ofrezca una API adecuada para una necesidad tan común. Inicialmente pensé que en UserLeaveHint () lo cortaría, pero no se puede saber si el usuario abandona la aplicación o no
atsakiridis
197

2018: Android admite esto de forma nativa a través de componentes del ciclo de vida.

Actualización de marzo de 2018 : ahora hay una mejor solución. Ver ProcessLifecycleOwner . Deberá usar los nuevos componentes de arquitectura 1.1.0 (más recientes en este momento) pero es específicamente diseñado para hacerlo.

Hay una muestra simple proporcionada en esta respuesta, pero escribí una aplicación de muestra y una publicación de blog respecto.

Desde que escribí esto en 2014, surgieron diferentes soluciones. Algunos funcionaron, se pensó que algunos estaban trabajando , pero tenían fallas (¡incluida la mía!) Y nosotros, como comunidad (Android) aprendimos a vivir con las consecuencias y escribimos soluciones para los casos especiales.

Nunca asuma que un solo fragmento de código es la solución que está buscando, es poco probable; mejor aún, trate de entender qué hace y por qué lo hace.

La MemoryBossclase nunca fue utilizada por mí como está escrita aquí, solo fue una pieza de pseudocódigo que funcionó.

A menos que haya una razón válida para que no use los nuevos componentes de arquitectura (y hay algunos, especialmente si apunta a apis súper antiguas), continúe y utilícelos. Están lejos de ser perfectos, pero tampocoComponentCallbacks2 .

ACTUALIZACIÓN / NOTAS (noviembre de 2015) : la gente ha estado haciendo dos comentarios, primero es que >=debe usarse en lugar de ==porque la documentación establece que no debe verificar los valores exactos . Esto está bien para la mayoría de los casos, pero tenga en cuenta que si solo le importa hacer algo cuando la aplicación pasó a segundo plano, tendrá que usar == y también combinarlo con otra solución (como las devoluciones de llamada de Activity Lifecycle), o usted Es posible que no obtenga el efecto deseado. El ejemplo (y esto me pasó a mí) es que si quieres bloquearsu aplicación con una pantalla de contraseña cuando pasa a segundo plano (como 1Password si está familiarizado con ella), puede bloquear accidentalmente su aplicación si se queda sin memoria y de repente la prueba >= TRIM_MEMORY, porque Android activará una LOW MEMORYllamada y eso es más alto que el tuyo Así que tenga cuidado de cómo / qué prueba.

Además, algunas personas han preguntado cómo detectar cuándo regresas.

La forma más simple que se me ocurre se explica a continuación, pero dado que algunas personas no están familiarizadas con esto, estoy agregando algunos pseudocódigos aquí. Suponiendo que tiene YourApplicationy las MemoryBossclases, en su class BaseActivity extends Activity(necesitará crear uno si no tiene uno).

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

Recomiendo onStart porque los cuadros de diálogo pueden pausar una actividad, por lo que apuesto a que no desea que su aplicación piense "pasó a un segundo plano" si todo lo que hizo fue mostrar un cuadro de diálogo de pantalla completa, pero su kilometraje puede variar.

Y eso es todo. El código en el bloque if solo se ejecutará una vez , incluso si va a otra actividad, la nueva (que también extends BaseActivity) informará wasInBackgroundes falsepara que no ejecute el código, hasta que onMemoryTrimmedse llame y el indicador se establezca en verdadero nuevamente .

Espero que ayude.

ACTUALIZACIÓN / NOTAS (abril de 2015) : antes de comenzar a copiar y pegar este código, tenga en cuenta que he encontrado un par de casos en los que puede no ser 100% confiable y debe combinarse con otros métodos para lograr los mejores resultados. En particular, hay dos instancias conocidas en las que onTrimMemoryno se garantiza la ejecución de la devolución de llamada:

  1. Si su teléfono bloquea la pantalla mientras su aplicación está visible (digamos que su dispositivo se bloquea después de nn minutos), esta devolución de llamada no se llama (o no siempre) porque la pantalla de bloqueo está en la parte superior, pero su aplicación aún está "ejecutándose" aunque cubierta.

  2. Si su dispositivo tiene relativamente poca memoria (y bajo estrés de memoria), el sistema operativo parece ignorar esta llamada y pasar directamente a niveles más críticos.

Ahora, dependiendo de lo importante que sea para usted saber cuándo su aplicación pasó a un segundo plano, es posible que necesite o no extender esta solución junto con realizar un seguimiento del ciclo de vida de la actividad y otras cosas.

Solo tenga en cuenta lo anterior y tenga un buen equipo de control de calidad;)

FIN DE ACTUALIZACIÓN

Puede ser tarde, pero hay un método confiable en Ice Cream Sandwich (API 14) y superior .

Resulta que cuando su aplicación ya no tiene una IU visible, se activa una devolución de llamada. La devolución de llamada, que puede implementar en una clase personalizada, se llama ComponentCallbacks2 (sí, con un dos). Esta devolución de llamada solo está disponible en API Nivel 14 (Ice Cream Sandwich) y superiores.

Básicamente recibes una llamada al método:

public abstract void onTrimMemory (int level)

El nivel es 20 o más específicamente

public static final int TRIM_MEMORY_UI_HIDDEN

He estado probando esto y siempre funciona, porque el nivel 20 es solo una "sugerencia" de que es posible que desee liberar algunos recursos ya que su aplicación ya no es visible.

Para citar los documentos oficiales:

Nivel para onTrimMemory (int): el proceso había estado mostrando una interfaz de usuario y ya no lo está haciendo. En este punto, se deben liberar grandes asignaciones con la IU para permitir que la memoria se administre mejor.

Por supuesto, debe implementar esto para hacer lo que dice (purgar la memoria que no se ha utilizado en cierto tiempo, borrar algunas colecciones que no se han utilizado, etc.) Las posibilidades son infinitas (consulte los documentos oficiales para obtener más información). niveles críticos ).

Pero, lo interesante, es que el sistema operativo te dice: ¡HEY, tu aplicación pasó a un segundo plano!

Que es exactamente lo que querías saber en primer lugar.

¿Cómo determina cuándo regresó?

Bueno, eso es fácil, estoy seguro de que tienes una "BaseActivity" para que puedas usar tu onResume () para marcar el hecho de que has vuelto. Porque la única vez que dirá que no ha regresado es cuando realmente recibe una llamada al onTrimMemorymétodo anterior .

Funciona. No obtienes falsos positivos. Si se reanuda una actividad, estás de vuelta el 100% de las veces Si el usuario vuelve a la parte posterior, recibe otra onTrimMemory()llamada.

Debe suscribir sus Actividades (o mejor aún, una clase personalizada).

La forma más fácil de garantizar que siempre reciba esto es crear una clase simple como esta:

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

Para usar esto, en la implementación de su aplicación ( tiene una, ¿DERECHO? ), Haga algo como:

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 crea una Interfacese podría añadir una elsea ese ife implementar ComponentCallbacks(sin la 2) usado en nada por debajo de la API de devolución de llamada 14. Que sólo tiene el onLowMemory()método y no ser llamado cuando vas a un segundo plano , pero que se debe utilizar para la memoria de ajuste .

Ahora inicie su aplicación y presione inicio. Su onTrimMemory(final int level)método debería llamarse (pista: agregar registro).

El último paso es cancelar el registro de la devolución de llamada. Probablemente el mejor lugar es el onTerminate()método de su aplicación, pero ese método no se llama en un dispositivo real:

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

Entonces, a menos que realmente tenga una situación en la que ya no quiera estar registrado, puede ignorarlo, ya que su proceso está muriendo a nivel del sistema operativo de todos modos.

Si decide cancelar el registro en algún momento (si, por ejemplo, proporciona un mecanismo de apagado para que su aplicación se limpie y muera), puede hacer lo siguiente:

unregisterComponentCallbacks(mMemoryBoss);

Y eso es.

Martin Marconcini
fuente
Al verificar esto desde un servicio, parece que solo se dispara cuando se presiona el botón de inicio. Al presionar el botón Atrás no se activa esto en KitKat.
Aprenda OpenGL ES
El servicio no tiene interfaz de usuario, por lo que podría estar relacionado con eso. Realice la verificación en su actividad base, no en un servicio. Desea saber cuándo su IU está oculta (y tal vez decirle al servicio, para que quede en primer plano)
Martin Marconcini
1
No funciona cuando apagas el teléfono. No se activa.
Juangcg
2
Usar ComponentCallbacks2.onTrimMemory () (en combinación con ActivityLifecycleCallbacks) es la única solución confiable que encontré hasta ahora, ¡gracias Martin! Para aquellos interesados, vea mi respuesta provista.
rickul
3
He estado usando este método desde hace un año y siempre ha sido confiable para mí. Es bueno saber que otras personas también lo usan. Solo uso lo level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENque evita el problema en su actualización, punto 2. Con respecto al punto 1, no es una preocupación para mí, ya que la aplicación realmente no pasó a un segundo plano, por lo que se supone que funciona.
sorianiv
175

Así es como me las arreglé para resolver esto. Funciona bajo la premisa de que el uso de una referencia de tiempo entre transiciones de actividad probablemente proporcionará evidencia adecuada de que una aplicación ha sido "en segundo plano" o no.

Primero, he usado una instancia de android.app.Application (llamémosla MyApplication) que tiene un Timer, un TimerTask, una constante para representar el número máximo de milisegundos que la transición de una actividad a otra podría llevar razonablemente (fui con un valor de 2s) y un valor booleano para indicar si la aplicación estaba "en segundo plano":

public class MyApplication extends Application {

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

La aplicación también proporciona dos métodos para iniciar y detener el temporizador / tarea:

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

La última parte de esta solución es agregar una llamada a cada uno de estos métodos desde los eventos onResume () y onPause () de todas las actividades o, preferiblemente, en una Actividad base de la cual todas sus Actividades concretas heredan:

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

Entonces, en el caso de que el usuario simplemente navegue entre las actividades de su aplicación, el onPause () de la actividad de partida inicia el temporizador, pero casi de inmediato la nueva actividad que se ingresa cancela el temporizador antes de que pueda alcanzar el tiempo máximo de transición. Y también lo eraInBackground sería falso .

Por otro lado, cuando una Actividad aparece en primer plano desde el Iniciador, el dispositivo se activa, finaliza la llamada telefónica, etc., es más que probable que la tarea del temporizador se haya ejecutado antes de este evento y, por lo tanto, wasInBackground se haya establecido en verdadero .

d60402
fuente
44
Hola d60402, su respuesta es realmente útil ... muchas gracias por esta respuesta ... pequeño aviso ... MyApplication debería mencionar en la etiqueta de la aplicación de archivo de manifiesto como android: name = "MyApplication", de lo contrario, la aplicación se bloquea ... solo para ayudar alguien como yo
praveenb
2
marca del gran programador, solución simple a uno de los problemas más complicados que he encontrado.
Aashish Bhatnagar
2
¡Impresionante solución! Gracias. Si alguien recibe el error "ClassCastException", es posible que haya perdido agregarlo en la etiqueta de la aplicación dentro de su Manifest.xml <application android: name = "your.package.MyApplication"
Wahib Ul Haq
27
Esta es una implementación agradable y simple. Sin embargo, creo que esto debería implementarse en onStart / onStop en lugar de onPause / onResume. Se llamará a onPause incluso si inicio un diálogo que cubre parcialmente la actividad. Y cerrar el cuadro de diálogo en realidad llamará a Reanudar para que parezca que la aplicación acaba de aparecer en primer plano
Shubhayu,
77
Espero usar una variación de esta solución. El punto sobre los diálogos identificados anteriormente es un problema para mí, así que probé la sugerencia de @ Shubhayu (onStart / onStop). Sin embargo, esto no ayuda porque cuando va A-> B, se llama onStart () de la Actividad B antes que onStop () de la Actividad A.
Trevor
150

Editar: los nuevos componentes de arquitectura trajeron algo prometedor: ProcessLifecycleOwner , vea la respuesta de @ vokilam


La solución real de acuerdo con una charla de E / S de Google :

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

}

Si. Sé que es difícil creer que esta solución simple funcione ya que tenemos tantas soluciones extrañas aquí.

Pero hay esperanza.

Fred Porciúncula
fuente
3
Esto funciona perfectamente! Ya probé tantas soluciones extrañas que tenían tantos defectos ... ¡muchas gracias! He estado buscando esto por un tiempo.
Eggakin Baconwalker
77
Funciona para múltiples actividades, pero para una: onrotate indicará que todas las actividades se han ido o están en segundo plano
deadfish
2
@Shyri tienes razón, pero eso es parte de esta solución, así que debes preocuparte. Si firebase se basa en esto, creo que mi aplicación mediocre también puede :) Gran respuesta, por cierto.
ElliotM
3
@deadfish Verifique el enlace a E / S provisto en la parte superior de la respuesta. Puede verificar las brechas de tiempo entre la parada de la actividad y comenzar a determinar si realmente pasó al fondo o no. Esta es una solución brillante, en realidad.
Alex Berdnikov
2
¿Hay una solución Java? Esto es Kotlin.
Giacomo Bartoli
116

ProcessLifecycleOwner Parece ser una solución prometedora también.

ProcessLifecycleOwner despachará ON_START, ON_RESUMEeventos, como primera actividad se mueve a través de estos eventos. ON_PAUSE, los ON_STOPeventos se enviarán con retraso después de que una última actividad los haya pasado. Este retraso es lo suficientemente largo como para garantizar que ProcessLifecycleOwnerno se enviarán eventos si las actividades se destruyen y se recrean debido a un cambio de configuración.

Una implementación puede ser tan simple como

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

Según el código fuente, el valor actual de retraso es 700ms.

También usar esta función requiere dependencies:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
vokilam
fuente
10
Tenga en cuenta que debe agregar las dependencias del ciclo de vida implementation "android.arch.lifecycle:extensions:1.0.0"y annotationProcessor "android.arch.lifecycle:compiler:1.0.0"del repositorio de Google (es decir google())
Sir Codesalot
1
Esto funcionó muy bien para mí, gracias. Tuve que usar la api 'android.arch.lifecycle: extensions: 1.1.0' en lugar de la implementación debido a un error que indica que la dependencia de Android tiene una versión diferente para el classpath de compilación y tiempo de ejecución.
FSUWX2011
¡Esta es una gran solución porque funciona en módulos sin necesidad de una referencia de actividad!
Max
Esto no funciona cuando la aplicación se bloquea. ¿Hay alguna solución para aplicación estrellado caso también a través de esta solución
tejraj
Gran solución Me salvó el día.
Soleado
69

Basado en la respuesta de Martín Marconcinis (¡gracias!) Finalmente encontré una solución confiable (y muy 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;
        }
    }
}

Luego agregue esto a su onCreate () de su clase de aplicación

public class MyApp extends android.app.Application {

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

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

    }

}
rickul
fuente
¿Puede mostrar cómo usa esto en una aplicación? ¿Llamo a esto desde la clase de aplicación o en otro lugar?
JPM
esto es perfecto gracias !! funciona muy bien en las pruebas hasta ahora
aherrick
Este ejemplo si está incompleto. ¿Qué es registerActivityLifecycleCallbacks?
Noman
es un método en la clase android.app.Application
rickul
1
Bien hecho +1 para ir arriba, porque es perfecto, no busques otras respuestas, esto se basa en la respuesta @reno pero con un ejemplo real
Stoycho Andreev
63

Usamos este método. Parece demasiado simple para trabajar, pero se probó bien en nuestra aplicación y, de hecho, funciona sorprendentemente bien en todos los casos, incluyendo ir a la pantalla de inicio con el botón "inicio", con el botón "regresar" o después del bloqueo de pantalla. Darle una oportunidad.

La idea es que, en primer plano, Android siempre comienza una nueva actividad justo antes de detener la anterior. Eso no está garantizado, pero así es como funciona. Por cierto, Flurry parece usar la misma lógica (solo una suposición, no lo comprobé, pero se engancha en los mismos eventos).

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

}

Editar: según los comentarios, también nos mudamos a onStart () en versiones posteriores del código. Además, estoy agregando super llamadas, que faltaban en mi publicación inicial, porque esto era más un concepto que un código de trabajo.

Nick Frolov
fuente
2
Esta es la respuesta más confiable, aunque uso onStart en lugar de onResume.
Greg Ennis
Debe agregar llamadas a super.onResume () y super.onStop () en los métodos reemplazados. De lo contrario, se lanza una android.app.SuperNotCalledException.
Jan Laussmann
1
para mí no funciona ... o al menos dispara el evento cuando también está girando el dispositivo (lo cual es una especie de falso positivo).
Noya
¡Solución muy simple y efectiva! Pero no estoy seguro de que funcione con actividades parcialmente transparentes que permiten ver algunas partes de la actividad anterior. A partir de la documentación, onStop is called when the activity is no longer visible to the user.
Nicolas Buquet
3
¿Qué sucede si el usuario cambia la orientación en la primera actividad? Informará que la aplicación pasó a un segundo plano, lo cual no es cierto. ¿Cómo manejas este escenario?
Nimrod Dayan
54

Si su aplicación consta de múltiples actividades y / o actividades apiladas como un widget de barra de pestañas, anular onPause () y onResume () no funcionará. Es decir, al comenzar una nueva actividad, las actividades actuales se pausarán antes de que se cree la nueva. Lo mismo se aplica al terminar (usando el botón "atrás") una actividad.

He encontrado dos métodos que parecen funcionar según lo deseado.

El primero requiere el permiso GET_TASKS y consiste en un método simple que verifica si la actividad principal en ejecución en el dispositivo pertenece a la aplicación, comparando los nombres de los paquetes:

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

Este método se encontró en el marco Droid-Fu (ahora llamado Ignition).

El segundo método que he implementado no requiere el permiso GET_TASKS, lo cual es bueno. En cambio, es un poco más complicado de implementar.

En su clase MainApplication tiene una variable que rastrea el número de actividades en ejecución en su aplicación. En onResume () para cada actividad aumentas la variable y en onPause () la disminuyes.

Cuando el número de actividades en ejecución llega a 0, la aplicación se pone en segundo plano SI se cumplen las siguientes condiciones:

  • La actividad que se está pausando no se está terminando (se utilizó el botón "Atrás"). Esto se puede hacer utilizando el método activity.isFinishing ()
  • No se está iniciando una nueva actividad (mismo nombre de paquete). Puede anular el método startActivity () para establecer una variable que indique esto y luego restablecerlo en onPostResume (), que es el último método que se ejecuta cuando se crea / reanuda una actividad.

Cuando puede detectar que la aplicación ha renunciado a un segundo plano, también es fácil detectar cuándo se vuelve a poner en primer plano.

Emil
fuente
18
Google probablemente rechazará una aplicación que use ActivityManager.getRunningTasks (). La documentación indica que es solo para propósitos de desarrollo enemigo. developer.android.com/reference/android/app/…
Sky Kelsey
1
Descubrí que tenía que usar una combinación de estos enfoques. onUserLeaveHint () se llamó al iniciar una actividad en 14. `@Override public void onUserLeaveHint () {inBackground = isApplicationBroughtToBackground (); } `
listado del barco el
77
Los usuarios no estarán muy contentos con el uso de un poderoso permiso android.permission.GET_TASKS.
MSquare
66
getRunningTasks está en desuso en el nivel 21 de API.
Noya
33

Crea una clase que se extienda Application. Luego, en ella podemos utilizar su método de reemplazo, onTrimMemory().

Para detectar si la aplicación pasó a un segundo plano, utilizaremos:

 @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
fuente
1
Para FragmentActivityusted también puede querer agregar level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEtambién.
Srujan Simha
2
Muchas gracias por apuntar a este método, necesito mostrar un cuadro de diálogo Pin siempre que el usuario reanude la actividad para el fondo, use este método para escribir un valor pref y verifique este valor en baseActivity.
Sam
18

Considere usar onUserLeaveHint. Esto solo se llamará cuando su aplicación pase a un segundo plano. onPause tendrá casos de esquina para manejar, ya que se puede llamar por otros motivos; por ejemplo, si el usuario abre otra actividad en su aplicación, como su página de configuración, se llamará al método onPause de su actividad principal aunque todavía esté en su aplicación; el seguimiento de lo que está sucediendo generará errores cuando en su lugar pueda simplemente usar la devolución de llamada onUserLeaveHint que hace lo que está pidiendo.

Cuando se llama a UserLeaveHint, puede establecer un indicador booleano inBackground en true. Cuando se llama a onResume, solo asuma que regresó al primer plano si la bandera inBackground está activada. Esto se debe a que onResume también se llamará a su actividad principal si el usuario solo estaba en su menú de configuración y nunca abandonó la aplicación.

Recuerde que si el usuario presiona el botón de inicio mientras está en su pantalla de configuración, onUserLeaveHint se llamará en su actividad de configuración, y cuando regrese a OnResume se llamará en su actividad de configuración. Si solo tiene este código de detección en su actividad principal, se perderá este caso de uso. Para tener este código en todas sus actividades sin duplicar el código, tenga una clase de actividad abstracta que extienda la Actividad y coloque su código común. Entonces, cada actividad que tenga puede extender esta actividad abstracta.

Por ejemplo:

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
fuente
19
onUserLeaveHint también se llama cuando se navega a otra actividad
Jonas Stawski
3
onUserLeaveHint no se llama cuando, por ejemplo, entra una llamada telefónica y la actividad de llamada se activa, por lo que esto también tiene un caso límite; también podría haber otros casos, ya que puede agregar un indicador a la intención de suprimir la llamada onUserLeaveHint. developer.android.com/reference/android/content/…
Groxx
1
Además, onResume no funciona bien. Lo probé y a veces se llama a onResume mientras el teléfono está bloqueado. Si ve la definición de onResume en la documentación, encontrará: Tenga en cuenta que onResume no es el mejor indicador de que su actividad es visible para el usuario; una ventana del sistema como el teclado puede estar al frente. Use onWindowFocusChanged (boolean) para saber con certeza que su actividad es visible para el usuario (por ejemplo, para reanudar un juego). developer.android.com/reference/android/app/…
J-Rou
esta solución no ayuda a decidir primer plano / fondo si hay múltiples actividades. Por favor,
Raj Trivedi
14

ActivityLifecycleCallbacks puede ser de interés, pero no está bien documentado.

Sin embargo, si llama a registerActivityLifecycleCallbacks (), debería poder obtener devoluciones de llamada para cuando las Actividades se crean, destruyen, etc. Puede llamar a getComponentName () para la Actividad.

Reno
fuente
11
Desde el nivel de API 14 = \
imort
Parece que este está limpio y funciona para mí. Gracias
duanbo1983
¿Cómo es esto diferente de la respuesta aceptada, ambos se basan en el mismo ciclo de vida de la actividad correcta?
Saitama
13

El paquete android.arch.lifecycle proporciona clases e interfaces que le permiten crear componentes que reconocen el ciclo de vida.

Su aplicación debe implementar la interfaz 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");
    }
}

Para hacer eso, debe agregar esta dependencia a su archivo build.gradle:

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

Según lo recomendado por Google, debe minimizar el código ejecutado en los métodos de actividades del ciclo de vida:

Un patrón común es implementar las acciones de los componentes dependientes en los métodos de ciclo de vida de actividades y fragmentos. Sin embargo, este patrón conduce a una mala organización del código y a la proliferación de errores. Al utilizar componentes que reconocen el ciclo de vida, puede mover el código de los componentes dependientes de los métodos del ciclo de vida a los componentes mismos.

Puede leer más aquí: https://developer.android.com/topic/libraries/architecture/lifecycle

matdev
fuente
y agregue esto para manifestar como: <application android: name = ". AnotherApp">
Dan Alboteanu
9

En su aplicación, agregue la devolución de llamada y verifique la actividad raíz de esta manera:

@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
fuente
Consideraría usar esta forma de implementación. Una transición de una actividad a otra solo toma unos pocos milisegundos. Según el momento en que desaparece la última actividad, se puede considerar volver a iniciar sesión en el usuario mediante una estrategia específica.
drindt
6

He creado un proyecto en Github app-foreground-background-listen

Cree una BaseActivity para toda la actividad en su aplicación.

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

Ahora use esta BaseActivity como una superclase de toda su Actividad, como MainActivity extiende BaseActivity y onAppStart se llamará cuando inicie su aplicación y onAppPause () se llamará cuando la aplicación pase al fondo desde cualquier pantalla.

kiran boghra
fuente
@kiran boghra: ¿Hay algún falso positivo en su solución?
Harish Vishwakarma
Respuesta perfecta La función onStart () y onStop () se puede utilizar en este caso. que le informa sobre su aplicación
Pir Fahim Shah
6

Esto es bastante fácil con ProcessLifecycleOwner

Agregar estas dependencias

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

Luego, en su actividad base:

override fun onCreate() {
        super.onCreate()

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

Vea mi artículo sobre este tema: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48

Ege Kuzubasioglu
fuente
5

Puede usar ProcessLifecycleOwner adjuntando un observador del ciclo de vida.

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

entonces en el onCreate()de su clase de Aplicación, llama a esto:

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

con esto podrás capturar los eventos de ON_PAUSEy ON_STOPde tu aplicación que suceden cuando pasa a segundo plano.

Alécio Carvalho
fuente
4

No hay métodos directos del ciclo de vida que le indiquen cuándo toda la aplicación pasa a segundo plano.

He hecho esto de manera simple. Siga las instrucciones a continuación para detectar la fase de fondo / primer plano de la aplicación.

Con una pequeña solución, es posible. Aquí, ActivityLifecycleCallbacks viene al rescate. Déjame caminar paso a paso.

  1. Primero, cree una clase que extienda android.app.Application e implemente la interfaz ActivityLifecycleCallbacks . En Application.onCreate (), registre la devolución de llamada.

    public class App extends Application implements 
        Application.ActivityLifecycleCallbacks {
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    }
  2. Registrar la clase “App” en el Manifiesto de la siguiente manera, <application android:name=".App".

  3. Habrá al menos una Actividad en el estado iniciado cuando la aplicación esté en primer plano y no habrá Actividad en el estado iniciado cuando la aplicación esté en segundo plano.

    Declare 2 variables como a continuación en la clase "Aplicación".

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

    activityReferencesmantendrá el recuento de la cantidad de actividades en el estado iniciado . isActivityChangingConfigurationses una bandera para indicar si la Actividad actual está pasando por un cambio de configuración como un interruptor de orientación.

  4. Con el siguiente código, puede detectar si la aplicación aparece en primer plano.

    @Override
    public void onActivityStarted(Activity activity) {
        if (++activityReferences == 1 && !isActivityChangingConfigurations) {
            // App enters foreground
        }
    }
  5. Así es como detectar si la aplicación pasa a segundo plano.

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

Cómo funciona:

Este es un pequeño truco hecho con la forma en que se llaman los métodos del Ciclo de vida en secuencia. Déjame pasar por un escenario.

Suponga que el usuario inicia la aplicación y se inicia la actividad de inicio A. Las llamadas del ciclo de vida serán,

A.onCreate ()

A.onStart () (++ activityReferences == 1) (la aplicación entra en primer plano)

A.onResume ()

Ahora la actividad A comienza la actividad B.

A.onPause ()

B.onCreate ()

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

B.onResume ()

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

Luego, el usuario navega hacia atrás desde la Actividad B,

B.onPause ()

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

A.onResume ()

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

B.onDestroy ()

Luego, el usuario presiona el botón Inicio,

A.onPause ()

A.onStop () (--activityReferences == 0) (la aplicación entra en segundo plano)

En caso de que, si el usuario presiona el botón de Inicio de la Actividad B en lugar del botón Atrás, seguirá siendo el mismo y las referencias de actividad serán 0 . Por lo tanto, podemos detectar como la aplicación entrando en segundo plano.

Entonces, ¿cuál es el papel de isActivityChangingConfigurations? En el escenario anterior, suponga que la Actividad B cambia la orientación. La secuencia de devolución de llamada será,

B.onPause ()

B.onStop () (--activityReferences == 0) (¿La aplicación entra en segundo plano?)

B.onDestroy ()

B.onCreate ()

B.onStart () (++ activityReferences == 1) (¿La aplicación entra en primer plano?)

B.onResume ()

Es por eso que tenemos una verificación adicional isActivityChangingConfigurationspara evitar el escenario cuando la Actividad está pasando por los cambios de Configuración.

Komal Nikhare
fuente
3

Encontré un buen método para detectar aplicaciones, ya sea que ingrese en primer plano o en segundo plano. Aquí está mi código . Espero que esto te ayude.

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

Puedes usar:

vacío protegido en Reiniciar ()

Para diferir entre nuevos comienzos y reinicios.

ingrese la descripción de la imagen aquí

AYBABTU
fuente
3

Edición 2: lo que he escrito a continuación en realidad no funcionará. Google ha rechazado una aplicación que incluye una llamada a ActivityManager.getRunningTasks (). De la documentación , es evidente que esta API es solo para fines de depuración y desarrollo. Actualizaré esta publicación tan pronto como tenga tiempo para actualizar el proyecto de GitHub a continuación con un nuevo esquema que usa temporizadores y es casi tan bueno.

Edición 1: escribí una publicación de blog y creé un repositorio simple de GitHub para que esto sea realmente fácil.

La respuesta aceptada y mejor calificada no son realmente el mejor enfoque. La implementación de la respuesta mejor calificada de isApplicationBroughtToBackground () no maneja la situación en la que la Actividad principal de la Aplicación está cediendo a una Actividad que está definida en la misma Aplicación, pero tiene un paquete Java diferente. Se me ocurrió una manera de hacer esto que funcionará en ese caso.

Llame a esto en onPause (), y le dirá si su aplicación está en segundo plano porque otra aplicación se ha iniciado o si el usuario ha presionado el botón de inicio.

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
fuente
Para su información, llamar a esto en onStart () en su lugar evitará que se llame cuando se genera un simple diálogo desde, por ejemplo, una alarma que suena.
Sky Kelsey
2

Respuesta correcta aquí

Cree una clase con el nombre MyApp como se muestra a continuación:

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

    }
}

Luego, donde quiera (mejor primera actividad iniciada en la aplicación), agregue el siguiente código:

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

¡Hecho! Ahora, cuando la aplicación está en segundo plano, obtenemos un registro status : we are out y cuando entramos en la aplicación, obtenemos un registrostatus : we are out

erfan
fuente
1

Mi solución se inspiró en la respuesta de @ d60402 y también se basa en una ventana de tiempo, pero no usando 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();
  }
}

donde el SingletonApplicationes una extensión de Applicationclase:

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

Estaba usando esto con Google Analytics EasyTracker, y funcionó. Podría extenderse para hacer lo que busca utilizando un entero 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) {

            }
        });
    }
}
Bill Mote
fuente
1

Sé que es un poco tarde, pero creo que todas estas respuestas tienen algunos problemas mientras lo hice a continuación y eso funciona perfecto.

cree una devolución de llamada del ciclo de vida de la actividad como esta:

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

    }
}

y simplemente regístralo en tu clase de aplicación como a continuación:

public class MyApp extends Application {

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
Amir Ziarati
fuente
Esto se llama todo el tiempo en cada actividad. ¿Cómo puedo usar esto si, por ejemplo, quiero detectar el estado en línea del usuario
Maksim Kniazev
eso es lo que la pregunta quiere. solo se llama cuando vas a la pantalla de inicio y vuelves a cualquier actividad.
Amir Ziarati
si te refieres a la conectividad a Internet, creo que es mejor verificar eso cuando lo necesites. Si necesita llamar a una API, verifique la conexión a Internet justo antes de llamar.
Amir Ziarati
1

Esta parece ser una de las preguntas más complicadas en Android ya que (al momento de escribir esto) Android no tiene equivalentes applicationDidEnterBackground()o applicationWillEnterForeground()devoluciones de llamada de iOS . Usé una biblioteca de AppState que fue creada por @jenzz .

[AppState es] una biblioteca de Android simple y reactiva basada en RxJava que monitorea los cambios de estado de la aplicación. Notifica a los suscriptores cada vez que la aplicación pasa a segundo plano y vuelve a estar en primer plano.

Resultó que esto es exactamente lo que necesitaba, especialmente porque mi aplicación tenía múltiples actividades, por lo que simplemente verificar onStart()o realizar onStop()una actividad no iba a ser suficiente.

Primero agregué estas dependencias a gradle:

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

Luego fue una simple cuestión de agregar estas líneas a un lugar apropiado en su código:

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

Dependiendo de cómo se suscriba al observable, es posible que deba cancelar su suscripción para evitar pérdidas de memoria. Nuevamente más información en la página de github .

deniz
fuente
1

Esta es la versión modificada de la respuesta de @ d60402: https://stackoverflow.com/a/15573121/4747587

Haz todo lo mencionado allí. Pero en lugar de tener un Base Activityy hacer eso como padre para cada actividad y anular el onResume()y onPause, haga lo siguiente:

En su clase de aplicación, agregue la línea:

registerActivityLifecycleCallbacks (devolución de llamada Application.ActivityLifecycleCallbacks);

Esto callbacktiene todos los métodos de ciclo de vida de la actividad y ahora puede anular onActivityResumed()y onActivityPaused().

Echa un vistazo a este Gist: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b

Enrique
fuente
1

Esto se puede conseguir fácilmente con la ayuda de ActivityLifecycleCallbacksy ComponentCallbacks2algo parecido a continuación.

Cree una clase AppLifeCycleHandlerimplementando por encima de dichas 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();
  }
}

En su clase, que amplía el Applicationimplemento AppLifeCycleCallbackpara obtener las devoluciones de llamada cuando la aplicación cambia entre primer plano y fondo. Algo como abajo.

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

Espero que esto ayude.

EDITAR Como alternativa, ahora puede utilizar el componente de arquitectura consciente del ciclo de vida.

Naveen TP
fuente
1

Como no encontré ningún enfoque, que también maneja la rotación sin verificar las marcas de tiempo, pensé que también compartiría cómo lo hacemos ahora en nuestra aplicación. La única adición a esta respuesta https://stackoverflow.com/a/42679191/5119746 es que también tenemos en cuenta la orientación.

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

Luego, para las devoluciones de llamada, primero tenemos el currículum:

   // ActivityLifecycleCallbacks

   override fun onActivityResumed(activity: Activity?) {

      mResumed++

      if (mAppIsInBackground) {

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

         mAppIsInBackground = false
      }
      mOrientationWasChanged = false
    }

Y enActivityStopped:

   override fun onActivityStopped(activity: Activity?) {

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

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

        mAppIsInBackground = true
    }

Y luego, aquí viene la adición: Verificar cambios de orientación:

   override fun onConfigurationChanged(newConfig: Configuration) {

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

Eso es. Espero que esto ayude a alguien :)

Julian Horst
fuente
1

Podemos ampliar esta solución usando 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
    }
}

Ahora podemos suscribirnos a este LiveData y capturar los eventos necesarios. Por ejemplo:

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

Estas respuestas no parecen ser correctas. Estos métodos también se llaman cuando comienza y termina otra actividad. Lo que puede hacer es mantener una bandera global (sí, los globales son malos :) y establecer esto en verdadero cada vez que comience una nueva actividad. Póngalo en falso en onCreate de cada actividad. Luego, en onPause, marca esta bandera. Si es falsa, su aplicación está pasando a un segundo plano o está siendo eliminada.

Joris Weimar
fuente
No hablé de una base de datos ... ¿qué quieres decir?
Joris Weimar
Estoy apoyando tu respuesta. a pesar de que podemos guardar ese valor de marca en la base de datos mientras estamos en pausa, no es la buena solución ..
Sandeep P