¿Cómo comprobar si la actividad está en primer plano o en un fondo visible?


104

Tengo una pantalla de presentación en un temporizador. Mi problema es que antes de comenzar finish()mi actividad necesito verificar que la siguiente actividad haya comenzado porque aparece un cuadro de diálogo del sistema y solo quiero hacerlo finish(); una vez que el usuario ha seleccionado una opción del cuadro de diálogo?

Sé que hay muchas preguntas sobre cómo ver si su actividad está en primer plano, pero no sé si esto también permite cuadros de diálogo en la parte superior de la actividad.

Aquí está el problema, el rojo es mi actividad que está en segundo plano mientras que el diálogo está en primer plano:

el rojo es mi actividad que está en segundo plano mientras que el diálogo está en primer plano

EDITAR: He intentado simplemente no usar, finish()pero luego mi actividad puede volver a la pila de aplicaciones que estoy tratando de evitar.



Para aclarar, ¿desea iniciar un selector de intención y esperar a que su aplicación termine () hasta que el usuario haya tocado una de las opciones? Parece que necesita Intent.createChooser () y startActivityForResult () seguido de finish () cuando se recibe el resultado.
Alanv


ProcessLifecycleOwner es la solución más nueva
SR

Respuestas:


189

Esto es lo que se recomienda como solución adecuada :

La solución correcta (los créditos van para Dan, CommonsWare y NeTeInStEiN) Realice un seguimiento de la visibilidad de su aplicación por su cuenta utilizando los métodos Activity.onPause, Activity.onResume. Almacene el estado de "visibilidad" en alguna otra clase. Las buenas opciones son su propia implementación de la Aplicación o un Servicio (también hay algunas variaciones de esta solución si desea verificar la visibilidad de la actividad del servicio).

Ejemplo Implementar la clase de aplicación personalizada (tenga en cuenta el método estático isActivityVisible ()):

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

Registre su clase de aplicación en AndroidManifest.xml:

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

Agregue onPause y onResume a cada actividad en el proyecto (puede crear un ancestro común para sus actividades si lo desea, pero si su actividad ya se extendió desde MapActivity / ListActivity, etc., aún debe escribir lo siguiente a mano) :

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

En su finish()método, desea utilizar isActivityVisible()para verificar si la actividad es visible o no. Allí también puedes comprobar si el usuario ha seleccionado una opción o no. Continúe cuando se cumplan ambas condiciones.

La fuente también menciona dos soluciones incorrectas ... así que evite hacer eso.

Fuente: stackoverflow


Hay un pequeño momento entre el final y el inicio de la actividad y
creo que

28
Esto no funciona de manera confiable. Es posible que tenga la siguiente situación: Reanudar A Reanudar B Pausa A. Ahora activityVisible es falso mientras que la aplicación es visible. Quizás use un contador de visibilidad: visibleCounter ++ en onResume y visibleCounter - en onPause.
Joris Weimar

4
Estuvo de acuerdo con Joris Weimar en que esta no es una solución infalible. Un escenario es si el usuario tira hacia abajo el panel de notificaciones, ni el onPause, onStopni el onResumeevento se llama. Entonces, ¿qué haces si ninguno de estos eventos se dispara?

1
De hecho, ninguna de las otras respuestas funciona al 100%.

2
Si la aplicación tiene más de una actividad, este esquema no funcionará. Reemplazar con contadores al menos
ruX

70

Si tiene como objetivo el nivel de API 14 o superior, se puede usar android.app.Application.ActivityLifecycleCallbacks

public class MyApplication extends Application implements ActivityLifecycleCallbacks {
    private static boolean isInterestingActivityVisible;

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

        // Register to be notified of activity state changes
        registerActivityLifecycleCallbacks(this);
        ....
    }

    public boolean isInterestingActivityVisible() {
        return isInterestingActivityVisible;
    }

    @Override
    public void onActivityResumed(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = true;
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = false;
        }
    }

    // Other state change callback stubs
    ....
}

18
También podría hacer esto en las devoluciones de llamada del ciclo de vida de la actividad regular (onResume (), onStop ()), diría yo.
Daniel Wilson

5
@DanielWilson Creo que el punto no es construir un sistema para hacer algo donde ya existe. En mi humilde opinión, esta debería ser la respuesta aceptada.
Jeffrey Blattman

26

UPD : actualizado al estado Lifecycle.State.RESUMED. Gracias a @htafoya por eso.

