que - Cómo detectar cuándo una aplicación de Android pasa al fondo y vuelve al primer plano
mantener aplicaciones en segundo plano android (30)
Estoy tratando de escribir una aplicación que hace algo específico cuando se vuelve a poner en primer plano después de cierto tiempo. ¿Hay alguna forma de detectar cuándo una aplicación se envía al fondo o se pone en primer plano?
2018: Android admite esto de forma nativa a través de los 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 está específicamente diseñado para hacer esto.
Se proporciona una muestra simple en esta respuesta, pero escribí una aplicación de muestra y una publicación de blog sobre ella.
Desde que escribí esto en 2014, surgieron diferentes soluciones. Algunos funcionaron, se pensó que algunos funcionaban , pero tenían fallas (¡incluso las mías!) 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 que sea el caso; Mejor aún, trate de entender lo que hace y por qué lo hace.
La clase de MemoryBoss
nunca fue utilizada por mí como está escrita aquí, fue solo un pedazo de pseudo código que funcionó.
A menos que haya una razón válida para no usar los nuevos componentes de la arquitectura (y hay algunos, especialmente si apuntas a apis super antiguas), entonces adelante y úselos. Están lejos de ser perfectos, pero tampoco lo fueron ComponentCallbacks2
.
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 indica que no debe verificar los valores exactos . Esto está bien para la mayoría de los casos, pero tenga en cuenta que si solo se preocupa por hacer algo cuando la aplicación pasó a segundo plano, tendrá que usar == y también combinarlo con otra solución (como devoluciones de llamada del ciclo de vida de la actividad), o Puede que no obtenga el efecto deseado. El ejemplo (y esto me sucedió a mí) es que si quiere bloquear su aplicación con una pantalla de contraseña cuando pasa a segundo plano (como 1Password si está familiarizado con ella), puede bloquear su aplicación accidentalmente si se queda sin fondos. en la memoria y de repente están probando >= TRIM_MEMORY
, porque Android activará una llamada de LOW MEMORY
y eso es más alto que el tuyo. Así que ten cuidado cómo / qué pruebas.
Además, algunas personas han preguntado sobre cómo detectar cuándo regresas.
La forma más sencilla en que puedo pensar se explica a continuación, pero como algunas personas no están familiarizadas con esto, estoy agregando un pseudo código aquí. Suponiendo que tenga las clases YourApplication
y MemoryBoss
, en su class BaseActivity extends Activity
(tendrá que crear una si no tiene una).
@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 diálogos pueden pausar una actividad, así que apuesto a que no quiere que su aplicación piense "se fue al fondo" si todo lo que hizo fue mostrar un cuadro de diálogo a 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 se va a otra actividad, la nueva (que también extends BaseActivity
) informará que wasInBackground
es false
por lo que no ejecutará el código, hasta que se onMemoryTrimmed
y el indicador se establece en true de nuevo .
Espero que ayude.
ACTUALIZACIÓN / NOTAS (abril de 2015) : antes de revisar Copiar y pegar en este código, tenga en cuenta que he encontrado un par de casos en los que puede no ser 100% confiable y deben combinarse con otros métodos para lograr los mejores resultados. En particular, hay dos casos conocidos en los que no se garantiza la onTrimMemory
la onTrimMemory
llamada onTrimMemory
:
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 todavía está "en ejecución" aunque esté cubierta.
Si su dispositivo tiene una memoria relativamente baja (y bajo carga de memoria), el sistema operativo parece ignorar esta llamada e ir directamente a niveles más críticos.
Ahora, dependiendo de lo importante que es para usted saber cuándo su aplicación pasó a segundo plano, es posible que necesite o no que extienda esta solución junto con 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 no tiene una IU más 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 dos). Esta devolución de llamada solo está disponible en el Nivel API 14 (Ice Cream Sandwich) y superior.
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 quizás desee liberar algunos recursos ya que su aplicación ya no es visible.
Para citar los documentos oficiales:
Nivel para onTrimMemory (int): el proceso mostraba una interfaz de usuario y ya no lo hace. Las asignaciones grandes con la interfaz de usuario deben liberarse en este punto para permitir que la memoria se administre mejor.
Por supuesto, debe implementar esto para hacer realmente lo que dice (purgue la memoria que no se ha usado en cierto tiempo, borre 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 le está diciendo: HEY, ¡su aplicación fue al fondo!
Que es exactamente lo que querías saber en primer lugar.
¿Cómo determinas cuándo volviste?
Bueno, eso es fácil, estoy seguro de que tienes una "BaseActivity" para que puedas usar tu onResume () para marcar el hecho de que estás de regreso. Debido a que la única vez que dirá que no regresó es cuando realmente recibe una llamada al método onTrimMemory
anterior.
Funciona. No obtienes falsos positivos. Si una actividad se está reanudando, estás de vuelta, el 100% de las veces. Si el usuario vuelve a la parte posterior nuevamente, recibirá otra llamada onTrimMemory()
.
Debe suscribir sus actividades (o, mejor aún, una clase personalizada).
La forma más fácil de garantizar que siempre recibas 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 creas una Interface
, podrías agregar otra else
a eso if
implementas ComponentCallbacks
(sin los 2) que se usan en cualquier cosa debajo de API 14. Esa devolución de llamada solo tiene el método onLowMemory()
y no se llama cuando vas al fondo , pero Deberías usarlo para recortar la memoria.
Ahora lanza tu aplicación y presiona home. onTrimMemory(final int level)
debe llamar a su método onTrimMemory(final int level)
(sugerencia: agregar registro).
El último paso es anular el registro de la devolución de llamada. Probablemente el mejor lugar sea el método onTerminate()
de tu 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 de manera segura, ya que su proceso está muriendo a nivel de sistema operativo de todos modos.
Si decide cancelar el registro en algún momento (si, por ejemplo, proporciona un mecanismo de cierre para que su aplicación se limpie y muera), puede hacer lo siguiente:
unregisterComponentCallbacks(mMemoryBoss);
Y eso es.
Así es como he logrado 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 "desarrollada" o no.
Primero, he usado una instancia de android.app.Application (llamémosla MyApplication) que tiene un Timer, una TimerTask, una constante para representar el número máximo de milisegundos que la transición de una actividad a otra podría tomar 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 de los eventos onResume () y onPause () de todas las actividades o, preferiblemente, en una actividad base de la que se hereden todas sus actividades concretas:
@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 esté navegando entre las actividades de su aplicación, el onPause () de la actividad de salida inicia el temporizador, pero casi inmediatamente la nueva actividad que se está ingresando cancela el temporizador antes de que pueda alcanzar el tiempo máximo de transición. Y así fue que el Fondo sería falso .
Por otro lado, cuando una Actividad llega al primer plano desde el Iniciador , la activación del dispositivo, la finalización de una llamada, etc., lo más probable es que la tarea del temporizador se haya ejecutado antes de este evento, y por lo tanto wasInBackground se configuró en verdadero .
Basado en la respuesta de Martín Marconcinis (¡gracias!) Finalmente encontré una solución confiable (y muy simple).
public class MyApp extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
}
}
Luego agregue esto a su onCreate () de su clase de aplicación
public class AppLifecycleListener implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onMoveToForeground() {
// app moved to foreground
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onMoveToBackground() {
// app moved to background
}
}
// register observer
ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleListener());
Considere el uso de onUserLeaveHint. Solo se llamará cuando tu aplicación pase al fondo. onPause tendrá casos de esquina para manejar, ya que puede ser llamado por otras razones; 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 aún estén en su aplicación; el seguimiento de lo que está ocurriendo dará lugar a errores cuando, en su lugar, simplemente puede utilizar la devolución de llamada onUserLeaveHint que hace lo que está pidiendo.
Cuando se llama a UserLeaveHint, puede establecer un indicador boolean inBackground en true. Cuando se llama a onResume, solo asuma que regresó al primer plano si la bandera inBackground está establecida. Esto se debe a que también se llamará a onResume en su actividad principal si el usuario estaba solo en su menú de configuración y nunca abandonó la aplicación.
Recuerde que si el usuario pulsa el botón de inicio mientras está en la pantalla de configuración, se llamará a onUserLeaveHint en su actividad de configuración, y cuando vuelva a activarse, se llamará al currículum en su actividad de configuración. Si solo tiene este código de detección en su actividad principal, 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 ponga su código común en ella. Entonces cada actividad que tengas 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 {
...
}
Crear una clase que amplíe la Application
. Luego en él podemos usar su método de anulación, onTrimMemory()
.
Para detectar si la aplicación fue al fondo, usaremos:
@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
}
}
Si su aplicación consta de múltiples actividades y / o actividades apiladas como un widget de la barra de pestañas, la anulación de onPause () y onResume () no funcionará. Es decir, al iniciar una nueva actividad, las actividades actuales se detendrán antes de que se cree la nueva. Lo mismo se aplica cuando se termina (usando el botón "atrás") una actividad.
He encontrado dos métodos que parecen funcionar como se desea.
El primero requiere el permiso GET_TASKS y consiste en un método simple que verifica si la actividad de mayor 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 de 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 las siguientes condiciones son verdaderas:
- La actividad que se está pausando no se está terminando (se usó el botón "atrás"). Esto se puede hacer usando el método activity.isFinishing ()
- No se está iniciando una nueva actividad (mismo nombre del 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 se ha resignado a un segundo plano, es fácil de detectar cuando se vuelve a poner en primer plano también.
Utilizamos este método. Parece demasiado simple para trabajar, pero fue bien probado en nuestra aplicación y, de hecho, funciona sorprendentemente bien en todos los casos, incluido ir a la pantalla de inicio con el botón "Inicio", con el botón "Volver" o después del bloqueo de pantalla. Darle una oportunidad.
La idea es que, cuando está 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 a 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
}
}
}
Edición: según los comentarios, también cambiamos a onStart () en versiones posteriores del código. Además, estoy agregando súper llamadas, que faltaban en mi publicación inicial, porque era más un concepto que un código de trabajo.
¿Qué tal esta solución?
public class BaseActivity extends Activity
{
static String currentAct = "";
@Override
protected void onStart()
{
super.onStart();
if (currentAct.equals(""))
Toast.makeText(this, "Start", Toast.LENGTH_LONG).show();
currentAct = getLocalClassName();
}
@Override
protected void onStop()
{
super.onStop();
if (currentAct.equals(getLocalClassName()))
{
currentAct = "";
Toast.makeText(this, "Stop", Toast.LENGTH_LONG).show();
}
}
}
Toda actividad necesita extender BaseActivity.
Cuando una actividad llama a otro (A-> B), currentAct no es igual a getLocalClassName () porque el onStart () de la segunda actividad (B) se llama antes del onStop () del primero (A) ( https://developer.android.com/guide/components/activities.html#CoordinatingActivities ).
Cuando el usuario presiona el botón de inicio o cambia de aplicación, solo se abrirá onStop () y luego currentAct es igual a getLocalClassName ().
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 se crean, destruyen, actividades, etc. Puede llamar a getComponentName () para la actividad.
ProcessLifecycleOwner
parece ser una solución prometedora.
ProcessLifecycleOwner
ON_RESUME
eventosON_START
,ON_RESUME
, como una primera actividad a través de estos eventos.ON_PAUSE
,ON_STOP
se enviarán con un retraso después de que una última actividad haya pasado a través de ellos. Este retraso es lo suficientemente largo como para garantizar queProcessLifecycleOwner
no envíe ningún evento si las actividades se destruyen y se recrean debido a un cambio de configuración.
Una implementación puede ser tan simple como
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;
}
}
}
Según el código fuente, el valor actual del retardo es de 700ms
.
Edición: los nuevos componentes de la arquitectura trajeron algo prometedor: ProcessLifecycleOwner , vea la respuesta de @ vokilam
La solución real según una charla de Google I / O :
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(AppLifecycleTracker())
}
}
class AppLifecycleTracker : Application.ActivityLifecycleCallbacks {
private var numStarted = 0
override fun onActivityStarted(activity: Activity?) {
if (numStarted == 0) {
// app went to foreground
}
numStarted++
}
override fun onActivityStopped(activity: Activity?) {
numStarted--
if (numStarted == 0) {
// app went to background
}
}
}
Sí. Sé que es difícil creer que esta solución simple funcione ya que tenemos muchas soluciones extrañas aquí.
Pero hay esperanza.
Al usar el código de abajo, puedo obtener mi aplicación en primer plano o en segundo plano.
Para más detalles sobre su funcionamiento, texto fuerte, haga clic here
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Context context;
private Toast toast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
}
private void showToast(String message) {
//If toast is already showing cancel it
if (toast != null) {
toast.cancel();
}
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
toast.show();
}
@Override
protected void onStart() {
super.onStart();
showToast("App In Foreground");
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
showToast("App In Background");
}
}
}
Crear una clase con el nombre MyApp como abajo:
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, en cualquier lugar que desee (mejor iniciación de la primera actividad 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 el registro status : we are out
y cuando ingresamos en la aplicación, obtenemos el registrostatus : we are out
En su aplicación, agregue la devolución de llamada y verifique la actividad de la raíz de la siguiente 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();
}
}
});
}
Los onPause()
y onResume()
cuando la aplicación se pone en segundo plano y de nuevo en primer plano. Sin embargo, también se les llama cuando la aplicación se inicia por primera vez y antes de que se cierre. Puedes leer más en la Activity .
No hay ningún enfoque directo para obtener el estado de la aplicación en segundo plano o en primer plano, pero incluso me he enfrentado a este problema y he encontrado la solución con onWindowFocusChanged
y onStop
.
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 al primer plano sin getRunningTasks o getRunningAppProcesses .
Sé que es un poco tarde, pero creo que todas estas respuestas tienen algunos problemas mientras lo hice como a continuación y funciona perfectamente.
crear una actividad llamada del ciclo de vida de esta manera:
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ístrese en su clase de aplicación como a continuación:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
Aquí está mi solución. Simplemente registre este ActivityLifecycleCallbacks en su clase de aplicación principal. En los comentarios, menciono un perfil de usuario. Caso de borde de actividad. Esa actividad es simplemente una con bordes transparentes.
/**
* This class used Activity lifecycle callbacks to determine when the application goes to the
* background as well as when it is brought to the foreground.
*/
public class Foreground implements Application.ActivityLifecycleCallbacks
{
/**
* How long to wait before checking onStart()/onStop() count to determine if the app has been
* backgrounded.
*/
public static final long BACKGROUND_CHECK_DELAY_MS = 500;
private static Foreground sInstance;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private boolean mIsForeground = false;
private int mCount;
public static void init(final Application application)
{
if (sInstance == null)
{
sInstance = new Foreground();
application.registerActivityLifecycleCallbacks(sInstance);
}
}
public static Foreground getInstance()
{
return sInstance;
}
public boolean isForeground()
{
return mIsForeground;
}
public boolean isBackground()
{
return !mIsForeground;
}
@Override
public void onActivityStarted(final Activity activity)
{
mCount++;
// Remove posted Runnables so any Meteor disconnect is cancelled if the user comes back to
// the app before it runs.
mMainThreadHandler.removeCallbacksAndMessages(null);
if (!mIsForeground)
{
mIsForeground = true;
}
}
@Override
public void onActivityStopped(final Activity activity)
{
mCount--;
// A transparent Activity like community user profile won''t stop the Activity that launched
// it. If you launch another Activity from the user profile or hit the Android home button,
// there are two onStops(). One for the user profile and one for its parent. Remove any
// posted Runnables so we don''t get two session ended events.
mMainThreadHandler.removeCallbacksAndMessages(null);
mMainThreadHandler.postDelayed(new Runnable()
{
@Override
public void run()
{
if (mCount == 0)
{
mIsForeground = false;
}
}
}, BACKGROUND_CHECK_DELAY_MS);
}
@Override
public void onActivityCreated(final Activity activity, final Bundle savedInstanceState)
{
}
@Override
public void onActivityResumed(final Activity activity)
{
}
@Override
public void onActivityPaused(final Activity activity)
{
}
@Override
public void onActivitySaveInstanceState(final Activity activity, final Bundle outState)
{
}
@Override
public void onActivityDestroyed(final Activity activity)
{
}
}
Como no encontré ningún enfoque, que también maneje la rotación sin verificar las marcas de tiempo, pensé que también comparto cómo lo hacemos ahora en nuestra aplicación. La única adición a esta respuesta https://.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 tenemos el currículum primero:
// ActivityLifecycleCallbacks
override fun onActivityResumed(activity: Activity?) {
mResumed++
if (mAppIsInBackground) {
// !!! App came from background !!! Insert code
mAppIsInBackground = false
}
mOrientationWasChanged = false
}
Y onActivityStopped:
override fun onActivityStopped(activity: Activity?) {
if (mResumed == mPaused && !mOrientationWasChanged) {
// !!! App moved to background !!! Insert code
mAppIsInBackground = true
}
Y luego, aquí viene la adición: Verificación de 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 :)
Edición 2: Lo que he escrito a continuación no funcionará. Google ha rechazado una aplicación que incluye una llamada a ActivityManager.getRunningTasks (). De developer.android.com/reference/android/app/… , es evidente que esta API es solo para fines de depuración y desarrollo. Estaré actualizando esta publicación tan pronto como tenga tiempo de actualizar el proyecto de GitHub a continuación con un nuevo esquema que usa temporizadores y es casi tan bueno.
Edición 1: he escrito una publicación de blog y he creado un repositorio de GitHub simple para que esto sea realmente fácil.
La respuesta aceptada y mejor calificada no es realmente el mejor enfoque. La implementación de isApplicationBroughtToBackground () con la mejor calificación de la respuesta no controla 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.
Llámalo en onPause (), y te dirá si tu aplicación está entrando en segundo plano porque se inició otra aplicación o si el usuario presionó 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;
}
Encontré un buen método para detectar la aplicación ya sea para ingresar en primer plano o en segundo plano. Aquí está mi code . 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();
}
Esta es la versión modificada de la respuesta de @ d60402: https://.com/a/15573121/4747587
Haz todo lo mencionado allí. Pero en lugar de tener Base Activity
y hacer eso como padre para cada actividad y para anular la onResume()
y onPause
, haga lo siguiente:
En su clase de aplicación, agregue la línea:
registerActivityLifecycleCallbacks (Application.ActivityLifecycleCallbacks callback);
Esto callback
tiene todos los métodos del ciclo de vida de la actividad y ahora puede anular onActivityResumed()
y onActivityPaused()
.
Eche un vistazo a este Gist: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
Esta es mi solución https://github.com/doridori/AndroidUtils/blob/master/App/src/main/java/com/doridori/lib/app/ActivityCounter.java
Básicamente, se trata de contar los métodos del ciclo de vida de todas las actividades con un temporizador para detectar casos en los que no hay actividad actualmente en primer plano, pero la aplicación está (es decir, en rotación)
Esta parece ser una de las preguntas más complicadas en Android desde que (hasta este momento) Android no tiene equivalentes de iOS applicationDidEnterBackground()
o applicationWillEnterForeground()
devoluciones de llamada. Usé una biblioteca de AppState que fue @jenzz 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 al fondo y vuelve a estar en primer plano.
Resultó que esto era exactamente lo que necesitaba, especialmente porque mi aplicación tenía múltiples actividades, así que simplemente verificar onStart()
o realizar onStop()
una actividad no iba a cortarla.
Primero agregué estas dependencias a gradle:
dependencies {
compile ''com.jenzz.appstate:appstate:3.0.1''
compile ''com.jenzz.appstate:adapter-rxjava2:3.0.1''
}
Entonces 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 darse de baja para evitar pérdidas de memoria. Una vez más más información en la página github .
Estaba usando esto con Google Analytics EasyTracker, y funcionó. Podría ampliarse 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) {
}
});
}
}
Esto es bastante fácil con ProcessLifecycleOwner
Añade 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 tu actividad base:
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get()
.lifecycle
.addObserver(
ForegroundBackgroundListener()
.also { appObserver = it })
}
Consulte mi artículo sobre este tema: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48
He creado un proyecto en la app-foreground-background-listen Github app-foreground-background-listen
Cree una BaseActivity para todas las actividades 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 súper clase de toda su actividad, ya que MainActivity extiende BaseActivity y se llamará onAppStart cuando inicie su aplicación y se llamará a onAppPause () cuando la aplicación pase al fondo desde cualquier pantalla.
Lo que hice es asegurarme de que todas las actividades en la aplicación se startActivityForResult
inicien y luego se verifique si se llamó a onActivityResult antes de onResume. Si no fue así, significa que acabamos de regresar de algún lugar fuera de nuestra aplicación.
boolean onActivityResultCalledBeforeOnResume;
@Override
public void startActivity(Intent intent) {
startActivityForResult(intent, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
onActivityResultCalledBeforeOnResume = true;
}
@Override
protected void onResume() {
super.onResume();
if (!onActivityResultCalledBeforeOnResume) {
// here, app was brought to foreground
}
onActivityResultCalledBeforeOnResume = false;
}
Mi aplicación debe "reiniciarse" después de regresar del fondo: mostrar una serie de actividades, de acuerdo con las solicitudes de los clientes. Después de una extensa búsqueda sobre cómo administrar las transiciones de fondo / primer plano (tratadas de manera muy diferente entre iOS y Android), crucé esta pregunta. He encontrado ayuda muy útil aquí, especialmente de la respuesta más votada y la marcada como correcta. Sin embargo, simplemente vuelva a establecer la actividad raíz CADA VEZ que la aplicación entre en primer plano parezca demasiado molesta, cuando piensa en UX. La solución que funcionó para mí, y la que creo que es la más adecuada, basada en la funcionalidad de las aplicaciones de Youtube y Twitter, fue combinar las respuestas de @GirishNair y @ d60402: llamar al temporizador cuando la aplicación recorte la memoria, como sigue:
@Override
public void onTrimMemory(int level) {
if (stateOfLifeCycle.equals("Stop")) {
startActivityTransitionTimer();
}
super.onTrimMemory(level);
}
El límite de mi temporizador se establece en 30 segundos. Estoy pensando en aumentar esto un poco.
private final long MAX_ACTIVITY_TRANSITION_TIME = 30000;
Y cuando la aplicación entra en primer plano, se reinicia o se destruye la aplicación, llame al método para cancelar el temporizador.
En la extensión de la aplicación:
@Override
public void onActivityCreated(Activity activity, Bundle arg1) {
stopActivityTransitionTimer();
stateOfLifeCycle = "Create";
}
@Override
public void onActivityDestroyed(Activity activity) {
stopActivityTransitionTimer();
stateOfLifeCycle = "Destroy";
}
Sobre la actividad (preferiblemente sobre una actividad base, heredada por los demás):
@Override
protected void onStart() {
super.onStart();
if (App.wasInBackground) {
stopActivityTransitionTimer();
}
}
En mi caso, cuando la aplicación se pone en primer plano después del tiempo máximo, se crea una nueva tarea, por lo que se llama a stopActivityTransitionTimer () en onActivityCreated () o onActivityDestroyed (), en la clase de extensión de la aplicación: no es necesario llamar al método en una actividad . Espero eso ayude.
Mi solución se inspiró en la respuesta de @ d60402 y también se basa en una ventana de tiempo, pero no utiliza 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;
}