Estou tentando escrever um aplicativo que faz algo específico quando é trazido de volta ao primeiro plano após algum tempo. Existe uma maneira de detectar quando um aplicativo é enviado para segundo plano ou trazido para o primeiro plano?
Estou tentando escrever um aplicativo que faz algo específico quando é trazido de volta ao primeiro plano após algum tempo. Existe uma maneira de detectar quando um aplicativo é enviado para segundo plano ou trazido para o primeiro plano?
Respostas:
Os métodos onPause()
e onResume()
são chamados quando o aplicativo é trazido para segundo plano e novamente para o primeiro plano. No entanto, eles também são chamados quando o aplicativo é iniciado pela primeira vez e antes de ser morto. Você pode ler mais em Atividade .
Não existe uma abordagem direta para obter o status do aplicativo em segundo plano ou em primeiro plano, mas mesmo eu já enfrentei esse problema e encontrei a solução com onWindowFocusChanged
e onStop
.
Para obter mais detalhes, consulte aqui Android: solução para detectar quando um aplicativo Android entra em segundo plano e volta ao primeiro plano sem getRunningTasks ou getRunningAppProcesses .
Março de 2018 ATUALIZAÇÃO : Agora existe uma solução melhor. Consulte ProcessLifecycleOwner . Você precisará usar os novos componentes da arquitetura 1.1.0 (mais recentes no momento), mas ele foi projetado especificamente para isso.
Há uma amostra simples fornecida nesta resposta, mas eu escrevi uma amostra de aplicativo e uma postagem no blog sobre ele.
Desde que escrevi isso em 2014, surgiram diferentes soluções. Alguns funcionaram, outros foram pensados para estar funcionando , mas tinham falhas (incluindo a minha!) E nós, como comunidade (Android), aprendemos a conviver com as consequências e escrevemos soluções alternativas para os casos especiais.
Nunca assuma que um único trecho de código é a solução que você está procurando; é improvável que seja o caso; melhor ainda, tente entender o que faz e por que faz.
A MemoryBoss
aula nunca foi realmente usada por mim, como foi escrita aqui, era apenas um pedaço de pseudo-código que funcionava.
A menos que haja uma razão válida para você não usar os novos componentes da arquitetura (e existem alguns, especialmente se você direcionar APIs super antigas), vá em frente e use-as. Eles estão longe de serem perfeitos, mas nem eram ComponentCallbacks2
.
ATUALIZAÇÃO / NOTAS (novembro de 2015) : As pessoas têm feito dois comentários, o primeiro é que >=
deve ser usado em vez de ==
porque a documentação afirma que você não deve verificar os valores exatos . Isso é bom para a maioria dos casos, mas tenha em mente que se você única preocupam com fazer algo quando o aplicativo foi para o fundo, você terá que usar == e também combiná-lo com outra solução (como chamadas de retorno Atividade do ciclo de vida), ou você pode não obter o efeito desejado. O exemplo (e isso aconteceu comigo) é que, se você deseja bloquearseu aplicativo com uma tela de senha quando estiver em segundo plano (como 1Password, se você estiver familiarizado com ele), poderá bloquear acidentalmente seu aplicativo se estiver com pouca memória e estiver testando repentinamente >= TRIM_MEMORY
, porque o Android acionará uma LOW MEMORY
chamada e isso é mais alto que o seu. Portanto, tenha cuidado como / o que você testa.
Além disso, algumas pessoas perguntaram sobre como detectar quando você volta.
A maneira mais simples de pensar é explicada abaixo, mas como algumas pessoas não estão familiarizadas com isso, estou adicionando algum pseudo-código aqui. Supondo que você tenha YourApplication
e as MemoryBoss
classes no seu class BaseActivity extends Activity
(você precisará criar uma se não tiver uma).
@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);
}
}
Eu recomendo o onStart porque o Dialogs pode pausar uma atividade, então aposto que você não quer que seu aplicativo pense "foi para segundo plano" se tudo o que você fez foi exibir uma caixa de diálogo em tela cheia, mas sua milhagem pode variar.
E isso é tudo. O código no bloco if vai executados apenas uma vez , mesmo que você vá para outra atividade, o novo (que também extends BaseActivity
) irá relatar wasInBackground
é false
que ele não vai executar o código, até que onMemoryTrimmed
é chamado ea bandeira é definido como verdadeiro novamente .
Espero que ajude.
ATUALIZAÇÃO / NOTAS (abril de 2015) : Antes de você copiar e colar este código, observe que encontrei algumas instâncias em que ele pode não ser 100% confiável e deve ser combinado com outros métodos para obter os melhores resultados. Notavelmente, existem duas instâncias conhecidas em que onTrimMemory
não é garantido que a chamada de retorno seja executada:
Se o telefone bloquear a tela enquanto o aplicativo estiver visível (digamos que o dispositivo bloqueie após nn minutos), esse retorno de chamada não será chamado (ou nem sempre) porque a tela de bloqueio está na parte superior, mas o aplicativo ainda está "em execução", embora coberto.
Se o seu dispositivo estiver com pouca memória (e com pouca carga), o sistema operacional parece ignorar esta chamada e passar diretamente para os níveis mais críticos.
Agora, dependendo da importância de você saber quando o aplicativo foi para o segundo plano, você pode ou não precisar estender essa solução, além de acompanhar o ciclo de vida da atividade e outros enfeites.
Lembre-se do exposto acima e tenha uma boa equipe de controle de qualidade;)
FIM DA ATUALIZAÇÃO
Pode ser tarde, mas há um método confiável no Ice Cream Sandwich (API 14) e acima .
Acontece que quando o aplicativo não tem mais interface do usuário visível, um retorno de chamada é acionado. O retorno de chamada, que você pode implementar em uma classe personalizada, é chamado ComponentCallbacks2 (sim, com dois). Esse retorno de chamada está disponível apenas na API nível 14 (sanduíche de sorvete) e acima.
Você basicamente recebe uma chamada para o método:
public abstract void onTrimMemory (int level)
O nível é 20 ou mais específico
public static final int TRIM_MEMORY_UI_HIDDEN
Venho testando isso e sempre funciona, porque o nível 20 é apenas uma "sugestão" de que você pode querer liberar alguns recursos, pois seu aplicativo não está mais visível.
Para citar os documentos oficiais:
Nível para onTrimMemory (int): o processo estava mostrando uma interface com o usuário e não está mais fazendo isso. Alocações grandes com a interface do usuário devem ser liberadas neste momento para permitir que a memória seja melhor gerenciada.
Obviamente, você deve implementar isso para realmente fazer o que diz (limpar a memória que não foi usada em um determinado período de tempo, limpar algumas coleções que não foram utilizadas, etc. As possibilidades são infinitas (consulte os documentos oficiais para obter outras informações mais detalhadas) níveis críticos ).
Mas o mais interessante é que o sistema operacional está lhe dizendo: Ei, seu aplicativo foi para o fundo!
Qual é exatamente o que você queria saber em primeiro lugar.
Como você determina quando voltou?
Bem, isso é fácil, tenho certeza que você tem uma "BaseActivity" para poder usar seu onResume () para sinalizar o fato de que você voltou. Porque o único momento em que você estará dizendo que não voltou é quando realmente recebe uma chamada para o onTrimMemory
método acima .
Funciona. Você não recebe falsos positivos. Se uma atividade está sendo retomada, você volta 100% das vezes. Se o usuário voltar atrás, você recebe outra onTrimMemory()
chamada.
Você precisa assinar suas atividades (ou, melhor ainda, uma classe personalizada).
A maneira mais fácil de garantir que você sempre receba isso é criar uma classe simples 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 isso, na implementação de seu aplicativo ( você tem um, certo? ), Faça 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);
}
}
Se você criar um, Interface
poderá adicionar um else
a isso if
e implementar ComponentCallbacks
(sem o 2) usado em qualquer coisa abaixo da API 14. Esse retorno de chamada possui apenas o onLowMemory()
método e não é chamado quando você passa para o segundo plano , mas deve usá-lo para aparar a memória .
Agora inicie seu aplicativo e pressione home. Seu onTrimMemory(final int level)
método deve ser chamado (dica: adicionar log).
A última etapa é cancelar o registro do retorno de chamada. Provavelmente, o melhor lugar é o onTerminate()
método do seu aplicativo, mas esse método não é chamado em um 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. */
Portanto, a menos que você realmente tenha uma situação em que não deseja mais se registrar, é possível ignorá-la com segurança, pois seu processo está acabando no nível do sistema operacional.
Se você decidir cancelar o registro em algum momento (se você, por exemplo, fornecer um mecanismo de desligamento para que seu aplicativo limpe e morra), você pode:
unregisterComponentCallbacks(mMemoryBoss);
E é isso.
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
que evita o problema na sua atualização, ponto 2. Em relação ao ponto 1, não é uma preocupação para mim, já que o aplicativo realmente não foi para o segundo plano, então é assim que ele deve funcionar.
Aqui está como eu consegui resolver isso. Ele trabalha com a premissa de que o uso de uma referência de tempo entre transições de atividade provavelmente fornecerá evidência adequada de que um aplicativo foi "em segundo plano" ou não.
Primeiro, usei uma instância android.app.Application (vamos chamar de MyApplication) que possui um Timer, um TimerTask, uma constante para representar o número máximo de milissegundos que a transição de uma atividade para outra poderia levar razoavelmente (eu fui com um valor de 2s) e um booleano para indicar se o aplicativo estava ou não "em 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;
...
O aplicativo também fornece dois métodos para iniciar e parar o cronômetro / tarefa:
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;
}
A última parte desta solução é adicionar uma chamada a cada um desses métodos a partir dos eventos onResume () e onPause () de todas as atividades ou, de preferência, em uma atividade base da qual todas as suas atividades concretas herdam:
@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();
}
Portanto, no caso em que o usuário está simplesmente navegando entre as atividades do seu aplicativo, a onPause () da atividade de partida inicia o cronômetro, mas quase imediatamente a nova atividade inserida cancela o cronômetro antes que ele atinja o tempo máximo de transição. E o wasInBackground também seria falso .
Por outro lado, quando uma Atividade chega em primeiro plano a partir do Iniciador, a ativação do dispositivo, a ligação telefônica final, etc., é mais provável que a tarefa do timer seja executada antes desse evento e, portanto, wasInBackground foi definido como verdadeiro .
Edit: os novos componentes da arquitetura trouxeram algo promissor: ProcessLifecycleOwner , veja a resposta de @ vokilam
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(AppLifecycleTracker())
}
}
class AppLifecycleTracker : Application.ActivityLifecycleCallbacks {
private var numStarted = 0
override fun onActivityStarted(activity: Activity?) {
if (numStarted == 0) {
// app went to foreground
}
numStarted++
}
override fun onActivityStopped(activity: Activity?) {
numStarted--
if (numStarted == 0) {
// app went to background
}
}
}
Sim. Sei que é difícil acreditar que essa solução simples funcione, pois temos muitas soluções estranhas aqui.
Mas há esperança.
ProcessLifecycleOwner
parece ser uma solução promissora também.
ProcessLifecycleOwner enviará
ON_START
,ON_RESUME
eventos, como primeiro move actividade através desses eventos.ON_PAUSE
,ON_STOP
, Eventos serão enviados com um atraso depois de uma última atividade passou por eles. Esse atraso é longo o suficiente para garantir queProcessLifecycleOwner
não enviará nenhum evento se as atividades forem destruídas e recriadas devido a uma alteração na configuração.
Uma implementação pode ser tão simples quanto
class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() { // app moved to foreground
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() { // app moved to background
}
}
// register observer
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
De acordo com o código fonte, o valor atual do atraso é 700ms
.
O uso desse recurso também requer dependencies
:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
implementation "android.arch.lifecycle:extensions:1.0.0"
e annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
do repositório do Google (ou seja google()
)
Com base na resposta de Martín Marconcinis (obrigado!), Finalmente encontrei uma solução confiável (e muito simples).
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;
}
}
}
Em seguida, adicione isso ao seu onCreate () da sua classe Application
public class MyApp extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
}
}
Nós usamos esse método. Parece muito simples de trabalhar, mas foi bem testado em nosso aplicativo e, de fato, funciona surpreendentemente bem em todos os casos, incluindo ir para a tela inicial pelo botão "home", pelo botão "return" ou após o bloqueio da tela. De uma chance.
A ideia é que, quando em primeiro plano, o Android sempre inicia uma nova atividade antes de parar a anterior. Isso não é garantido, mas é assim que funciona. BTW, Flurry parece usar a mesma lógica (apenas um palpite, eu não verifiquei isso, mas é viciado nos mesmos eventos).
public abstract class BaseActivity extends Activity {
private static int sessionDepth = 0;
@Override
protected void onStart() {
super.onStart();
sessionDepth++;
if(sessionDepth == 1){
//app came to foreground;
}
}
@Override
protected void onStop() {
super.onStop();
if (sessionDepth > 0)
sessionDepth--;
if (sessionDepth == 0) {
// app went to background
}
}
}
Editar: conforme os comentários, também mudamos para onStart () nas versões posteriores do código. Além disso, estou adicionando super chamadas, que estavam faltando na minha postagem inicial, porque isso era mais um conceito do que um código funcional.
onStop is called when the activity is no longer visible to the user
,.
Se o seu aplicativo consistir em várias atividades e / ou atividades empilhadas, como um widget da barra de guias, substituir onPause () e onResume () não funcionará. Ou seja, ao iniciar uma nova atividade, as atividades atuais serão pausadas antes da criação da nova. O mesmo se aplica ao concluir (usando o botão "voltar") uma atividade.
Eu encontrei dois métodos que parecem funcionar como desejado.
O primeiro requer a permissão GET_TASKS e consiste em um método simples que verifica se a principal atividade em execução no dispositivo pertence ao aplicativo, comparando os nomes dos pacotes:
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 foi encontrado na estrutura Droid-Fu (agora denominada Ignition).
O segundo método que eu implementei não requer a permissão GET_TASKS, o que é bom. Em vez disso, é um pouco mais complicado de implementar.
Na classe MainApplication, você tem uma variável que rastreia o número de atividades em execução no seu aplicativo. Em onResume () para cada atividade você aumenta a variável e em onPause () você a diminui.
Quando o número de atividades em execução atinge 0, o aplicativo é colocado em segundo plano se as seguintes condições forem verdadeiras:
Quando você pode detectar que o aplicativo renunciou ao plano de fundo, é fácil detectar também quando ele é trazido de volta ao primeiro plano.
Crie uma classe que se estenda Application
. Então, nele podemos usar seu método de substituição onTrimMemory()
,.
Para detectar se o aplicativo foi para segundo plano, 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
}
}
FragmentActivity
você também pode querer adicionar level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
também.
Considere usar onUserLeaveHint. Isso só será chamado quando o aplicativo entrar em segundo plano. O onPause terá casos de canto para lidar, pois pode ser chamado por outros motivos; por exemplo, se o usuário abrir outra atividade no seu aplicativo, como a página de configurações, o método onPause da sua atividade principal será chamado, mesmo que ele ainda esteja no seu aplicativo; rastrear o que está acontecendo levará a erros quando você pode simplesmente usar o retorno de chamada onUserLeaveHint, que faz o que você está pedindo.
Quando em UserLeaveHint é chamado, você pode definir um sinalizador inBackground booleano como true. Quando onResume for chamado, suponha que você voltou ao primeiro plano se o sinalizador inBackground estiver definido. Isso ocorre porque onResume também será chamado em sua atividade principal se o usuário estiver apenas no menu de configurações e nunca sair do aplicativo.
Lembre-se de que, se o usuário pressionar o botão home na tela de configurações, onUserLeaveHint será chamado em sua atividade de configurações e, quando retornar em Resume, será chamado em sua atividade de configurações. Se você tiver apenas esse código de detecção em sua atividade principal, perderá este caso de uso. Para ter esse código em todas as suas atividades sem duplicar o código, tenha uma classe de atividade abstrata que estenda Activity e insira seu código comum. Então, cada atividade que você possui pode estender essa atividade abstrata.
Por exemplo:
public abstract AbstractActivity extends Activity {
private static boolean inBackground = false;
@Override
public void onResume() {
if (inBackground) {
// You just came from the background
inBackground = false;
}
else {
// You just returned from another activity within your own app
}
}
@Override
public void onUserLeaveHint() {
inBackground = true;
}
}
public abstract MainActivity extends AbstractActivity {
...
}
public abstract SettingsActivity extends AbstractActivity {
...
}
ActivityLifecycleCallbacks pode ser interessante, mas não está bem documentado.
Porém, se você chamar registerActivityLifecycleCallbacks (), poderá obter retornos de chamada para quando as Atividades forem criadas, destruídas, etc. Você pode chamar getComponentName () para a Atividade.
O pacote android.arch.lifecycle fornece classes e interfaces que permitem criar componentes com reconhecimento do ciclo de vida
Seu aplicativo deve implementar a interface LifecycleObserver:
public class MyApplication extends Application implements LifecycleObserver {
@Override
public void onCreate() {
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private void onAppBackgrounded() {
Log.d("MyApp", "App in background");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
private void onAppForegrounded() {
Log.d("MyApp", "App in foreground");
}
}
Para fazer isso, você precisa adicionar essa dependência ao seu arquivo build.gradle:
dependencies {
implementation "android.arch.lifecycle:extensions:1.1.1"
}
Conforme recomendado pelo Google, você deve minimizar o código executado nos métodos de atividades do ciclo de vida:
Um padrão comum é implementar as ações dos componentes dependentes nos métodos de ciclo de vida de atividades e fragmentos. No entanto, esse padrão leva a uma organização ruim do código e à proliferação de erros. Usando componentes com reconhecimento do ciclo de vida, é possível mover o código dos componentes dependentes para fora dos métodos do ciclo de vida e para os próprios componentes.
Você pode ler mais aqui: https://developer.android.com/topic/libraries/architecture/lifecycle
No seu aplicativo, adicione o retorno de chamada e verifique a atividade raiz da seguinte maneira:
@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();
}
}
});
}
Criei um projeto no Github app-foreground-background-listen
Crie uma BaseActivity para todas as atividades no seu aplicativo.
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
}
}
Agora use esta BaseActivity como uma superclasse de toda a sua Atividade, como MainActivity estende BaseActivity e onAppStart será chamado quando você iniciar o aplicativo e onAppPause () será chamado quando o aplicativo for em segundo plano em qualquer tela.
Isso é muito fácil com o ProcessLifecycleOwner
Adicione essas dependências
implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"
Em 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")
}
}
Então, na sua atividade base:
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get()
.lifecycle
.addObserver(
ForegroundBackgroundListener()
.also { appObserver = it })
}
Consulte meu artigo sobre este tópico: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48
Você pode usar o ProcessLifecycleOwner conectando um observador de ciclo de vida a ele.
public class ForegroundLifecycleObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onAppCreated() {
Timber.d("onAppCreated() called");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onAppStarted() {
Timber.d("onAppStarted() called");
}
@OnLifecycleEvent(Event.ON_RESUME)
public void onAppResumed() {
Timber.d("onAppResumed() called");
}
@OnLifecycleEvent(Event.ON_PAUSE)
public void onAppPaused() {
Timber.d("onAppPaused() called");
}
@OnLifecycleEvent(Event.ON_STOP)
public void onAppStopped() {
Timber.d("onAppStopped() called");
}
}
então, na onCreate()
classe Application, você chama isso:
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());
com isso, você poderá capturar os eventos de ON_PAUSE
e ON_STOP
de seu aplicativo que acontecem quando são exibidos em segundo plano.
Não há métodos simples de ciclo de vida para informar quando todo o aplicativo fica em segundo plano / em primeiro plano.
Eu fiz isso de maneira simples. Siga as instruções abaixo para detectar a fase de segundo plano / primeiro plano do aplicativo.
Com um pouco de solução alternativa, é possível. Aqui, ActivityLifecycleCallbacks vem em socorro. Deixe-me passar passo a passo.
Primeiro, crie uma classe que estenda o android.app.Application e implemente a interface ActivityLifecycleCallbacks . No Application.onCreate (), registre o retorno de chamada.
public class App extends Application implements
Application.ActivityLifecycleCallbacks {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
}
Registre a classe "App" no manifesto como abaixo <application android:name=".App"
,.
Haverá pelo menos uma atividade no estado iniciado quando o aplicativo estiver em primeiro plano e não haverá atividade no estado iniciado quando o aplicativo estiver em segundo plano.
Declare 2 variáveis como abaixo na classe "App".
private int activityReferences = 0;
private boolean isActivityChangingConfigurations = false;
activityReferences
manterá a contagem do número de atividades no estado iniciado . isActivityChangingConfigurations
é um sinalizador para indicar se a Atividade atual está passando por alterações na configuração como uma opção de orientação.
Usando o código a seguir, você pode detectar se o aplicativo vem em primeiro plano.
@Override
public void onActivityStarted(Activity activity) {
if (++activityReferences == 1 && !isActivityChangingConfigurations) {
// App enters foreground
}
}
Isto é como detectar se o aplicativo fica em segundo plano.
@Override
public void onActivityStopped(Activity activity) {
isActivityChangingConfigurations = activity.isChangingConfigurations();
if (--activityReferences == 0 && !isActivityChangingConfigurations) {
// App enters background
}
}
Como funciona:
Este é um pequeno truque feito com a maneira como os métodos do Ciclo de Vida são chamados em sequência. Deixe-me mostrar um cenário.
Suponha que o usuário inicie o aplicativo e a Atividade A do iniciador A seja iniciada. As chamadas do Ciclo de vida serão,
A.onCreate ()
A.onStart () (++ activityReferences == 1) (o aplicativo entra em primeiro plano)
A.onResume ()
Agora a Atividade A inicia a Atividade B.
A.onPause ()
B.onCreate ()
B.onStart () (++ activityReferences == 2)
B.onResume ()
A.onStop () (--activityReferences == 1)
Em seguida, o usuário volta da Atividade B,
B.onPause ()
A.onStart () (++ activityReferences == 2)
A.onResume ()
B.onStop () (--activityReferences == 1)
B.onDestroy ()
Em seguida, o usuário pressiona o botão Início,
A.onPause ()
A.onStop () (--activityReferences == 0) (o aplicativo entra em segundo plano)
Caso o usuário pressione o botão Início da Atividade B em vez do botão Voltar, continuará sendo o mesmo e o item ActivityReferences 0
. Portanto, podemos detectar como o aplicativo entrando em segundo plano.
Então, qual é o papel isActivityChangingConfigurations
? No cenário acima, suponha que a atividade B altere a orientação. A sequência de retorno de chamada será,
B.onPause ()
B.onStop () (--activityReferences == 0) (o aplicativo entra em segundo plano?)
B.onDestroy ()
B.onCreate ()
B.onStart () (++ activityReferences == 1) (o aplicativo entra em primeiro plano ??)
B.onResume ()
É por isso que temos uma verificação adicional isActivityChangingConfigurations
para evitar o cenário quando a Atividade estiver passando pelas mudanças na Configuração.
Encontrei um bom método para detectar aplicativos, seja em primeiro plano ou em segundo plano. Aqui está o meu código . Espero que isso ajude você.
/**
* 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();
}
Edição 2: O que escrevi abaixo não funcionará realmente. O Google rejeitou um aplicativo que inclui uma chamada para ActivityManager.getRunningTasks (). A partir da documentação , é aparente que essa API é apenas para fins de depuração e desenvolvimento. Estarei atualizando este post assim que tiver tempo para atualizar o projeto GitHub abaixo com um novo esquema que usa temporizadores e é quase tão bom.
Edit 1: Eu escrevi uma postagem no blog e criei um repositório simples do GitHub para tornar isso realmente fácil.
A resposta aceita e a melhor avaliada não são realmente a melhor abordagem. A implementação da resposta mais bem avaliada de isApplicationBroughtToBackground () não lida com a situação em que a Atividade principal do Aplicativo está cedendo a uma Atividade definida no mesmo Aplicativo, mas possui um pacote Java diferente. Eu vim com uma maneira de fazer isso que funcionará nesse caso.
Ligue para onPause () e ele informará se seu aplicativo está entrando em segundo plano porque outro aplicativo foi iniciado ou o usuário pressionou o botão home.
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;
}
Resposta correta aqui
Crie uma classe com o nome MyApp, como abaixo:
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() {
}
}
Em qualquer lugar que você quiser (melhor primeira atividade lançada no aplicativo), adicione o código abaixo:
MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);
Feito! Agora, quando o aplicativo está em segundo plano, obtemos log status : we are out
e, quando entramos no aplicativo, obtemos logstatus : we are out
Minha solução foi inspirada na resposta de @ d60402 e também conta com uma janela de tempo, mas sem usar o 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();
}
}
onde SingletonApplication
é uma extensão da Application
classe:
public class SingletonApplication extends Application {
public boolean isInBackground = false;
public long lastForegroundTransition = 0;
}
Eu estava usando isso com o Google Analytics EasyTracker, e funcionou. Pode ser estendido para fazer o que você procura usando um número inteiro simples.
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) {
}
});
}
}
Eu sei que é um pouco tarde, mas acho que todas essas respostas têm alguns problemas enquanto eu fazia isso abaixo e isso funciona perfeitamente.
crie um retorno de chamada do ciclo de vida da atividade como este:
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) {
}
}
e apenas registre-o na sua classe de aplicativo, como abaixo:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
Essa parece ser uma das perguntas mais complicadas do Android, já que (no momento em que este artigo foi escrito) o Android não possui equivalentes applicationDidEnterBackground()
ou applicationWillEnterForeground()
retornos de chamada do iOS . Eu usei uma AppState Library que foi montada por @jenzz .
[AppState é] uma biblioteca Android simples e reativa baseada em RxJava que monitora as alterações de estado do aplicativo. Ele notifica os assinantes toda vez que o aplicativo entra em segundo plano e volta ao primeiro plano.
Aconteceu que era exatamente isso que eu precisava, especialmente porque meu aplicativo tinha várias atividades; portanto, simplesmente verificar onStart()
ou ativar onStop()
uma atividade não seria suficiente.
Primeiro, adicionei essas dependências para classificar:
dependencies {
compile 'com.jenzz.appstate:appstate:3.0.1'
compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}
Depois, era simples adicionar essas linhas a um local apropriado no seu 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;
}
}
});
Dependendo de como você assina o observável, pode ser necessário cancelar o registro para evitar vazamentos de memória. Novamente, mais informações na página do github .
Esta é a versão modificada da resposta da @ d60402: https://stackoverflow.com/a/15573121/4747587
Faça tudo mencionado lá. Mas, em vez de ter um Base Activity
e torná-lo um pai para todas as atividades e substituir o onResume()
e onPause
, faça o seguinte:
Na sua classe de aplicativo, adicione a linha:
registerActivityLifecycleCallbacks (retorno de chamada Application.ActivityLifecycleCallbacks);
Isso callback
possui todos os métodos de ciclo de vida da atividade e agora você pode substituir onActivityResumed()
e onActivityPaused()
.
Dê uma olhada neste Gist: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
Você pode conseguir isso facilmente com a ajuda de ActivityLifecycleCallbacks
e ComponentCallbacks2
algo como abaixo.
Crie uma classe AppLifeCycleHandler
implementada acima das interfaces.
package com.sample.app;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.os.Bundle;
/**
* Created by Naveen on 17/04/18
*/
public class AppLifeCycleHandler
implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
AppLifeCycleCallback appLifeCycleCallback;
boolean appInForeground;
public AppLifeCycleHandler(AppLifeCycleCallback appLifeCycleCallback) {
this.appLifeCycleCallback = appLifeCycleCallback;
}
@Override
public void onActivityResumed(Activity activity) {
if (!appInForeground) {
appInForeground = true;
appLifeCycleCallback.onAppForeground();
}
}
@Override
public void onTrimMemory(int i) {
if (i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
appInForeground = false;
appLifeCycleCallback.onAppBackground();
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
interface AppLifeCycleCallback {
void onAppBackground();
void onAppForeground();
}
}
Na sua classe, que estende o Application
implemento AppLifeCycleCallback
para obter retornos de chamada quando o aplicativo alterna entre primeiro e segundo plano. Algo como abaixo.
public class BaseApplication extends Application implements AppLifeCycleHandler.AppLifeCycleCallback{
@Override
public void onCreate() {
super.onCreate();
AppLifeCycleHandler appLifeCycleHandler = new AppLifeCycleHandler(this);
registerActivityLifecycleCallbacks(appLifeCycleHandler);
registerComponentCallbacks(appLifeCycleHandler);
}
@Override
public void onAppBackground() {
Log.d("LifecycleEvent", "onAppBackground");
}
@Override
public void onAppForeground() {
Log.d("LifecycleEvent", "onAppForeground");
}
}
Espero que isto ajude.
EDIT Como alternativa, agora você pode usar o componente de arquitetura compatível com o ciclo de vida.
Como não encontrei nenhuma abordagem, que também lida com a rotação sem verificar os carimbos de data e hora, pensei em compartilhar como agora o fazemos em nosso aplicativo. A única adição a esta resposta https://stackoverflow.com/a/42679191/5119746 é que também levamos em consideração a orientação.
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
Em seguida, para os retornos de chamada, primeiro temos o resumo:
// ActivityLifecycleCallbacks
override fun onActivityResumed(activity: Activity?) {
mResumed++
if (mAppIsInBackground) {
// !!! App came from background !!! Insert code
mAppIsInBackground = false
}
mOrientationWasChanged = false
}
E onActivityStopped:
override fun onActivityStopped(activity: Activity?) {
if (mResumed == mPaused && !mOrientationWasChanged) {
// !!! App moved to background !!! Insert code
mAppIsInBackground = true
}
E então, aqui vem a adição: Verificando mudanças de orientação:
override fun onConfigurationChanged(newConfig: Configuration) {
if (newConfig.orientation != mCurrentOrientation) {
mCurrentOrientation = newConfig.orientation
mOrientationWasChanged = true
}
super.onConfigurationChanged(newConfig)
}
É isso aí. Espero que isso ajude alguém :)
Podemos expandir esta solução usando LiveData
:
class AppForegroundStateLiveData : LiveData<AppForegroundStateLiveData.State>() {
private var lifecycleListener: LifecycleObserver? = null
override fun onActive() {
super.onActive()
lifecycleListener = AppLifecycleListener().also {
ProcessLifecycleOwner.get().lifecycle.addObserver(it)
}
}
override fun onInactive() {
super.onInactive()
lifecycleListener?.let {
this.lifecycleListener = null
ProcessLifecycleOwner.get().lifecycle.removeObserver(it)
}
}
internal inner class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
value = State.FOREGROUND
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
value = State.BACKGROUND
}
}
enum class State {
FOREGROUND, BACKGROUND
}
}
Agora podemos assinar este LiveData e capturar os eventos necessários. Por exemplo:
appForegroundStateLiveData.observeForever { state ->
when(state) {
AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
}
}
Essas respostas não parecem estar corretas. Esses métodos também são chamados quando outra atividade inicia e termina. O que você pode fazer é manter uma bandeira global (sim, globais são ruins :) e defina isso como verdadeiro sempre que você iniciar uma nova atividade. Defina-o como false no onCreate de cada atividade. Em seguida, na onPause você verifica esse sinalizador. Se for falso, seu aplicativo está entrando em segundo plano ou está sendo morto.