En 2019, con la ayuda de la nueva biblioteca de soporte 28+o AndroidX, simplemente puede usar:

val isActivityInForeground = activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)

Puedes leer más en la documentación para comprender lo que sucedió bajo el capó.


2
En realidad no, probablemente sea mejor colocar activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) o EMPEZAR. INITIALIZEDno garantiza que esté en primer plano.
htafoya

11

Activity :: hasWindowFocus () te devuelve el booleano que necesitas.

public class ActivityForegroundChecker extends TimerTask
{
    private static final long FOREGROUND_CHECK_PERIOD = 5000;
    private static final long FIRST_DELAY             = 3000;

    private Activity m_activity;
    private Timer    m_timer;

    public ActivityForegroundChecker (Activity p_activity)
    {
        m_activity = p_activity;
    }

    @Override
    public void run()
    {
        if (m_activity.hasWindowFocus() == true) {
            // Activity is on foreground
            return;
        }
        // Activity is on background.
    }

    public void start ()
    {
        if (m_timer != null) {
            return;
        }
        m_timer = new Timer();
        m_timer.schedule(this, FIRST_DELAY, FOREGROUND_CHECK_PERIOD);
    }

    public void stop ()
    {
        if (m_timer == null) {
            return;
        }
        m_timer.cancel();
        m_timer.purge();
        m_timer = null;
    }
}

Aquí hay una clase de ejemplo para verificar la visibilidad de sus actividades desde donde se encuentre.

Recuerde que si muestra un diálogo , el resultado será falso ya que el diálogo tendrá el foco principal. Aparte de eso, es realmente útil y más confiable que las soluciones sugeridas.


1
Gracias por enmendar la respuesta @Burak Day, en realidad es una respuesta ahora
Nick

Esto no funciona, prefiero usar una propiedad booleana en la clase, configurada como verdadera en OnResume y configurada como falsa en OnPause ();
Chandler

@Chandler, ¿cuál es el problema exacto que tiene con este código? ¿También qué versión?
Día Burak

@Chandler también, ¿qué sucede si no tiene acceso a los métodos del ciclo de vida de la actividad? Considere que solo está verificando la visibilidad de la actividad desde una biblioteca.
Día Burak

El verdadero problema de esta respuesta es que NO funciona, activity.hasWindowFocus es verdadero no puede garantizar que la actividad esté entre el estado onResume y onPause. Preferiría agregar una propiedad bool isResumed en esa actividad, establecer manualmente el valor y agregar un método de obtención.
Chandler

10

Esa es exactamente la diferencia entre onPausey onStopeventos de la actividad como se describe en la documentación de la clase Actividad .

Si te entiendo bien, lo que quieres hacer es llamar finish()desde tu actividad onStoppara dar por terminada. Vea la imagen adjunta de la aplicación Activity Lifecycle Demo . Así es como se ve cuando la Actividad B se inicia desde la Actividad A. El orden de los eventos es de abajo hacia arriba para que pueda ver que la Actividad A onStopse llama después de que la Actividad B onResumeya se llamó.

Demostración del ciclo de vida de la actividad

En caso de que se muestre un cuadro de diálogo, su actividad se atenuará en segundo plano y solo onPausese llamará.


7

Dos posibles soluciones:

1) Devolución de llamada del ciclo de vida de la actividad

Utilice una aplicación que implemente ActivityLifecycleCallbacks y úsela para realizar un seguimiento de los eventos del ciclo de vida de las actividades en su aplicación. Tenga en cuenta que ActivityLifecycleCallbacks son para la api de Android> = 14. Para la api de Android anterior, deberá implementarla usted mismo dentro de todas sus actividades ;-)

Utilice la aplicación cuando necesite compartir / almacenar estados en todas las actividades.

2) Verifique la información del proceso en ejecución

Puede verificar el estado de un proceso en ejecución con esta clase RunningAppProcessInfo

Obtenga la lista de procesos en ejecución con ActivityManager.getRunningAppProcesses () y filtre la lista de resultados para verificar el RunningAppProcessInfo deseado y verificar su "importancia"



3

Utilice el intervalo de tiempo entre la pausa y la reanudación desde el fondo para determinar si está despierto desde el fondo

En aplicación personalizada

private static boolean isInBackground;
private static boolean isAwakeFromBackground;
private static final int backgroundAllowance = 10000;

