No logout, limpe a pilha do histórico de atividades, impedindo que o botão "voltar" abra as atividades somente de logon

235

Todas as atividades no meu aplicativo exigem que um usuário esteja logado para visualizar. Os usuários podem sair de praticamente qualquer atividade. Este é um requisito do aplicativo. A qualquer momento, se o usuário sair, quero enviar o usuário para o Login Activity. Nesse ponto, quero que essa atividade esteja na parte inferior da pilha do histórico, de modo que pressionar o botão "voltar" retorne o usuário à tela inicial do Android.

Já vi essa pergunta em alguns lugares diferentes, todas respondidas com respostas semelhantes (que descrevi aqui), mas quero colocá-la aqui para obter feedback.

Tentei abrir a atividade de logon definindo seus Intentsinalizadores para o FLAG_ACTIVITY_CLEAR_TOPque parece fazer o descrito na documentação, mas não alcança meu objetivo de colocar a atividade de logon na parte inferior da pilha do histórico e impedindo que o usuário volte. às atividades de logon vistas anteriormente. Também tentei usar android:launchMode="singleTop"a atividade Login no manifesto, mas isso também não cumpre minha meta (e parece não ter efeito mesmo).

Acredito que preciso limpar a pilha de histórico ou concluir todas as atividades abertas anteriormente.

Uma opção é ter o onCreatestatus de logon de verificação de cada atividade e, finish()se não estiver logado. Não gosto dessa opção, pois o botão Voltar ainda estará disponível para uso, navegando de volta à medida que as atividades se fecham.

A próxima opção é manter uma LinkedListreferência a todas as atividades abertas que são estaticamente acessíveis de qualquer lugar (talvez usando referências fracas). No logout, acessarei esta lista e repetirei todas as atividades abertas anteriormente, invocando finish()cada uma. Provavelmente vou começar a implementar esse método em breve.

Eu prefiro usar alguns Intenttruques de bandeira para fazer isso, no entanto. Ficaria feliz em descobrir que posso atender aos requisitos do meu aplicativo sem precisar usar nenhum dos dois métodos descritos acima.

Existe uma maneira de fazer isso usando Intentou manifestar configurações ou a minha segunda opção é manter uma LinkedListdas atividades abertas a melhor opção? Ou existe outra opção que estou completamente ignorando?

Skyler
fonte

Respostas:

213

Posso sugerir-lhe uma outra abordagem IMHO mais robusta. Basicamente, você precisa transmitir uma mensagem de logout para todas as suas atividades que precisam permanecer com o status de logon. Assim, você pode usar o sendBroadcaste instalar um BroadcastReceiverem todas as suas atividades. Algo assim:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);

O receptor (atividade segura):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**snip **/
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.package.ACTION_LOGOUT");
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("onReceive","Logout in progress");
            //At this point you should start the login activity and finish this one
            finish();
        }
    }, intentFilter);
    //** snip **//
}
Francesco Laurita
fonte
27
@Christopher, cada atividade é registrada para a transmissão quando é criada. Quando se trata de segundo plano (ou seja, uma nova atividade chega ao topo da pilha), seu onStop () será chamado, mas ainda pode receber transmissões. Você só precisa chamar unregisterReceiver () em onDestroy () em vez de em onStop ().
Russell Davis
9
Isso funciona se uma atividade em algum lugar da pilha foi desativada pelo sistema operacional para recuperar memória? Ou seja. o sistema considerará realmente concluído após o envio da transmissão acima e não a recriará ao pressionar o botão Voltar?
Jan Żankowski
5
Embora isso pareça uma solução elegante, é importante observar que isso não é síncrono.
Che Jami 25/10
34
Uma boa solução, mas em vez de usar o registro do receptor Broadcast, conforme descrito no código acima, você deve usar um LocalBroadcastManager.getInstance (this) .registerReceiver (...) e LocalBroadcastManager.getInstance (this) .unregisterReceiver (..) . Otherwiser sua aplicação pode receber intenções de qualquer outro aplicativo (problema de segurança)
Uku Loskit
5
@ Warlock Você está correto. A armadilha dessa abordagem é quando uma Atividade na pilha de trás é destruída pelo sistema (pode ocorrer por vários motivos, como o cenário de pouca memória observado). Nesse caso, a instância de atividade não estará disponível para receber a transmissão. Mas o sistema ainda navegará de volta por essa atividade, recriando-a. Isso pode ser testado / reproduzido ativando a configuração do desenvolvedor "Don't Keep Activities"
Eric Schlenz
151

