ログアウト時にアクティビティ履歴スタックをクリアし、「戻る」ボタンでログイン済みのみのアクティビティを開けないようにする


235

アプリケーションのすべてのアクティビティで、ユーザーが表示するにはログインする必要があります。ユーザーはほとんどすべてのアクティビティからログアウトできます。これはアプリケーションの要件です。ユーザーがログアウトした場合はいつでも、ユーザーをLoginに送信しますActivity。この時点で、このアクティビティを履歴スタックの一番下にして、「戻る」ボタンを押すとユーザーがAndroidのホーム画面に戻るようにしたいと思います。

この質問がいくつかの異なる場所で質問され、すべて同じような回答で回答されているのを見てきました(ここで概要を説明します)が、フィードバックを収集するためにここでポーズをとりたいと思います。

ドキュメントに概要が示されIntentFLAG_ACTIVITY_CLEAR_TOPいるようにフラグを設定してログインアクティビティを開こうとしましたが、ログインアクティビティを履歴スタックの最下部に配置し、ユーザーが戻るのを防ぐという私の目標を達成していません以前に表示されたログインアクティビティに。android:launchMode="singleTop"マニフェストのLoginアクティビティにも使用しようとしましたが、これも私の目的を達成しません(とにかく効果がないようです)。

履歴スタックをクリアするか、以前に開いたアクティビティをすべて完了する必要があると思います。

1つのオプションは、各アクティビティのonCreateログインステータスをチェックし、ログインしてfinish()いない場合はチェックすることです。私はこのオプションが好きではありません。アクティビティが閉じたときに戻るボタンが引き続き使用できるためです。

次のオプションはLinkedList、どこからでも静的にアクセスできる(おそらく弱い参照を使用する)すべての開いているアクティビティへの参照を維持することです。ログアウト時にこのリストにアクセスして、以前に開いたすべてのアクティビティを繰り返しfinish()、それぞれのアクティビティを呼び出します。私はおそらくすぐにこのメソッドの実装を開始します。

Intentしかし、私はこれを達成するためにいくつかのフラグトリックを使用したいと思います。上で概説した2つの方法のいずれかを使用しなくても、アプリケーションの要件を満たすことができると私は嬉しく思います。

Intentまたはマニフェスト設定を使用してこれを達成する方法はありますか、または私の2番目のオプションは、LinkedList開いているアクティビティを維持することが最良のオプションですか?または、私が完全に見落としている別のオプションはありますか?

回答:


213

IMHOをより堅牢な別のアプローチを提案できます。基本的に、ログイン状態を維持する必要があるすべてのアクティビティにログアウトメッセージをブロードキャストする必要があります。したがって、を使用しsendBroadcastBroadcastReceiver、すべてのActvitiesにをインストールできます。このようなもの:

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

レシーバー(保護されたアクティビティ):

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 **//
}

27
@Christopher、各アクティビティは、作成時にブロードキャストに登録します。バックグラウンドになると(つまり、新しいアクティビティがスタックの一番上に来ると)、そのonStop()が呼び出されますが、まだブロードキャストを受信できます。onStop()ではなくonDestroy()でunregisterReceiver()を呼び出すことを確認する必要があるだけです。
ラッセルデイビス、

9
スタック内のどこかのアクティビティがメモリを回復するためにOSによってシャットダウンされた場合、これは機能しますか?すなわち。上記のブロードキャストが送信された後、システムはそれが本当に終了したと見なし、[戻る]ボタンを押したときにそれを再作成しませんか?
JanŻankowski11年

5
これは洗練されたソリューションのように見えますが、これは同期ではないことに注意することが重要です。
Che Jami

34
良い解決策ですが、上記のコードで説明されているブロードキャストレシーバーの登録を使用する代わりに、LocalBroadcastManager.getInstance(this).registerReceiver(...)とLocalBroadcastManager.getInstance(this).unregisterReceiver(..)を使用する必要があります。 。それ以外の場合、アプリケーションは他のアプリケーションからインテントを受け取る可能性があります(セキュリティ上の懸念)
Uku Loskit 2013年