public static void activityPaused() {
    isInBackground = true;
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (isInBackground) {
                isAwakeFromBackground = true;
            }
        }
    }, backgroundAllowance);
    Log.v("activity status", "activityPaused");
}

public static void activityResumed() {
    isInBackground = false;
    if(isAwakeFromBackground){
        // do something when awake from background
        Log.v("activity status", "isAwakeFromBackground");
    }
    isAwakeFromBackground = false;
    Log.v("activity status", "activityResumed");
}

En la clase BaseActivity

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

3

Creo que tengo una mejor solución. Porque puede compilar simplemente en MyApplication.activityResumed (); a cada actividad en una extensión.

Primero tienes que crear (como CyberneticTwerkGuruOrc)

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

A continuación, debe agregar la clase de aplicación a AndroidManifest.xml

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

Luego, crea la clase ActivityBase

public class ActivityBase extends Activity {

    @Override
    protected void onPause() {
        super.onPause();
        MyApplication.activityPaused();
    }

    @Override
    protected void onResume() {
        super.onResume();
        MyApplication.activityResumed();
    }
}

Finalmente, cuando crea una nueva Actividad, simplemente puede extenderla por ActivityBase en lugar de Activity.

public class Main extends ActivityBase {
    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }
}

Para mí, es un mejor método porque solo debes recordar sobre extender por ActivityBase. Además, puede ampliar su función básica en el futuro. En mi caso, agregué receptores para mi servicio y alertas sobre la red en una clase.

Si desea verificar la visibilidad de su aplicación, simplemente puede llamar

MyApplication.isActivityVisible()

¿Qué pasa si necesito mis actividades para extender la AppCombatActivity?
winklerrr

2

Esto puede lograr esto de una manera eficiente utilizando Application.ActivityLifecycleCallbacks

Por ejemplo, tomemos el nombre de la clase de actividad como ProfileActivity permite encontrar si está en primer plano o en segundo plano

primero tenemos que crear nuestra clase de aplicación ampliando la clase de aplicación

que implementa

Application.ActivityLifecycleCallbacks

Seamos mi clase de aplicación de la siguiente manera

Clase de aplicación

public class AppController extends Application implements Application.ActivityLifecycleCallbacks {


private boolean activityInForeground;

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

//register ActivityLifecycleCallbacks  

    registerActivityLifecycleCallbacks(this);

}



public static boolean isActivityVisible() {
    return activityVisible;
}

public static void activityResumed() {
    activityVisible = true;
}

public static void activityPaused() {
    activityVisible = false;
}

private static boolean activityVisible;

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

}

@Override
public void onActivityStarted(Activity activity) {

}

@Override
public void onActivityResumed(Activity activity) {
    //Here you can add all Activity class you need to check whether its on screen or not

    activityInForeground = activity instanceof ProfileActivity;
}

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

}

public boolean isActivityInForeground() {
    return activityInForeground;
}
}

en la clase anterior hay un método de anulación enActivityResumed de ActivityLifecycleCallbacks

 @Override
public void onActivityResumed(Activity activity) {
    //Here you can add all Activity class you need to check whether its on screen or not

    activityInForeground = activity instanceof ProfileActivity;
}

donde se pueden encontrar todas las instancias de actividad que se muestran actualmente en la pantalla, simplemente verifique si su actividad está en la pantalla o no mediante el método anterior.

Registre su clase de aplicación en manifest.xml

<application
    android:name=".AppController" />

Para verificar el clima, la actividad está en primer plano o en segundo plano según la solución anterior, llame al siguiente método en los lugares que necesita verificar

AppController applicationControl = (AppController) getApplicationContext();
    if(applicationControl.isActivityInForeground()){
     Log.d("TAG","Activity is in foreground")
    }
    else
    {
      Log.d("TAG","Activity is in background")
    }

1

Si desea saber si alguna actividad de su aplicación es visible en la pantalla, puede hacer algo como esto:

