Como verificar se a atividade está em primeiro plano ou em segundo plano visível?


104

Eu tenho uma tela inicial em um cronômetro. Meu problema é que antes de iniciar finish()minha atividade, preciso verificar se a próxima atividade foi iniciada porque uma caixa de diálogo do sistema aparece e eu só quero finish(); depois que o usuário selecionou uma opção da caixa de diálogo?

Sei que há muitas perguntas sobre como ver se sua atividade está em primeiro plano, mas não sei se isso permite caixas de diálogo no topo da atividade também.

Aqui está o problema, o vermelho é a minha atividade que está em segundo plano enquanto o diálogo está em primeiro plano:

o vermelho é a minha atividade que fica em segundo plano enquanto o diálogo está em primeiro plano

EDIT: Eu tentei apenas não usar, finish()mas minha atividade pode voltar para a pilha de aplicativos que estou tentando evitar.



Para esclarecer, você deseja iniciar um seletor de intent e esperar que seu aplicativo termine () até que o usuário toque em uma das opções? Parece que você precisa de Intent.createChooser () e startActivityForResult () seguido de finish () quando o resultado for recebido.
Alanv


ProcessLifecycleOwner é a solução mais recente
SR

Respostas:


189

Isso é o que é recomendado como a solução certa :

A solução certa (os créditos vão para Dan, CommonsWare e NeTeInStEiN) Rastreie a visibilidade de seu aplicativo por conta própria usando os métodos Activity.onPause, Activity.onResume. Armazene o status de "visibilidade" em alguma outra classe. Boas escolhas são sua própria implementação do aplicativo ou serviço (também existem algumas variações desta solução se você gostaria de verificar a visibilidade da atividade do serviço).

Exemplo de implementação de classe de aplicativo personalizada (observe o 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 sua classe de aplicativo em AndroidManifest.xml:

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

Adicione onPause e onResume a cada Activity no projeto (você pode criar um ancestral comum para suas Activities se desejar, mas se sua atividade já foi estendida de MapActivity / ListActivity etc., você ainda precisa escrever o seguinte manualmente) :

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

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

No seu finish()método, você deseja isActivityVisible()verificar se a atividade está visível ou não. Lá você também pode verificar se o usuário selecionou uma opção ou não. Continue quando ambas as condições forem atendidas.

A fonte também menciona duas soluções erradas ... portanto, evite fazer isso.

Fonte: stackoverflow


Há um pequeno momento entre o término e o início da atividade e eu
acho que

28
Isso não funciona de forma confiável. Você pode ter a seguinte situação: Retomar A Retomar B Pausa A. Agora, activityVisible é falso enquanto o aplicativo está visível. Talvez você use um contador de visibilidade: visibleCounter ++ em onResume e visibleCounter - em onPause.
Joris Weimar

4
Concordou com Joris Weimar que esta não é uma solução infalível. Um cenário é se o usuário puxado para baixo o painel de notificação, em seguida, nem o onPause, onStopnem o onResumeevento é chamado. Então o que você faz se nenhum desses eventos for disparado ?!

1
Na verdade, nenhuma das outras respostas funciona 100% também.

2
Se o aplicativo tiver mais de uma atividade, este esquema não funcionará. Substitua com contadores pelo menos
ruX

70

Se tiver como objetivo a API de nível 14 ou superior, pode-se 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
Você poderia simplesmente fazer isso nos retornos de chamada do ciclo de vida de atividade regular (onResume (), onStop ()) também, eu diria.
Daniel Wilson

5
@DanielWilson Acho que a questão não é construir um sistema para fazer algo onde já existe um. IMHO esta deve ser a resposta aceita.
Jeffrey Blattman

26

UPD : atualizado para o estado Lifecycle.State.RESUMED. Obrigado a @htafoya por isso.

Em 2019, com a ajuda da nova biblioteca de suporte 28+ou AndroidX, você pode simplesmente usar:

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

Você pode ler mais na documentação para entender o que aconteceu nos bastidores.


2
Na verdade não, provavelmente é melhor colocar activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) ou COMEÇAR. INITIALIZEDnão garante que esteja em primeiro plano.
htafoya

11

Activity :: hasWindowFocus () retorna o booleano que você precisa.

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

Aqui está um exemplo de aula para verificar a visibilidade de suas atividades de onde você estiver.

Lembre-se de que se você mostrar um caixa de diálogo , o resultado será falso, pois a caixa de diálogo terá o foco principal. Fora isso, é realmente útil e mais confiável do que as soluções sugeridas.


1
Obrigado por alterar a resposta do @Burak Day, na verdade é uma resposta agora
Nick

Isso não funciona, prefiro usar uma propriedade booleana na classe, definida como true em OnResume e definida como false em OnPause ();
Chandler,