5
@ウォーロックあなたは正しいです。このアプローチの落とし穴は、バックスタック内のアクティビティがシステムによって破棄される場合です(前述のメモリ不足のシナリオなど、さまざまな理由で発生する可能性があります)。その場合、Activityインスタンスはブロードキャストを受信できません。ただし、システムは、アクティビティを再作成することにより、そのアクティビティを引き続きナビゲートします。これは、開発者設定の「アクティビティを保持しない」をオンにすることでテスト/再現できます
Eric Sc​​hlenz 2014年

151

新しいAndroidプログラマーがこの問題を調査し、これらのStackOverflowスレッドをすべて読むのに1日を費やすことは、一節の儀式のようです。私は新たに始められ、将来の巡礼者を助けるために私の謙虚な経験の痕跡をここに残します。

まず、私の研究ごとにこれを行うには明白なまたは即時方法はありません(as of September 2012).あなたは、単純なことができると思うだろうstartActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)けどなし

あなたは行うことができますstartActivity(new Intent(this, LoginActivity.class))してFLAG_ACTIVITY_CLEAR_TOP-そしてこれは、スタックをダウン検索LoginActivityの以前の元のインスタンスを見つけ、それを再作成し、(上向き)スタックの残りの部分をクリアするための枠組みが発生します。そして、ログインはおそらくスタックの最下部にあるため、スタックは空になり、[戻る]ボタンはアプリケーションを終了するだけです。

ただし、これは、以前にLoginActivityの元のインスタンスをスタックのベースに残したままにした場合にのみ機能します。多くのプログラマのように、あなたがすることを選んだ、場合finish()そのLoginActivityユーザーが正常にログインした後、それはスタックの底に長いと全くませんFLAG_ACTIVITY_CLEAR_TOPセマンティクスが適用されません...あなたは新しいを作成し終わるLoginActivity既存のスタックの一番上に。これは、ほぼ間違いなくあなたが望んでいることではありません(ユーザーがログインから前の画面に「戻る」ことができる奇妙な動作)。

そのfinish()ためLoginActivity、以前にを使用したことがある場合は、スタックをクリアして新しいスタックを開始するためのメカニズムを追求する必要がありますLoginActivity@doreamonこのスレッドでの回答が最良の解決策のようです(少なくとも私の謙虚な目に対して)。

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

LoginActivityを存続させるかどうかのトリッキーな影響がこの混乱の多くを引き起こしていると私は強く疑います。

幸運を。


5
いい答えだ。FLAG_ACTIVITY_CLEAR_TOPトリックは、ほとんどの人が使用するようにアドバイスしていますが、LoginActivityを終了した場合は機能しません。
Konsumierer、2012年

回答ありがとうございます。別のシナリオは、ログインアクティビティを呼び出さなくてもセッションがある場合(fbなど)にあるため、スタックにログインアクティビティのポイントがありません。その後、上記のアプローチは無意味です。
Prashanth Debbadwar

118

更新

super finishAffinity()メソッドはコードを削減するのに役立ちますが、同じことを実現します。現在のアクティビティとスタック内のすべてのアクティビティを終了しますgetActivity().finishAffinity()。フラグメント内にいる場合に使用します。

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

元の回答

LoginActivity-> HomeActivity-> ...-> SettingsActivityがsignOut()を呼び出すと仮定します。

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

HomeActivity:

@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();
}

これは私にとってはうまくいきます。あなたにも役立つことを願っています。:)


1
あなたのソリューションは、ユーザーがsignOutをクリックして1つのアクティビティ(HomeActivity)に戻ることを前提としていると思います。スタックに10のアクティビティがある場合はどうなりますか?
IgorGanapolsky 2012年

11
HomeActivityの上に10のアクティビティがある場合、FLAG_ACTIVITY_CLEAR_TOPフラグはそれらすべてをクリーンアップするのに役立ちます。
thanhbinh84 2012年

1
これは、HomeActivityの開始時に、OnCreate of HomeActivityを取得した場合にのみ機能します。ホームアクティビティを開始するだけでは、既に終了または破棄されていない限り、必ずしも再作成されません。HomeActivityを再作成する必要がない場合、OnCreateは呼び出されず、ログアウトすると、ホームアクティビティに座ったままになります。
topwik

1
これは可能な解決策であり、ログアウトなどの単純な(ユーザーにとっての)機能をコーディングする必要はほとんどありません。
Subin Sebastian 2013