public class MyAppActivityCallbacks implements Application.ActivityLifecycleCallbacks {
private Set<Class<Activity>> visibleActivities = new HashSet<>();

@Override
public void onActivityResumed(Activity activity) {
    visibleActivities.add((Class<Activity>) activity.getClass());
}

@Override
public void onActivityStopped(Activity activity) {
     visibleActivities.remove(activity.getClass());
}

public boolean isAnyActivityVisible() {
    return !visibleActivities.isEmpty();
}

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

@Override
public void onActivityStarted(Activity activity) {}

@Override
public void onActivityPaused(Activity activity) {}

@Override
public void onActivityDestroyed(Activity activity) {}

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

Simplemente cree un singleton de esta clase y configúrelo en su instancia de aplicación como se muestra a continuación:

class App extends Application{
     @Override
     public void onCreate() {
         registerActivityLifecycleCallbacks(myAppActivityCallbacks);
     }
}

¡Entonces puede usar el método isAnyActivityVisible () de su instancia MyAppActivityCallbacks en todas partes!


0

¿Intentaste no llamar a finish y poner "android: noHistory =" true "en el manifiesto? Esto evitará que la actividad vaya a la pila.


0

Debo decir que su flujo de trabajo no es estándar de Android. En Android, no necesita finish()su actividad si desea abrir otra actividad desde Intent. En cuanto a la conveniencia del usuario, Android le permite usar la tecla 'atrás' para volver de la actividad que abrió a su aplicación.

Así que deje que el sistema detenga su actividad y guarde todo lo que sea necesario cuando se recupere su actividad.


Realmente no sé por qué se rebaja esto. Al menos dar un comentario como razón es lo que quiero.
Owen Zhao

3
"Pero este no es el androide", las respuestas en la forma son fastidiosas y no responden a la pregunta planteada en primer lugar. además, hay razones válidas para terminar (); - por ejemplo, es concebible que volver a él una vez que la acción ha tenido lugar no sirva para nada. en otras palabras, ¿crees que ponen fin () ahí por diversión? permanecer en la pila es exactamente lo que el autor de la pregunta quería evitar
Lassi Kinnunen

0

Guarde una bandera si está pausado o reanudado. Si se reanuda, significa que está en primer plano.

boolean  isResumed = false;

@Override
public void onPause() {
  super.onPause();    
  isResumed = false;
}

@Override
public void onResume() {
  super.onResume();    
  isResumed = true;
}

private void finishIfForeground() {
  if (isResumed) {
    finish();
  }
}

0

Una posible solución podría ser establecer una bandera mientras se muestra el diálogo del sistema y luego en el método onStop del ciclo de vida de la actividad, verificar la bandera, si es verdadera, finalizar la actividad.

Por ejemplo, si el cuadro de diálogo del sistema se activa al hacer clic en un botón, entonces el oyente onclick podría ser como

private OnClickListener btnClickListener = new OnClickListener() {

    @Override
    public void onClick(View v) {           
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_SEND);
        intent.setType("text/plain");
        CheckActivity.this.startActivity(Intent.createChooser(intent, "Complete action using"));
        checkFlag = true;  //flag used to check

    }
};

y en la parada de la actividad:

@Override
protected void onStop() {
    if(checkFlag){
        finish();
    }
    super.onStop();
}

0

¿Por qué no utilizar transmisiones para esto? la segunda actividad (la que necesita estar activada) puede enviar una transmisión local como esta:

//put this in onCreate(..) or any other lifecycle method that suits you best
//notice the string sent to the intent, it will be used to register a receiver!
Intent result = new Intent("broadcast identifier");
result.putString("some message");//this is optional
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(result);

luego escriba un receptor simple dentro de la actividad de bienvenida:

//this goes on the class level (like a class/instance variable, not in a method) of your splash activity:
private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //kill activity here!!!
        //mission accomplished!
    }
};

y registre su nuevo receptor con LocalBroadcastManager para escuchar la transmisión de su segunda actividad:

//notice the string sent to the intent filter, this is where you tell the BroadcastManager which broadcasts you want to listen to!
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(receiver, new IntentFilter("broadcast identifier"));

TENGA EN CUENTA que puede usar una constante o un recurso de cadena para la cadena "Identificador de difusión".


Para una mejor eficiencia de los anuncios de seguridad, use LocalBroadcastManageraquí
Alexander Farber

0

Si usa finish()solo para evitar que una nueva aplicación se inicie en la pila (tarea) de su aplicación, puede usar la Intent.FLAG_ACTIVITY_NEW_TASKbandera al iniciar una nueva aplicación y no llamar finish()en absoluto. Según la documentación , esta es la bandera que se utilizará para implementar un comportamiento de estilo "lanzador".

// just add this line before you start an activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

0

Utilice estos métodos dentro de un Activity.

isDestroyed()

Agregado en Api 17
Devuelve verdadero si la llamada final a onDestroy () se ha realizado en la Actividad, por lo que esta instancia ahora está muerta.

isFinishing()