Parece um rito de passagem que um novo programador do Android gaste um dia pesquisando esse problema e lendo todos esses threads do StackOverflow. Agora sou recém iniciado e deixo aqui vestígios de minha humilde experiência para ajudar um futuro peregrino.

Primeiro, não há uma maneira óbvia ou imediata de fazer isso de acordo com minha pesquisa. (as of September 2012).Você pensaria que poderia ser simples, startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)mas não .

Você pode fazer startActivity(new Intent(this, LoginActivity.class))com FLAG_ACTIVITY_CLEAR_TOP- e isto fará com que o quadro para procurar para baixo da pilha, encontrar o seu instância anterior original LoginActivity, recriá-lo e limpar o resto da (para cima) pilha. E como o Login está presumivelmente na parte inferior da pilha, agora você tem uma pilha vazia e o botão Voltar simplesmente sai do aplicativo.

MAS - isso só funciona se você deixou a instância original do LoginActivity ativa na base da sua pilha. Se, como muitos programadores, você optar por fazer finish()isso LoginActivitydepois que o usuário fizer login com sucesso, ele não FLAG_ACTIVITY_CLEAR_TOPestará mais na base da pilha e a semântica não se aplicará ... você acaba criando um novo LoginActivityno topo da pilha existente. O que quase certamente NÃO é o que você deseja (comportamento estranho em que o usuário pode "voltar" do caminho para uma tela anterior).

Portanto, se você já teve finish()esse problema LoginActivity, precisa seguir algum mecanismo para limpar sua pilha e iniciar um novo LoginActivity. Parece que a resposta @doreamonneste tópico é a melhor solução (pelo menos para meus humildes olhos):

https://stackoverflow.com/a/9580057/614880

Eu suspeito fortemente que as implicações complicadas de você deixar o LoginActivity ativo estão causando muita dessa confusão.

Boa sorte.

Mike Repass
fonte
5
Boa resposta. O truque FLAG_ACTIVITY_CLEAR_TOP, recomendado pela maioria das pessoas, não funciona se você tiver concluído a LoginActivity.
Konsumierer
Obrigado pela resposta. Outro cenário é como quando há uma sessão (por exemplo, como fb), mesmo que não chamemos atividade de login, portanto não há nenhum ponto da atividade de login na pilha. Então, a abordagem acima mencionada é inútil.
Prashanth Debbadwar
118

ATUALIZAR

o super finishAffinity()método ajudará a reduzir o código, mas conseguirá o mesmo. Terminará a atividade atual, bem como todas as atividades na pilha, use getActivity().finishAffinity()se você estiver em um fragmento.

finishAffinity(); 
startActivity(new Intent(mActivity, LoginActivity.class));

RESPOSTA ORIGINAL

Suponha que LoginActivity -> HomeActivity -> ... -> SettingsActivity indicativo de chamadaOut ():

void signOut() {
    Intent intent = new Intent(this, HomeActivity.class);
    intent.putExtra("finish", true);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // To clean up all activities
    startActivity(intent);
    finish();
}

Página inicial

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    boolean finish = getIntent().getBooleanExtra("finish", false);
    if (finish) {
        startActivity(new Intent(mContext, LoginActivity.class));
        finish();
        return;
    }
    initializeView();
}

Isso funciona para mim, espero que seja útil para você também. :)