2
Intent.FLAG_ACTIVITY_CLEAR_TOPフラグは、HomeActivityを含むすべてのアクティビティをクリーンアップするのに役立ちます。したがって、このアクティビティを再度開始すると、onCreate()メソッドが呼び出されます。
thanhbinh84 2014年

73

API 11以降を使用している場合は、これを試すことができますFLAG_ACTIVITY_CLEAR_TASK。-発生している問題に正確に対処しているようです。明らかに、API 11以前の群衆は、@ doreamonが示唆するように、すべてのアクティビティに追加のチェックを行わせる、またはその他のトリックのいくつかの組み合わせを使用する必要があります。

(また注意してください:これを使用するには、渡す必要があります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();

1
魅力のように働いています。どうもありがとうございました!私が最小API 14で開発しているとき、それが実装する唯一のものです。
AndyB

1
このLoginActivityのソリューションを使用する場合、FLAG_ACTIVITY_CLEAR_TOPは必要ないと思います。
Bharat Dodeja

finish();ログアウト後に戻ってこないようにするだけでいいと思います。フラグを設定する必要性は何ですか?
Pankaj 2017年

これにより、Activityコンテキストの外部からstartActivity()を呼び出すという例外が発生します。FLAG_ACTIVITY_NEW_TASKフラグが必要です
Aman

31

私もこれに数時間を費やしました...そしてFLAG_ACTIVITY_CLEAR_TOPはあなたが望むように聞こえることに同意します:起動されているアクティビティを除いてスタック全体をクリアし、[戻る]ボタンがアプリケーションを終了します。ただし、Mike Repassが述べたように、FLAG_ACTIVITY_CLEAR_TOPは、起動しているアクティビティがすでにスタックにある場合にのみ機能します。アクティビティがない場合、フラグは何もしません。

何をすべきか?起動中のアクティビティをFLAG_ACTIVITY_NEW_TASKを使用してスタックに入れます。これにより、そのアクティビティが履歴スタックの新しいタスクの開始になります。次に、FLAG_ACTIVITY_CLEAR_TOPフラグを追加します。

これで、FLAG_ACTIVITY_CLEAR_TOPがスタック内の新しいアクティビティを見つけに行くと、そこにあり、他のすべてがクリアされる前にプルアップされます。

これが私のログアウト関数です。Viewパラメータは、関数が関連付けられているボタンです。

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

1
FLAG_ACTIVITY_CLEAR_TASKまだ追加されていないため、API <= 10で動作しません
Youans '11 / 11/13

1
@christinacあなたはFLAG_ACTIVITY_CLEAR_TOPについて話していて、コードスニペットにFLAG_ACTIVITY_CLEAR_TASKが含まれています。どちらが有効ですか?
MarianPaździoch15年

10

たくさんの答え。これも役に立つかもしれません-

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

Kotlinバージョン-

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

4

これを使用すると、役に立ちます。わずかに変更されたxbakesxの回答。

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

4

受け入れられたソリューションは正しくありません。ブロードキャストレシーバーを使用することはこの問題に適さないため、問題があります。アクティビティがすでにonDestroy()メソッドを呼び出している場合、レシーバーは取得されません。最善の解決策は、共有設定にブール値を設定し、アクティビティのonCreate()メソッドでそれをチェックすることです。ユーザーがログインしていないときに呼び出さない場合は、アクティビティを終了します。以下がそのサンプルコードです。とてもシンプルで、あらゆる条件で機能します。

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

1
もう何年か経ちましたが、両方をやったと思います。各アクティビティはLoggedInActivityを拡張し、onCreate()でユーザーのログインステータスをチェックしました。LoggedInActivityは、「ユーザーがログアウトした」ブロードキャストもリッスンし、これに応答するために必要なことは何でもしました。
スカイラー、2015

2
もっとおそらくあなたはメソッドにcheckAuthStatusを置くべきonResume()です。戻るボタンを押すと、アクティビティが作成されるのではなく再開される可能性が高くなるためです。
Maysi

1
@maysi提案ありがとうございます。そうすると、このように[戻る]ボタンも正しく機能します。エントリを更新しました
Yekmer Simsek

4

時々finish()動作していない

私はその問題を解決しました

finishAffinity()


3

この質問には別のアプローチをお勧めします。多分それは最も効率的なものではないかもしれませんが、私はそれが適用するのが最も簡単で、非常に少ないコードを必要とすると思います。最初のアクティビティ(私の場合はログインアクティビティ)で次のコードを記述しても、ユーザーはログアウト後に以前に起動したアクティビティに戻ることができません。

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

LoginActivityは、ユーザーがログインした直後に終了するため、後で[戻る]ボタンを押して元に戻すことはできないと想定しています。代わりに、ユーザーは適切にログアウトするために、アプリ内のログアウトボタンを押す必要があります。このログアウトボタンが実装するのは、次のような単純なインテントです。

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

すべての提案を歓迎します。


2

選択した答えは巧妙でトリッキーです。ここに私がそれをした方法があります:

LoginActivityはタスクのルートアクティビティです。Manifest.xmlでandroid:noHistory = "true"を設定します。SettingsActivityからログアウトする場合は、次のように実行できます。

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);