Agregado en Api 1
Verifique si esta actividad está en proceso de finalización, ya sea porque llamó a terminar () o alguien más solicitó que finalizara. Esto se usa a menudo en onPause () para determinar si la actividad simplemente está en pausa o terminando por completo.


De la documentación de Memory Leaks

Un error común con AsyncTaskes capturar una fuerte referencia al host Activity(o Fragment):

class MyActivity extends Activity {
  private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
    // Don't do this! Inner classes implicitly keep a pointer to their
    // parent, which in this case is the Activity!
  }
}

Esto es un problema porque AsyncTaskpuede sobrevivir fácilmente al padre Activity, por ejemplo, si ocurre un cambio de configuración mientras se ejecuta la tarea.

La forma correcta de hacer esto es hacer que su tarea sea una staticclase, que no capture al padre y mantenga una referencia débil al host Activity:

class MyActivity extends Activity {
  static class MyTask extends AsyncTask<Void, Void, Void> {
    // Weak references will still allow the Activity to be garbage-collected
    private final WeakReference<MyActivity> weakActivity;

    MyTask(MyActivity myActivity) {
      this.weakActivity = new WeakReference<>(myActivity);
    }

    @Override
    public Void doInBackground(Void... params) {
      // do async stuff here
    }

    @Override
    public void onPostExecute(Void result) {
      // Re-acquire a strong reference to the activity, and verify
      // that it still exists and is active.
      MyActivity activity = weakActivity.get();
      if (activity == null
          || activity.isFinishing()
          || activity.isDestroyed()) {
        // activity is no longer valid, don't do anything!
        return;
      }

      // The activity is still valid, do main-thread stuff here
    }
  }
}

0

Aquí hay una solución usando Applicationclass.

public class AppSingleton extends Application implements Application.ActivityLifecycleCallbacks {

private WeakReference<Context> foregroundActivity;


@Override
public void onActivityResumed(Activity activity) {
    foregroundActivity=new WeakReference<Context>(activity);
}

@Override
public void onActivityPaused(Activity activity) {
    String class_name_activity=activity.getClass().getCanonicalName();
    if (foregroundActivity != null && 
            foregroundActivity.get().getClass().getCanonicalName().equals(class_name_activity)) {
        foregroundActivity = null;
    }
}

//............................

public boolean isOnForeground(@NonNull Context activity_cntxt) {
    return isOnForeground(activity_cntxt.getClass().getCanonicalName());
}

public boolean isOnForeground(@NonNull String activity_canonical_name) {
    if (foregroundActivity != null && foregroundActivity.get() != null) {
        return foregroundActivity.get().getClass().getCanonicalName().equals(activity_canonical_name);
    }
    return false;
}
}

Simplemente puede usarlo como sigue,

((AppSingleton)context.getApplicationContext()).isOnForeground(context_activity);

Si tiene una referencia a la Actividad requerida o usa el nombre canónico de la Actividad, puede averiguar si está en primer plano o no. Esta solución puede no ser infalible. Por lo tanto, sus comentarios son bienvenidos.


0

No sé por qué nadie habló sobre sharedPreferences, para la Actividad A, estableciendo una SharedPreference como esa (por ejemplo, en onPause ()):

SharedPreferences pref = context.getSharedPreferences(SHARED_PREF, 0);
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean("is_activity_paused_a", true);
editor.commit();

Creo que esta es la forma confiable de rastrear la visibilidad de las actividades.


0

¿ Activity.onWindowFocusChanged(boolean hasFocus)Sería útil aquí? Eso, más una bandera de nivel de clase, algo como isFocusedeso onWindowFocusChanged, sería una manera fácil de saber en cualquier punto de su actividad si está enfocado o no. Al leer los documentos, parece que se establecería correctamente como "falso" en cualquier situación en la que la actividad no esté directamente en el "primer plano" físico, como si se muestra un cuadro de diálogo o se abre la bandeja de notificaciones.

Ejemplo:

boolean isFocused;
@Override
void onWindowFocusChanged (boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    isFocused = hasFocus;
}

void someMethod() {
    if (isFocused) {
        // The activity is the foremost object on the screen
    } else {
        // The activity is obscured or otherwise not visible
    }
}

0

Si está utilizando EventBus , es un método llamado hasSubscriberForEventque puede usarse para verificar si un Activityestá enfocado.


-3

Solía ​​hacer como

si la actividad no está en primer plano

getIntent ()

devolverá nulo. : = P

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.