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?
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?
Respuestas:
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 onWindowFocusChanged
yonStop
.
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 .
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 MemoryBoss
clase 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 MEMORY
llamada 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 YourApplication
y las MemoryBoss
clases, 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á wasInBackground
es false
para que no ejecute el código, hasta que onMemoryTrimmed
se 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 onTrimMemory
no se garantiza la ejecución de la devolución de llamada:
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.
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 onTrimMemory
mé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 Interface
se podría añadir una else
a ese if
e 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.
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
que 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.
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 .
Editar: los nuevos componentes de arquitectura trajeron algo prometedor: ProcessLifecycleOwner , vea la respuesta de @ vokilam
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.
ProcessLifecycleOwner
Parece ser una solución prometedora también.
ProcessLifecycleOwner despachará
ON_START
,ON_RESUME
eventos, como primera actividad se mueve a través de estos eventos.ON_PAUSE
, losON_STOP
eventos se enviarán con retraso después de que una última actividad los haya pasado. Este retraso es lo suficientemente largo como para garantizar queProcessLifecycleOwner
no 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"
implementation "android.arch.lifecycle:extensions:1.0.0"
y annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
del repositorio de Google (es decir google()
)
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);
}
}
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.
onStop is called when the activity is no longer visible to the user
.
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:
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.
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
}
}
FragmentActivity
usted también puede querer agregar level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
también.
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 {
...
}
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.
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
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();
}
}
});
}
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.
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
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_PAUSE
y ON_STOP
de tu aplicación que suceden cuando pasa a segundo plano.
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.
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);
}
}
Registrar la clase “App” en el Manifiesto de la siguiente manera, <application android:name=".App"
.
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;
activityReferences
mantendrá el recuento de la cantidad de actividades en el estado iniciado . isActivityChangingConfigurations
es una bandera para indicar si la Actividad actual está pasando por un cambio de configuración como un interruptor de orientación.
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
}
}
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 isActivityChangingConfigurations
para evitar el escenario cuando la Actividad está pasando por los cambios de Configuración.
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();
}
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;
}
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
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 SingletonApplication
es una extensión de Application
clase:
public class SingletonApplication extends Application {
public boolean isInBackground = false;
public long lastForegroundTransition = 0;
}
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) {
}
});
}
}
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());
}
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 .
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 Activity
y 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 callback
tiene 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
Esto se puede conseguir fácilmente con la ayuda de ActivityLifecycleCallbacks
y ComponentCallbacks2
algo parecido a continuación.
Cree una clase AppLifeCycleHandler
implementando 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 Application
implemento AppLifeCycleCallback
para 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.
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 :)
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 */ }
}
}
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.