2

これが私がアプリで思いついたソリューションです。

LoginActivityで、ログインを正常に処理した後、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);
}

次に、私のLoginActivityのonActivityForResultメソッドで:

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

最後に、他のアクティビティでログアウトを処理した後:

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

Gingerbreadの場合は、MainActivityから戻るボタンを押すと、LoginActivityがすぐに非表示になります。Honeycomb以降では、ログインの処理後にLoginActivityを完了するだけで、ログアウトの処理後に適切に再作成されます。


それは私のために働いていません。私の場合、フラグメントアクティビティを使用しました。Onactivityresultメソッドは呼び出されません。
Mohamed Ibrahim

1

これは私のために働きました:

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

提供するソリューションについてもう少し説明を追加して、回答を詳しく説明していただけませんか?
abarisone 2015年

0

StartActivityForResultでアクティビティを開始し、ログアウト中に結果を設定し、結果に従ってアクティビティを終了します

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

}

0

@doreamonが提供するソリューションは、1つを除くすべてのケースで正常に機能します。

ログイン後、Killing Login画面のユーザーが中央の画面に直接移動した場合。たとえば、A-> B-> Cのフローで、次のようにナビゲートします。ログイン-> B-> C->ショートカットを押してホームに移動します。FLAG_ACTIVITY_CLEAR_TOPを使用すると、Home(A)がスタック履歴にないため、Cアクティビティのみがクリアされます。A画面で[戻る]を押すと、Bに戻ります。

この問題に取り組むために、アクティビティスタック(Arraylist)を保持し、ホームが押されたときに、このスタック内のすべてのアクティビティを強制終了する必要があります。


0

SharedPreferencesまたはApplication Activityでフラグを管理することで可能です。

アプリの起動時に(スプラッシュスクリーンで)、フラグをfalseに設定します。ログアウトクリックイベントでフラグをtrueに設定し、すべてのアクティビティのOnResume()でフラグがtrueかどうかを確認してから、finish()を呼び出します。

それは魅力のように機能します:)


0

ログアウトをクリックすると、これを呼び出すことができます

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

前のアクティビティのonActivityResult()は、すべてのアクティビティが完了するまで、上記のコードを再度呼び出します。


1
つまり、finish()を一度にブロードキャストするのではなく、すべてのアクティビティを直線的にトラバースする必要がありますか?
IgorGanapolsky 2012年

@Igor G.はい、これは連続して終了する別の方法です
Mak

-1

1つのオプションは、各アクティビティのonCreateにログインステータスをチェックさせ、ログインしていない場合はfinish()をチェックさせることです。私はこのオプションが好きではありません。アクティビティが閉じたときに戻るボタンが引き続き使用できるためです。

やりたいことは、onStop()またはonPause()メソッドでlogout()およびfinish()を呼び出すことです。これにより、アクティビティがスタックになくなるため、アクティビティが再開されたときにAndroidがonCreate()を呼び出すようになります。次に、言うとおりに、onCreate()でログインステータスを確認し、ログインしていない場合はログイン画面に転送します。

もう1つのことは、onResume()でログインステータスを確認し、ログインしていない場合は、finish()でログインアクティビティを起動することです。


アクティビティが一時停止または停止するたびにログアウトしない方がいいです。また、アプリケーションがログアウトまたはログインを開始するため、実際にログインしているかどうかを確認する必要はありません。
スカイラー

@リカルド:あなたのソリューションはBroadcastReceiverを必要としませんか?
IgorGanapolsky 2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.