@Chandler qual é o problema exato que você está tendo com este código? Também qual versão?
Burak Day

@Chandler também, e se você não tiver acesso aos métodos de ciclo de vida da atividade. Considere que você está apenas verificando a visibilidade da atividade em uma biblioteca.
Burak Day

O verdadeiro problema desta resposta é que ela NÃO funciona, activity.hasWindowFocus is true não pode garantir que a atividade esteja entre os estados onResume e onPause. Eu preferiria recomendar adicionar uma propriedade bool isResumed nessa atividade, definir manualmente o valor e adicionar um método get.
Chandler

10

Essa é exatamente a diferença entre os eventos onPausee onStopda atividade, conforme descrito na documentação da classe Activity .

Se bem entendi, o que você quer fazer é ligar finish()de sua atividade onStoppara encerrá-la. Veja a imagem em anexo do aplicativo Activity Lifecycle Demo . É assim que parece quando a Atividade B é iniciada a partir da Atividade A. A ordem dos eventos é de baixo para cima para que você possa ver que a Atividade A onStopé chamada depois que a Atividade B onResumejá foi chamada.

Demonstração do ciclo de vida da atividade

Caso seja exibida uma caixa de diálogo, sua atividade fica esmaecida no fundo e apenas onPauseé chamada.


7

Duas soluções possíveis:

1) Callbacks de ciclo de vida de atividades

Use um aplicativo que implemente ActivityLifecycleCallbacks e use-o para rastrear eventos de ciclo de vida de atividades em seu aplicativo. Observe que ActivityLifecycleCallbacks são para Android api> = 14. Para Android API anterior, você precisa implementá-lo sozinho em todas as suas atividades ;-)

Use o aplicativo quando precisar compartilhar / armazenar estados em atividades.

2) Verifique as informações do processo em execução

Você pode verificar o status de um processo em execução com esta classe RunningAppProcessInfo

Obtenha a lista de processos em execução com ActivityManager.getRunningAppProcesses () e filtre a lista de resultados para verificar o RunningAppProcessInfo desejado e verifique sua "importância"



3

Use o intervalo de tempo entre pausar e retomar do segundo plano para determinar se ele foi ativado do segundo plano

Em aplicativo personalizado

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

Na classe BaseActivity

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

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

3

Acho que tenho uma solução melhor. Porque você pode criar simplesmente MyApplication.activityResumed (); a cada atividade por uma extensão.

Em primeiro lugar, você deve criar (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;
}

Em seguida, você deve adicionar a classe Application ao AndroidManifest.xml

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

Em seguida, crie a classe ActivityBase

public class ActivityBase extends Activity {

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

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

Finalmente, ao criar uma nova Activity, você pode simplesmente estendê-la por ActivityBase em vez de Activity.

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

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

Para mim, é um método melhor porque você só precisa se lembrar de extend by ActivityBase. Além disso, você pode expandir sua função básica no futuro. No meu caso, adicionei receptores para meu serviço e alertas sobre a rede em uma classe.

Se você quiser verificar a visibilidade do seu aplicativo, basta ligar

MyApplication.isActivityVisible()

E se eu precisar que minhas atividades estendam a AppCombatActivity?
winklerrr

2

Isso pode ser alcançado de maneira eficiente, usando Application.ActivityLifecycleCallbacks

Por exemplo, vamos pegar o nome da classe Activity como ProfileActivity permite descobrir se ela está em primeiro plano ou em segundo plano

primeiro precisamos criar nossa classe de aplicativo estendendo a classe de aplicativo

que implementa

Application.ActivityLifecycleCallbacks

Vamos ser minha classe de aplicativo da seguinte maneira

Aula de aplicação

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

na classe acima, há um método de substituição onActivityResumed 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;
}

onde todas as instâncias de atividade que estão sendo exibidas na tela podem ser encontradas, basta verificar se Sua Atividade está na Tela ou não pelo método acima.

Registre sua classe de aplicativo em manifest.xml

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

Para verificar se a atividade do clima está em primeiro ou segundo plano, de acordo com a solução acima, chame o seguinte método nos lugares que você precisa verificar

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

1

Se você quiser saber se alguma atividade do seu aplicativo está visível na tela, pode fazer algo assim:

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

Basta criar um singleton desta classe e configurá-lo em sua instância do aplicativo como abaixo:

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

Então você pode usar o método isAnyActivityVisible () de sua instância MyAppActivityCallbacks em qualquer lugar!


0

você tentou não chamar finish e colocar "android: noHistory =" true "no manifesto? Isso impedirá que a atividade vá para a pilha.


0

Devo dizer que seu fluxo de trabalho não está no padrão Android. No Android, você não precisa da finish()sua atividade se quiser abrir outra atividade do Intent. Quanto à conveniência do usuário, o Android permite que o usuário use a tecla 'voltar' para voltar da atividade que você abriu em seu aplicativo.