thanhbinh84
fonte
1
Acho que sua solução pressupõe que o usuário clique em signOut e volte para apenas uma atividade (HomeActivity). E se você tiver 10 atividades na pilha?
IgorGanapolsky
11
Se você tiver 10 atividades na parte superior do HomeActivity, o sinalizador FLAG_ACTIVITY_CLEAR_TOP ajudará a limpar todas elas.
precisa
1
Isso só funciona se, ao iniciar o HomeActivity, você obtiver seu OnCreate de HomeActivity. Simplesmente iniciar a atividade em casa não a recriará necessariamente, a menos que já tenha sido concluída ou destruída. Se o HomeActivity não precisar ser recriado, o OnCreate não será chamado e, depois que você sair, estará sentado na sua atividade em casa.
Topwik
1
Essa é uma solução possível e envolve muito menos a codificação de um recurso simples (para o usuário), como logout.
Subin Sebastian
2
O sinalizador Intent.FLAG_ACTIVITY_CLEAR_TOP ajuda a limpar todas as atividades, incluindo HomeActivity, portanto, o método onCreate () será chamado quando você iniciar esta atividade novamente.
thanhbinh84
73

Se você usa a API 11 ou superior, pode tentar o seguinte: FLAG_ACTIVITY_CLEAR_TASK- parece estar resolvendo exatamente o problema que está tendo. Obviamente, o público pré-API 11 teria que usar alguma combinação de todas as atividades para verificar um extra, como sugere @doreamon, ou algum outro truque.

(Observe também: para usar isso, você deve passar FLAG_ACTIVITY_NEW_TASK)

Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("finish", true); // if you are checking for this in your other Activities
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 
                Intent.FLAG_ACTIVITY_CLEAR_TASK |
                Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
xbakesx
fonte
1
Trabalhando como um encanto. Muito obrigado! À medida que desenvolvo no min API 14, essa é a única coisa a implementar.
AndyB
1
Acho que não precisamos de FLAG_ACTIVITY_CLEAR_TOP ao usar esta solução para LoginActivity.
Bharat Dodeja
Eu acho que apenas finish();fará o trabalho para evitar voltar após o logout. Qual é a necessidade de definir sinalizadores?
Pankaj
Isso cria uma exceção. Calling startActivity () de fora de um contexto de Atividade requer o sinalizador FLAG_ACTIVITY_NEW_TASK
Aman
31

Também passei algumas horas nisso ... e concordo que FLAG_ACTIVITY_CLEAR_TOP soa como o que você deseja: limpe toda a pilha, exceto a atividade que está sendo iniciada, para que o botão Voltar saia do aplicativo. No entanto, como Mike Repass mencionou, FLAG_ACTIVITY_CLEAR_TOP só funciona quando a atividade que você está iniciando já está na pilha; quando a atividade não está lá, a bandeira não faz nada.

O que fazer? Coloque a atividade sendo iniciada na pilha com FLAG_ACTIVITY_NEW_TASK , que torna essa atividade o início de uma nova tarefa na pilha de histórico. Em seguida, adicione o sinalizador FLAG_ACTIVITY_CLEAR_TOP.

Agora, quando FLAG_ACTIVITY_CLEAR_TOP for encontrar a nova atividade na pilha, ela estará lá e será exibida antes que todo o resto seja limpo.

Aqui está a minha função de logout; o parâmetro View é o botão ao qual a função está conectada.

public void onLogoutClick(final View view) {
    Intent i = new Intent(this, Splash.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(i);
    finish();
}
christinac
fonte
1
Não funcionará na API <= 10, pois FLAG_ACTIVITY_CLEAR_TASKainda não foi adicionado
Youans
1
@christinac Você está falando sobre FLAG_ACTIVITY_CLEAR_TOP e seu snippet de código tem FLAG_ACTIVITY_CLEAR_TASK; qual é válido então?
Marian Paździoch 17/07/2015
10

Muitas respostas. Pode ser que este também ajude

Intent intent = new Intent(activity, SignInActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
this.finish();

Versão Kotlin-

Intent(this, SignInActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also { startActivity(it) }
finish()
Gulshan
fonte
4

Use isso, deve ser útil para você. Resposta xbakesx ligeiramente modificada.

Intent intent = new Intent(this, LoginActivity.class);
if(Build.VERSION.SDK_INT >= 11) {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
startActivity(intent);
Mohamed Ibrahim
fonte
4

A solução aceita não está correta, há problemas, pois o uso de um receptor de transmissão não é uma boa ideia para esse problema. Se sua atividade já chamou o método onDestroy (), você não receberá o receptor. A melhor solução é ter um valor booleano em suas preferências compartilhadas e verificá-lo no método onCreate () da sua atividade. Se não deve ser chamado quando o usuário não estiver conectado, conclua a atividade. Aqui está o código de exemplo para isso. Tão simples e funciona para todas as condições.

protected void onResume() {
  super.onResume();
  if (isAuthRequired()) {
    checkAuthStatus();
  }
}

private void checkAuthStatus() {
  //check your shared pref value for login in this method
  if (checkIfSharedPrefLoginValueIsTrue()) {
    finish();
  }
}

boolean isAuthRequired() {
  return true;
}
Yekmer Simsek
fonte
1
Faz anos, mas acredito que fiz os dois. Cada atividade estendeu o LoggedInActivity, que verificou o status de logon do usuário em onCreate (). O LoggedInActivity também ouviu a transmissão "usuário desconectado" e fez o que fosse necessário para responder a isso.
Skyler
2
mais provavelmente, você deve colocar o checkAuthStatus na onResume()methode. Porque quando você pressiona o botão Voltar, é mais provável que a atividade seja retomada em vez de criada.
Maysi
1
Graças @maysi para a sugestão, sim este botão caminho de volta irá funcionar corretamente também, eu atualizei a entrada
Yekmer Simsek
4

Às vezes finish()não está funcionando

Eu resolvi esse problema com

finishAffinity()

AJay
fonte
3

Eu sugeriria uma abordagem diferente para esta pergunta. Talvez não seja o mais eficiente, mas acho que é o mais fácil de aplicar e requer muito pouco código. Escrever o próximo código em sua primeira atividade (atividade de logon, no meu caso) não permitirá que o usuário volte para as atividades iniciadas anteriormente após o logout.

@Override
public void onBackPressed() {
    // disable going back to the MainActivity
    moveTaskToBack(true);
}

Suponho que o LoginActivity tenha terminado logo após o login do usuário, para que ele não possa voltar mais tarde pressionando o botão Voltar. Em vez disso, o usuário deve pressionar um botão de logout dentro do aplicativo para fazer logoff corretamente. O que esse botão de logoff implementaria é uma intenção simples, como a seguir:

Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();

Todas as sugestões são bem vindas.

Nada
fonte
2

A resposta selecionada é inteligente e complicada. Aqui está como eu fiz isso:

LoginActivity é a atividade raiz da tarefa, defina android: noHistory = "true" no Manifest.xml; Digamos que você queira sair do SettingsActivity, faça o seguinte:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);
t-gao
fonte
2

Aqui está a solução que eu encontrei no meu aplicativo.

No meu LoginActivity, depois de processar com êxito um login, inicio o próximo de maneira diferente, dependendo do nível da API.

Intent i = new Intent(this, MainActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    startActivity(i);
    finish();
} else {
    startActivityForResult(i, REQUEST_LOGIN_GINGERBREAD);
}

Em seguida, no método onActivityForResult do meu LoginActivity:

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
        requestCode == REQUEST_LOGIN_GINGERBREAD &&
        resultCode == Activity.RESULT_CANCELED) {
    moveTaskToBack(true);
}

Por fim, depois de processar um logout em qualquer outra atividade:

Intent i = new Intent(this, LoginActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);

Quando no Gingerbread, fica assim se eu pressionar o botão voltar de MainActivity, o LoginActivity será imediatamente oculto. No Honeycomb e posterior, apenas concluo o LoginActivity após o processamento de um login e ele é recriado corretamente após o processamento de um logout.

Seastland
fonte
Não está funcionando para mim. No meu caso, usei fragmentatividade. O método de resultado da atividade não é chamado.
Mohamed Ibrahim
1

Isso funcionou para mim:

     // After logout redirect user to Loing Activity
    Intent i = new Intent(_context, MainActivity.class);
    // Closing all the Activities
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

    // Add new Flag to start new Activity
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    // Staring Login Activity
    _context.startActivity(i);
Preetansh
fonte
Você poderia elaborar mais sua resposta adicionando um pouco mais de descrição sobre a solução que você fornece?
abarisone
0

Inicie sua atividade com StartActivityForResult e, enquanto você efetua logout, defina seu resultado e, de acordo com o resultado, termine sua atividade

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivityForResult(intent, BACK_SCREEN);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case BACK_SCREEN:
        if (resultCode == REFRESH) {
            setResult(REFRESH);
            finish();
        }
        break;
    }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        AlertDialog alertDialog = builder.create();

        alertDialog
                .setTitle((String) getResources().getText(R.string.home));
        alertDialog.setMessage((String) getResources().getText(
                R.string.gotoHome));
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {

                        setResult(REFRESH);
                        finish();
                    }

                });

        alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {
                    }
                });
        alertDialog.show();
        return true;
    } else
        return super.onKeyDown(keyCode, event);

}
Eby
fonte
0