Portanto, deixe o sistema interromper sua atividade e salvar tudo o que for necessário para quando sua atividade for chamada de volta.


3
A forma como as respostas do "buu não é o andróide" são cansativas e não respondem à pergunta feita em primeiro lugar. além disso, existem razões válidas para terminar (); - por exemplo, é concebível que voltar a ele uma vez que a ação tenha ocorrido não sirva a nenhum propósito. em outras palavras, você acha que eles colocam finish () lá para se divertir? permanecer na pilha é exatamente o que o questionador queria evitar
Lassi Kinnunen

A forma como as respostas do "buu não é o andróide" são cansativas e não respondem à pergunta feita em primeiro lugar. Seu elogio foi injusto. Embora eu tenha apontado que esse não era o estilo do Android, respondi após essa frase em vez de nada. Só não dei a resposta em código, pois isso era desnecessário. Portanto, foi injusto dizer que eu não respondi à pergunta em primeiro lugar.
Owen Zhao

0

Salve um sinalizador se você for pausado ou retomado. Se você voltou, significa que você está em primeiro 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

Uma solução possível pode ser definir um sinalizador enquanto mostra a caixa de diálogo do sistema e, em seguida, no método onStop do ciclo de vida da atividade, verificar o sinalizador, se verdadeiro, conclua a atividade.

Por exemplo, se a caixa de diálogo do sistema for acionada por algum clique de botão, o ouvinte onclick pode 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

    }
};

e na parada da atividade:

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

0

Por que não usar transmissões para isso? a segunda atividade (a que precisa ser ativada) pode enviar uma transmissão 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);

em seguida, escreva um receptor simples na atividade inicial:

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

e registre seu novo receptor no LocalBroadcastManager para ouvir a transmissão de sua segunda atividade:

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

NOTE que você pode usar uma constante ou um recurso de string para a string de "identificador de transmissão".


Para melhor segurança e eficiência do anúncio, use LocalBroadcastManageraqui
Alexander Farber

0

Se você usa finish()apenas para evitar que um novo aplicativo inicie na pilha (tarefa) de seu aplicativo, você pode usar Intent.FLAG_ACTIVITY_NEW_TASKsinalizar, ao iniciar um novo aplicativo e não chamar de finish()jeito nenhum. De acordo com a documentação , este é o sinalizador a ser usado para implementar um comportamento do estilo "lançador".

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

0

Use esses métodos dentro de um Activity.

isDestroyed()

Adicionado na Api 17
Retorna verdadeiro se a chamada final onDestroy () foi feita na Activity, então esta instância agora está morta.

isFinishing()

Adicionado na Api 1
Verifique se esta atividade está em processo de conclusão, seja porque você chamou terminar () nela ou outra pessoa solicitou que ela fosse finalizada. Isso é frequentemente usado em onPause () para determinar se a atividade está simplesmente pausando ou terminando completamente.


Da documentação de vazamentos de memória

Um erro comum com AsyncTaské capturar uma referência forte ao host Activity(ou 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!
  }
}

Isso é um problema porque AsyncTaskpode sobreviver facilmente ao pai Activity, por exemplo, se uma mudança de configuração acontecer enquanto a tarefa está em execução.

A maneira certa de fazer isso é transformar sua tarefa em uma staticclasse, que não capture o pai e mantenha uma referência fraca ao 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

Aqui está uma solução usando Applicationclasse.

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

Você pode simplesmente usá-lo como segue,

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

Se você tiver uma referência à atividade necessária ou usando o nome canônico da atividade, poderá descobrir se ela está em primeiro plano ou não. Esta solução pode não ser infalível. Portanto, seus comentários são muito bem-vindos.


0

Não sei por que ninguém falou sobre sharedPreferences, para Activity A, definir uma SharedPreference como essa (por exemplo em onPause ()):

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

Acho que essa é a maneira confiável de rastrear a visibilidade das atividades.


0

Seria Activity.onWindowFocusChanged(boolean hasFocus)útil aqui? Isso, mais um sinalizador de nível de classe, algo como isFocusedesse onWindowFocusChangedconjunto, seria uma maneira fácil de dizer em qualquer ponto de sua atividade se ela está focada ou não. Ao ler os documentos, parece que seria definido corretamente como "falso" em qualquer situação em que a atividade não estivesse diretamente no "primeiro plano" físico, como se uma caixa de diálogo estivesse sendo exibida ou a bandeja de notificação fosse puxada para baixo.

Exemplo:

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

Se você estiver usando EventBus , ele é um método chamado hasSubscriberForEventque pode ser usado para verificar se um Activityestá focado.


-3

Eu costumava fazer como,

se a atividade não estiver em primeiro plano

getIntent ()

retornará nulo. : = P

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.