A solução @doreamon fornecida funciona bem em todos os casos, exceto um:

Se Após o login, o usuário da tela Killing Login navegou diretamente para uma tela do meio. Por exemplo, em um fluxo de A-> B-> C, navegue como: Login -> B -> C -> Pressione o atalho para voltar para casa. O uso de FLAG_ACTIVITY_CLEAR_TOP limpa apenas a atividade C, pois a Página inicial (A) não está no histórico da pilha. Pressionar Voltar em uma tela nos levará de volta a B.

Para resolver esse problema, podemos manter uma pilha de atividades (Arraylist) e, quando pressionamos o botão home, precisamos matar todas as atividades nessa pilha.

Surendra Kumar
fonte
0

É possível gerenciando um sinalizador em SharedPreferences ou em Application Activity.

No início do aplicativo (na tela inicial), defina o sinalizador = false; No evento Logout Click, apenas defina o sinalizador true e, em OnResume () de todas as atividades, verifique se o sinalizador é true e chame finish ().

Ele funciona como um encanto :)

MrDumb
fonte
0

ao clicar em Logout, você pode chamar isso

private void GoToPreviousActivity() {
    setResult(REQUEST_CODE_LOGOUT);
    this.finish();
}

onActivityResult () da atividade anterior, chame esse código acima novamente até concluir todas as atividades.

Mak
fonte
1
Isso significa que ele precisa percorrer todas as atividades de maneira linear, em vez de transmitir o finish () de uma só vez?
IgorGanapolsky
@ IGg G. sim, é a maneira alternativa de terminar em sequência #
Mak
-1

Uma opção é fazer com que o onCreate de cada atividade verifique o status de logon e termine () se não estiver conectado. Não gosto dessa opção, pois o botão Voltar ainda estará disponível para uso, navegando de volta à medida que as atividades se fecham.

O que você quer fazer é chamar logout () e finish () nos métodos onStop () ou onPause (). Isso forçará o Android a ligar para onCreate () quando a atividade for reativada, pois ela não ficará mais na pilha de atividades. Em seguida, faça o que você diz: em onCreate () verifique o status de logon e encaminhe para a tela de logon, se não estiver logado.

Outra coisa que você pode fazer é verificar o status de logon em onResume () e, se não estiver logado, terminar () e iniciar a atividade de logon.

Ricardo Villamil
fonte
Prefiro não sair de cada atividade pausada ou interrompida. Além disso, o aplicativo inicia o logout ou o logon, portanto, não preciso verificar se realmente está logado.
Skylife
@ Ricardo: sua solução não requer um BroadcastReceiver?
IgorGanapolsky